将多个DbContexts与通用存储库和工作单元一起使用

我的应用程序越来越大,到目前为止,我有一个MyDbContext ,它包含我在我的应用程序中需要的所有表。 我希望(为了概述)将它们分成多个DbContext ,如MainDbContextEstateModuleDbContextAnotherModuleDbContextUserDbContext

我不确定这是怎么做的可能因为我现在正在使用dependecy injection(ninject)将我的DbContext放在我的UnitOfWork类上,如:

 kernel.Bind(typeof(IUnitOfWork)).To(typeof(UnitOfWork)); 

我是否应该通过dependency injection和显式设置我希望在我的服务上使用的DbContext来删除此方法,例如:

 private readonly EstateService _estateService; public HomeController() { IUnitOfWork uow = new UnitOfWork(); _estateService = new EstateService(uow); } 

代替:

 private readonly EstateService _estateService; public HomeController(IUnitOfWork uow) { _estateService = new EstateService(uow); } 

或者这有另一种更好的方法吗? 另外作为一个附带问题,我不喜欢将uow传递给我的服务 – 还有另一种(更好的)方法吗?

我有这个IDbContext和MyDbContext:

 public interface IDbContext { DbSet Set() where T : class; DbEntityEntry Entry(T entity) where T : class; int SaveChanges(); void Dispose(); } public class MyDbContext : DbContext, IDbContext { public DbSet Table1 { get; set; } public DbSet Table1 { get; set; } public DbSet Table1 { get; set; } public DbSet Table1 { get; set; } public DbSet Table1 { get; set; } /* and so on */ static MyDbContext() { Database.SetInitializer(new CreateDatabaseIfNotExists()); } public MyDbContext() : base("MyDbContext") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { } } 

然后我有这个IRepository和实现:

 public interface IRepository where T : class { IQueryable GetAll(); void Add(T entity); void Delete(T entity); void DeleteAll(IEnumerable entity); void Update(T entity); bool Any(); } public class Repository : IRepository where T : class { private readonly IDbContext _context; private readonly IDbSet _dbset; public Repository(IDbContext context) { _context = context; _dbset = context.Set(); } public virtual IQueryable GetAll() { return _dbset; } public virtual void Add(T entity) { _dbset.Add(entity); } public virtual void Delete(T entity) { var entry = _context.Entry(entity); entry.State = EntityState.Deleted; _dbset.Remove(entity); } public virtual void DeleteAll(IEnumerable entity) { foreach (var ent in entity) { var entry = _context.Entry(ent); entry.State = EntityState.Deleted; _dbset.Remove(ent); } } public virtual void Update(T entity) { var entry = _context.Entry(entity); _dbset.Attach(entity); entry.State = EntityState.Modified; } public virtual bool Any() { return _dbset.Any(); } } 

以及处理DbContext完成工作的IUnitOfWork和实现

 public interface IUnitOfWork : IDisposable { IRepository GetRepository() where TEntity : class; void Save(); } public class UnitOfWork : IUnitOfWork where TContext : IDbContext, new() { private readonly IDbContext _ctx; private readonly Dictionary _repositories; private bool _disposed; public UnitOfWork() { _ctx = new TContext(); _repositories = new Dictionary(); _disposed = false; } public IRepository GetRepository() where TEntity : class { // Checks if the Dictionary Key contains the Model class if (_repositories.Keys.Contains(typeof(TEntity))) { // Return the repository for that Model class return _repositories[typeof(TEntity)] as IRepository; } // If the repository for that Model class doesn't exist, create it var repository = new Repository(_ctx); // Add it to the dictionary _repositories.Add(typeof(TEntity), repository); return repository; } public void Save() { _ctx.SaveChanges(); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (this._disposed) return; if (disposing) { _ctx.Dispose(); } this._disposed = true; } } 

除非存在逻辑接缝,否则不要将模块化数据块拆分为多个DbContext 。 来自DbContextA实体不能具有DbContextA实体的自动导航或集合属性。 如果拆分上下文,则代码必须负责手动强制执行约束并在上下文之间加载相关数据。

为了“概述”(又名保持理智),您仍然可以按模块组织CLR代码和数据库表。 对于POCO,将它们保存在不同名称空间下的不同文件夹中。 对于表,您可以按模式进行分组。 (但是,在按照SQL模式进行组织时,您可能还应该考虑安全性因素。例如,如果有任何数据库用户应该对某些表具有受限访问权限,请根据这些规则设计模式。)然后,您可以执行此操作在构建模型时:

 ToTable("TableName", "SchemaName"); // put table under SchemaName, not dbo 

当它的实体与第一个DbContext任何实体没有关系时,才使用单独的DbContext

我也同意Wiktor,因为我不喜欢你的界面和实现设计。 我特别不喜欢public interface IRepository 。 另外,为什么声明多个public DbSet TableN { get; set; } public DbSet TableN { get; set; } 你的MyDbContext ? 请帮帮忙,阅读这篇文章 ,然后阅读本文 。

您可以使用以下EF界面设计大大简化代码:

 interface IUnitOfWork { int SaveChanges(); } interface IQueryEntities { IQueryable Query(); // implementation returns Set().AsNoTracking() IQueryable EagerLoad(IQueryable queryable, Expression> expression); // implementation returns queryable.Include(expression) } interface ICommandEntities : IQueryEntities, IUnitOfWork { T Find(params object[] keyValues); IQueryable FindMany(); // implementation returns Set() without .AsNoTracking() void Create(T entity); // implementation changes Entry(entity).State void Update(T entity); // implementation changes Entry(entity).State void Delete(T entity); // implementation changes Entry(entity).State void Reload(T entity); // implementation invokes Entry(entity).Reload } 

如果声明MyDbContext : ICommandEntities ,则只需设置几个方法来实现接口(通常是单行)。 然后,您可以将3个接口中的任何一个注入到服务实现中:通常ICommandEntities用于具有副作用的操作,而IQueryEntities用于不具有副作用的操作。 任何只负责保存状态的服务(或服务修饰者)都可以依赖IUnitOfWork 。 我不同意Controller应该依赖于IUnitOfWork 。 使用上述设计,您的服务应在返回Controller之前保存更改。

如果你的应用程序中有多个单独的DbContext类是有意义的,你可以像Wiktor建议的那样做 ,并使上述接口通用。 然后,您可以dependency injection服务,如下所示:

 public SomeServiceClass(IQueryEntities users, ICommandEntities estateModule) { ... } public SomeControllerClass(SomeServiceClass service) { ... } // Ninject will automatically constructor inject service instance into controller // you don't need to pass arguments to the service constructor from controller 

创建广泛的每聚合(甚至更糟的每个实体)存储库接口可以与EF对抗,繁殖无聊的管道代码,并过度注入构造函数。 相反,为您的服务提供更多灵活性。 像.Any()这样的方法不属于接口,您可以在服务方法中调用QueryFindMany返回的IQueryable上的扩展。

您的工作单元界面不是通用的,但实现方式是。 清理这个的最简单方法是决定并遵循相同的惯例。

例如,也使您的界面通用。 这样,您可以将三个不同的接口(具有三个不同通用参数的相同接口)注册到三个不同的实现:

  container.Bind( typeof> ).To( typeof> ); ... 

是的,将您的工作单元注入控制器/服务是个好主意。