Creating Data Layer

In this session we will create the full data layer of our application using Entity Framework.

Creating directories

First let's create the directories that will be used in our project. We will create the directory of Configuration, Context, extensions, Repositories and UoW.


Creating the Files

Now first of all let's install Entity Framework core by right clicking on dependencies as below example.



Same for lib Microsoft.EntityFrameworkCore where its function is to customize through fluent api entity vs database


Now let's first create the extension that we will use in implementing our generic repository to concatenate expressions.

              
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace Rodolfo.Schmidt.Data.Extensions
{
    public static class IQueryableExtensions
    {
        public static IQueryable<T> EagerLoad<T>(this IQueryable<T> query, params
            Expression<Func<T, object>>[] includes) where T : class
        {
            if (includes != null)
                query = includes.Aggregate(query,
                    (current, include) => current.Include(include));

            return query;
        }
    }
}

              
            

Now let's create the configuration file responsible for parameterizing the entity according to your database standards as well as field type specific settings among others.

              
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Rodolfo.Schmidt.Domain.Entities;
using System;
using System.Collections.Generic;
using System.Text;

namespace Rodolfo.Schmidt.Data.Configuration
{
    class PersonConfiguration : IEntityTypeConfiguration<Person>
    {
        public void Configure(EntityTypeBuilder<Person> modelBuilder)
        {
            modelBuilder.ToTable("PERSON");

            modelBuilder
                .Property(p => p.Id)
                .HasColumnName("ID");

            modelBuilder
                .Property(p => p.Name)
                .HasColumnName("NAME")
                .HasColumnType("varchar(100)")
                .HasMaxLength(100)
                .IsRequired();

            modelBuilder
                .Property(p => p.Age)
                .HasColumnName("AGE")
                .IsRequired();

        }
    }
    
}

              
            

Next we will create the file responsible for configuring our ORM. Let's call it RodolfoSchmidtDbContext. In it we will put our configuration file to set the bank settings for our mapped entity in ORM. We also created an overloaded constructor to receive the bank connection string and initialize ORM and finally map our entire Domain with the bank through ORM.

              
using Microsoft.EntityFrameworkCore;
using Rodolfo.Schmidt.Data.Configuration;
using Rodolfo.Schmidt.Domain.Entities;
using System;
using System.Collections.Generic;
using System.Text;

namespace Rodolfo.Schmidt.Data.Context
{
    public class RodolfoSchmidtDbContext : DbContext
    {
        public RodolfoSchmidtDbContext()
        {
        }
        public RodolfoSchmidtDbContext(DbContextOptions<RodolfoSchmidtDbContext> options) : base(options)
        {

        }

        public DbSet<Person> Person { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.ApplyConfiguration(new PersonConfiguration());
        }
    }
}

              
            

We're almost there, now let's implement our generic repository

              
                
using Microsoft.EntityFrameworkCore;
using Rodolfo.Schmidt.Data.Context;
using Rodolfo.Schmidt.Data.Extensions;
using Rodolfo.Schmidt.Domain.Interfaces.Repositories;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace Rodolfo.Schmidt.Data.Repositories
{
    public abstract class Repository<TEntity> : IRepository<TEntity> where TEntity : class
    {
        protected readonly RodolfoSchmidtDbContext _dbContext;

        public Repository(RodolfoSchmidtDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public async Task<IEnumerable<TEntity>> FetchExpression(Expression<Func<TEntity, bool>> where) => await _dbContext.Set<TEntity>().Where(where).ToListAsync();

        public async Task<IEnumerable<TEntity>> FetchExpression(Expression<Func<TEntity, bool>> where, params Expression<Func<TEntity, object>>[] includes)
        {
            var query = _dbContext.Set<TEntity>() as IQueryable<TEntity>;
            query = query.EagerLoad(includes);

            return await query.Where(where).ToListAsync();
        }

        public async Task<IEnumerable<TEntity>> FetchExpression(Expression<Func<TEntity, bool>> where, int start, int limit, params Expression<Func<TEntity, object>>[] includes)
        {
            var query = _dbContext.Set<TEntity>() as IQueryable<TEntity>;
            query = query.EagerLoad(includes);
            query = query.Where(where);
            query = query.Skip(start).Take(limit);

            return await query.ToListAsync();
        }

        public async Task<IEnumerable<TEntity>> FetchExpression(Expression<Func<TEntity, bool>> where, Expression<Func<TEntity, int>> orderBy, params Expression<Func<TEntity, object>>[] includes)
        {
            var query = _dbContext.Set<TEntity>() as IQueryable<TEntity>;
            query = query.EagerLoad(includes);

            return await query.Where(where).OrderBy(orderBy).ToListAsync();
        }

        public async Task<IEnumerable<TEntity>> GetAll() => await _dbContext.Set<TEntity>().ToListAsync();

        public async Task<IEnumerable<TEntity>> GetAll(params Expression<Func<TEntity, object>>[] includes)
        {
            var query = _dbContext.Set<TEntity>() as IQueryable<TEntity>;
            query = query.EagerLoad(includes);

            return await query.ToListAsync();
        }
        public async Task<TEntity> GetById(int id) => await _dbContext.Set<TEntity>().FindAsync(id);

        public void Save(TEntity entity) => _dbContext.Add(entity);

        public void SaveMany(IEnumerable<TEntity> entity) => _dbContext.AddRange(entity);

        public void Delete(TEntity entity) => _dbContext.Remove(entity);

        public void DeleteMany(IEnumerable<TEntity> entity) => _dbContext.RemoveRange(entity);

    }
}

              
            

And finally, our unit of work responsible for committing any ORM memory changes / insertions

              
using Rodolfo.Schmidt.Data.Context;
using Rodolfo.Schmidt.Domain.Interfaces.UoW;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace Rodolfo.Schmidt.Data.UoW
{
    public class UnitOfWork : IUnitOfWork
    {
        private readonly RodolfoSchmidtDbContext _context;

        public UnitOfWork(RodolfoSchmidtDbContext context)
        {
            _context = context;
        }

        public async Task<bool> CommitAsync()
        {
            return (await _context.SaveChangesAsync()) > 0;
        }

    }
}

              
            

Now to close the data layer with all pre sets done we can implement the person repository

              
                  using Rodolfo.Schmidt.Data.Context;
                  using Rodolfo.Schmidt.Domain.Entities;
                  using Rodolfo.Schmidt.Domain.Interfaces.Repositories;
                  using System;
                  using System.Collections.Generic;
                  using System.Text;
                  using System.Threading.Tasks;
                  
                  namespace Rodolfo.Schmidt.Data.Repositories
                  {
                      public class PersonRepository : Repository<Person>, IPersonRepository
                      {
                          public PersonRepository(RodolfoSchmidtDbContext dbContext) : base(dbContext)
                          {
                          }
                  
                          public void DoDeletePerson(Person person)
                          {
                              Delete(person);
                          }
                  
                          public void DoSavePerson(Person person)
                          {
                              Save(person);
                          }
                  
                          public async Task<IEnumerable<Person>> GetPeople() => await GetAll();
                  
                          public async Task<Person> GetPersonById(int id) => await GetById(id);
                      }
                  }
                  
              
              

Our data layer is ready.