0

How do I update this code below to allow for multiple DbContexts? I searched other posts and I tried to make my unit of work and dbfactory classes accept a generic type but I had trouble with the RepositoryBase class, the repository, the service and tying it all together; I still struggle with generics in C#. Can anyone help me out?

DbFactory.cs

public class DbFactory : Disposable, IDbFactory
{
    WilMpeContext _dbContext;

    public WilMpeContext Init()
    {
        return _dbContext ?? (_dbContext = new WilMpeContext());
    }

    protected override void DisposeCore()
    {
        _dbContext?.Dispose();
    }
}

Disposable.cs

public class Disposable : IDisposable
{
    private bool isDisposed;

    ~Disposable()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    private void Dispose(bool disposing)
    {
        if (!isDisposed && disposing)
        {
            DisposeCore();
        }

        isDisposed = true;
    }

    // Ovveride this to dispose custom objects
    protected virtual void DisposeCore()
    {
    }
}

iDbFactory.cs

public interface IDbFactory : IDisposable
{
    WilMpeContext Init();
}

IRepository.cs

public interface IRepository<T> where T : class
{
    // Marks an entity as new
    void Add(T entity);
    // Marks an entity as modified
    void Update(T entity);
    // Marks an entity to be removed
    void Delete(T entity);
    void Delete(Expression<Func<T, bool>> where);
    // Get an entity by int id
    T GetById(int id);

    // Get an entity using delegate
    T Get(Expression<Func<T, bool>> where);
    // Gets all entities of type T
    IEnumerable<T> GetAll();
    // Gets entities using delegate
    IEnumerable<T> GetMany(Expression<Func<T, bool>> where);
}

IUnitOfWork.cs

public interface IUnitOfWork
{
    void Commit();
}

RepositoryBase.cs

public abstract class RepositoryBase<T> where T : class
{
    #region Properties
    private WilMpeContext _dataContext;
    private readonly IDbSet<T> _dbSet;

    protected IDbFactory DbFactory
    {
        get;
        private set;
    }

    protected WilMpeContext DbContext
    {
        get { return _dataContext ?? (_dataContext = DbFactory.Init()); }
    }
    #endregion

    protected RepositoryBase(IDbFactory dbFactory)
    {
        DbFactory = dbFactory;
        _dbSet = DbContext.Set<T>();
    }

    #region Implementation
    public virtual void Add(T entity)
    {
        _dbSet.Add(entity);
    }

    public virtual void Update(T entity)
    {
        _dbSet.Attach(entity);
        _dataContext.Entry(entity).State = EntityState.Modified;
    }

    public virtual void Delete(T entity)
    {
        _dbSet.Remove(entity);
    }

    public virtual void Delete(Expression<Func<T, bool>> where)
    {
        IEnumerable<T> objects = _dbSet.Where<T>(where).AsEnumerable();
        foreach (T obj in objects)
            _dbSet.Remove(obj);
    }

    public virtual T GetById(int id)
    {
        return _dbSet.Find(id);
    }

    public virtual IEnumerable<T> GetAll()
    {
        return _dbSet.ToList();
    }

    public virtual IEnumerable<T> GetMany(Expression<Func<T, bool>> where)
    {
        return _dbSet.Where(where).ToList();
    }

    public T Get(Expression<Func<T, bool>> where)
    {
        return _dbSet.Where(where).FirstOrDefault<T>();
    }

    #endregion

}

UnitOfWork.cs

public class UnitOfWork : IUnitOfWork
{
    private readonly IDbFactory _dbFactory;
    private WilMpeContext _dbContext;

    public UnitOfWork(IDbFactory dbFactory)
    {
        this._dbFactory = dbFactory;
    }

    public WilMpeContext DbContext
    {
        get { return _dbContext ?? (_dbContext = _dbFactory.Init()); }
    }

    public void Commit()
    {
        DbContext.Commit();
    }
}

WilMpeContext.cs - this is one of my DbContexts but now I need another and I'm not sure how to implement that with my existing design

public class WilMpeContext : IdentityDbContext<ApplicationUser>

{
    public WilMpeContext()
        : base("name=DefaultConnection", throwIfV1Schema: false) { }


    public IDbSet<AppSetting> AppSettings { get; set; }
    //the rest of the tables were removed for brevity

    public virtual void Commit()
    {
        base.SaveChanges();
    }

    public static WilMpeContext Create()
    {
        return new WilMpeContext();

    }

    protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

    }

}

Here is how I setup the repository to use the unit of work. AppSetting is a table in my model:

public interface IAppSettingRepository : IRepository<AppSetting>
{
    void UpdateAppSetting(AppSetting appSetting);
}

public class AppSettingRepository : RepositoryBase<AppSetting>,IAppSettingRepository
{
    public AppSettingRepository(IDbFactory dbFactory)
        : base(dbFactory) { }

    //an example of how I do something in the database.  See I use DbContext from RepositoryBase
    public void UpdateAppSetting(AppSetting appSetting)
    {
        DbContext.Entry(appSetting).State = EntityState.Modified;
    }
}

And this is my service:

public class AppSettingService : IAppSettingService
{
    private readonly IAppSettingRepository _appSettingRepository;
    private readonly IUnitOfWork _unitOfWork;

    public AppSettingService(IAppSettingRepository appSettingRepository,
        IUnitOfWork unitOfWork)
    {
        _appSettingRepository = appSettingRepository;
        _unitOfWork = unitOfWork;
    }

    //call repository to do database stuff and then commit changes
    public void UpdateAppSetting(AppSetting appSetting)
    {
        _appSettingRepository.UpdateAppSetting(appSetting);
        _unitOfWork.Commit();
    }

}
MLightsOut
  • 1
  • 1
  • 1

1 Answers1

3

You need to make all your interfaces and classes that is using your current WilMpeContext to generic. Also you would need to put a generic constraint on new generic.

First make your Interfaces generic for Unit of work and DBFactory:

public interface IUnitOfWork<TContext> where TContext: DbContext, new()
{
    void Commit();
}


public interface IDbFactory<TContext> : IDisposable where TContext: DbContext, new()
{
    TContext Init();
}

Now let's update your Classes for UnitOfWork and DBFactory:

public class UnitOfWork<TContext> : IUnitOfWork<TContext> where TContext: DbContext, new()
{
    private readonly IDbFactory<TContext> _dbFactory;
    private TContext _dbContext;

    public UnitOfWork(IDbFactory<TContext> dbFactory)
    {
        this._dbFactory = dbFactory;
    }

    public TContext DbContext
    {
        get { return _dbContext ?? (_dbContext = _dbFactory.Init()); }
    }

    public void Commit()
    {
        this.DbContext.SaveChanges();
    }
}

public class DbFactory<TContext> : Disposable, IDbFactory<TContext> where TContext: DbContext, new()
{
    TContext _dbContext;

    public TContext Init()
    {
        return _dbContext ?? (_dbContext = new TContext());
    }

    protected override void DisposeCore()
    {
        _dbContext?.Dispose();
    }
}

Add an additional generic on Repository base class:

public abstract class RepositoryBase<T, TContext> where T : class where TContext: DbContext, new()
{
    #region Properties
    private TContext _dataContext;
    private readonly IDbSet<T> _dbSet;

    protected IDbFactory<TContext> DbFactory
    {
        get;
        private set;
    }

    protected TContext DbContext
    {
        get { return _dataContext ?? (_dataContext = this.DbFactory.Init()); }
    }
    #endregion

    protected RepositoryBase(IDbFactory<TContext> dbFactory)
    {
        DbFactory = dbFactory;
        _dbSet = DbContext.Set<T>();
    }

    #region Implementation
    public virtual void Add(T entity)
    {
        _dbSet.Add(entity);
    }

    public virtual void Update(T entity)
    {
        _dbSet.Attach(entity);
        _dataContext.Entry(entity).State = System.Data.Entity.EntityState.Modified;
    }

    public virtual void Delete(T entity)
    {
        _dbSet.Remove(entity);
    }

    public virtual void Delete(Expression<Func<T, bool>> where)
    {
        IEnumerable<T> objects = _dbSet.Where<T>(where).AsEnumerable();
        foreach (T obj in objects)
            _dbSet.Remove(obj);
    }

    public virtual T GetById(int id)
    {
        return _dbSet.Find(id);
    }

    public virtual IEnumerable<T> GetAll()
    {
        return _dbSet.ToList();
    }

    public virtual IEnumerable<T> GetMany(Expression<Func<T, bool>> where)
    {
        return _dbSet.Where(where).ToList();
    }

    public T Get(Expression<Func<T, bool>> where)
    {
        return _dbSet.Where(where).FirstOrDefault<T>();
    }

    #endregion

}

Here is your repository that will be typed with your Actual Context WilMpeContext class.

public class AppSettingRepository : RepositoryBase<AppSetting, WilMpeContext>, IAppSettingRepository
{
    public AppSettingRepository(IDbFactory<WilMpeContext> dbFactory)
        : base(dbFactory) { }

    //an example of how I do something in the database.  See I use DbContext from RepositoryBase
    public void UpdateAppSetting(AppSetting appSetting)
    {
        DbContext.Entry(appSetting).State = System.Data.Entity.EntityState.Modified;
    }
}

Finally your AppsettingService

public class AppSettingService : IAppSettingService
{
    private readonly IAppSettingRepository _appSettingRepository;
    private readonly IUnitOfWork<WilMpeContext> _unitOfWork;

    public AppSettingService(IAppSettingRepository appSettingRepository,
        IUnitOfWork<WilMpeContext> unitOfWork)
    {
        _appSettingRepository = appSettingRepository;
        _unitOfWork = unitOfWork;
    }

    //call repository to do database stuff and then commit changes
    public void UpdateAppSetting(AppSetting appSetting)
    {
        _appSettingRepository.UpdateAppSetting(appSetting);
        _unitOfWork.Commit();
    }

}

Now your UnitOfWork infrastructure will support any DbContext for all repositories.

Udpate

Alternative solution(May be)

May be I'm wrong but why would you need multiple dbcontext if your dbContext is not shared across the applications. I'm sure you are using Asp.net 5 MVC project that generates it's own IdenityDbContext and you might have your other DBContext as well. You can merge these two context and this way you can keep your current implementation of UOW.

Check out these SO QnA if this is the actual problem.

ASP.NET Identity DbContext confusion

Merge MyDbContext with IdentityDbContext

vendettamit
  • 14,315
  • 2
  • 32
  • 54
  • Ah ha!! This is very helpful, thank you so much. The part I kept stumbling on was the RepositoryBase class because I did not understand how to add another generic. I need another DB context because I need to read data from a different SQL server (in this case a Historian SQL Server) within the web application. The web application needs to verify the part "passed" in the Historian SQL database before inserting any new production records to the WilMPEContext. That is why I need 2 dbContexts (2 connection strings). Am I on the right track making my unit of work accept a generic context? – MLightsOut Jun 22 '18 at 12:34
  • I'm sure with refactoring above you are on right track. But it can still be improved based on the usage. – vendettamit Jun 22 '18 at 14:52
  • As UoW is to keep integrity of transaction accross different dbcontexts (custom), this approach still does not provide transactional integrity, when any two command from both dbcontext need to be executed in one transaction, this gonna stuck. – ibubi Sep 06 '18 at 13:10