A Dependency Injection and Repository Pattern Example – I

Here is the usage of Dependency Injection and Depository Pattern in our project. We can see the layers and structure of the project and learn how to build a big project using the technologies mentioned before.

Entity Framework Model Layer

Firstly the EDMX model is built using entity framework database first mode in Cobra.Model project. We can see that the DBContext and POCO classes are generated by VS automatically:

And here is the EDMX model:

In CobraEntities.Context.cs, we use the connection string named CobraEntities.

namespace Cobra.Model
{
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    
    public partial class CobraEntities : DbContext
    {
        public CobraEntities()
            : base("name=CobraEntities")
        {
        }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            throw new UnintentionalCodeFirstException();
        }
    
        public virtual DbSet<Address> Addresses { get; set; }
        public virtual DbSet<AddressType> AddressTypes { get; set; }
        public virtual DbSet<Alert> Alerts { get; set; }
        public virtual DbSet<AlertType> AlertTypes { get; set; }
        public virtual DbSet<Country> Countries { get; set; }
        ...
    }
}

So in Web.config, the connection string is built:

<connectionStrings>
  <add name="CobraEntities" connectionString="metadata=res://*/CobraEntities.csdl|res://*/CobraEntities.ssdl|res://*/CobraEntities.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=***;initial catalog=Cobra;persist security info=True;user id=CobraAppUser;password=***;MultipleActiveResultSets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />
  <add name="CobraIdentityConnection" connectionString="Data Source=***;Initial Catalog=Cobra;Integrated Security=False;User ID=CobraAppUser;Password=***" providerName="System.Data.SqlClient" />
</connectionStrings>

You can see there is another connection string CobraIdentityConnection, which is used for identity system. Since in ASP.NET identity, code first mode is used. So we have to create two connection strings on the same database with two different providers:  System.Data.EntityClient is for database first mode (used by main CRUD operation) and System.Data.SqlClient is for code first mode (used by ASP.NET identity). Now we can use the CobraEntities to operate on the database.

Repository Layer

Unit of Work Sub-Layer

We consume the CobraEntities in UnitOfWork class which implement the IUnitOfWork interface:

namespace Cobra.Infrastructure.Data
{
    public interface IUnitOfWork
    {
        DbContext GetDbContext();
        DbEntityEntry<T> Entry<T>(T entity) where T : class;
        IDbSet<T> Set<T>() where T : class;
        void Commit();

        void Dispose();
    }
}

The method defined in the interface is very important. The GetDbContext() method will return the DbContext, that is, the CobraEntities.

The Entry<T>(T entity) method will return the DbEntry of a specific table, we can use it to change the data in the table of the database, something like that:

AucklandHighSchoolEntities db = new AucklandHighSchoolEntities();
db.Entry<Student>(s).State = EntityState.Modified;
db.SaveChanges();

The Set<T>() method will return the DbSet of a specific table, we can use it to change the data in the table of the database as well, something like that:

AucklandHighSchoolEntities db = new AucklandHighSchoolEntities();
db.Set<Student>().Add(s);
db.SaveChanges();

In the above methods, we use the generic version and  T is the entity (table), which means it will return the Set and Entry of the specific table according to what we pass to the variable T.

And the Commit(); will save the changes to database.

So in  UnitOfWork class:

namespace Cobra.Infrastructure.Data
{
    public class UnitOfWork : IUnitOfWork
    {
        private CobraEntities _dbContext;
        private bool _disposed = false;

        #region Lifetime

        public UnitOfWork(CobraEntities dbContext)
        {
            _dbContext = dbContext;
        }

        public DbContext GetDbContext()
        {
            return _dbContext;
        }
        #endregion

        #region Utilities

        public void Commit()
        {
            _dbContext.SaveChanges();
        }

        public IDbSet<T> Set<T>() where T : class
        {
            return _dbContext.Set<T>();
        }

        public DbEntityEntry<T> Entry<T>(T entity) where T : class
        {
            return _dbContext.Entry<T>(entity);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!this._disposed)
            {
                if (disposing)
                {
                    _dbContext.Dispose();
                }
            }
            this._disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion
    }
}

In the constructor, we pass in a CobraEntities instance, so that we can return DbSet and DbEntry of the table based on the CobraEntities. Since we are using Ninject for dependency injection, we put the following code in AppInfrastructureNinjectModule.cs:

Bind<CobraEntities>().ToMethod(ctx => new CobraEntities()).InRequestScope();

It means when we require a  CobraEntities, Ninject will create a instance of  CobraEntities for us automatically. So when we need an instance of UnitOfWork which needs an instance of CobraEntities, Ninject will create the instance of CobraEntities automatically for us. Now we can use the UnitOfWork to operate on the database.

Repository Sub-Layer

Here we need to consume UnitOfWork. Firstly we bind the interface and the class in Ninject:

Bind<IUnitOfWork>().To<UnitOfWork>();

It means when we need  IUnitOfWork, Ninject will instantiate UnitOfWork for us.

Then we create the IRepository interface which contains the basic CRUD operation on the table of the database:

namespace Cobra.Infrastructure
{
    public interface IRepository<TEntity> where TEntity : class
    {
        TEntity Add(TEntity model, bool persist = false);

        TEntity GetById(int id);
        TEntity GetByName(string name);

        IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> predicate);
        IEnumerable<TEntity> GetAll();

        void Update(TEntity model, bool persist = false);

        void Remove(int id, bool persist = false);
        void Remove(TEntity model, bool persist = false);

        void Save();
        
    }
}

We implement the interface in Repository.cs:

namespace Cobra.Infrastructure
{
    public class Repository<TEntity> : IRepository<TEntity> where TEntity : class 
    {
        private IUnitOfWork _unitOfWork;
        internal DbContext db;
        internal IDbSet<TEntity> dbSet;
        internal DbEntityEntry<TEntity> dbEntry; 


        public Repository(IUnitOfWork uow)
        {
            _unitOfWork = uow;
            dbSet = uow.Set<TEntity>();
            db = uow.GetDbContext();
        }


        public TEntity GetById(int id)
        {
            TEntity model = dbSet.Find(id);
            return model;
        }

        public TEntity GetByName(string name)
        {
            TEntity model = dbSet.Find(name);
            return model;
        }

        public IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> predicate)
        {
            return dbSet.Where(predicate).AsEnumerable();
        }

        public IEnumerable<TEntity> GetAll()
        {
            return dbSet.AsEnumerable();
        }

        public TEntity Add(TEntity model, bool persist = false)
        {
            dbSet.Add(model);
            Save(persist);

            return model;
        }

        public virtual void Update(TEntity entityToUpdate, bool persist = false)
        {
            var entry = db.Entry(entityToUpdate);

            if (entry.State == EntityState.Detached)
            {
                db.Set<TEntity>().Attach(entityToUpdate);
                entry.State = EntityState.Modified;
            }
            Save(persist);
        }

        public void Remove(int id, bool persist = false)
        {
            TEntity model = dbSet.Find(id);
            Remove(model, persist);
        }

        public void Remove(TEntity model, bool persist = false)
        {
            if (model != null)
            {
                if (db.Entry(model).State == EntityState.Detached)
                {
                    dbSet.Attach(model);
                }
                dbSet.Remove(model);
                Save(persist);
            }
        }

        public void Save()
        {
            Save(true);
        }

        private void Save(bool persist)
        {
            if (persist)
            {
                db.SaveChanges();
            }
        }
    }
}

Here when we want to instantiate the Repository object of the specific entity (table), Ninject will pass in the UnitOfWork instance. Now we can use the Repository object of the specific table, in which contains not only the data in the table, but also the CRUD methods that can operate on the table.

(to be continued)

 

Leave a Reply

Your email address will not be published. Required fields are marked *