我的应用程序越来越大,到目前为止我只有一个MyDbContext
,它包含了我在应用程序中需要的所有表。我希望(为了概述)将它们分成多个DbContext
,例如MainDbContext
,EstateModuleDbContext
,AnotherModuleDbContext
和UserDbContext
。
我不确定这是如何完成的,因为我现在正在使用dependecy injection(ninject)将我的DbContext放在我的UnitOfWork类上,如:
kernel.Bind(typeof(IUnitOfWork)).To(typeof(UnitOfWork<MyDbContext>));
我是否应该通过依赖注入和显式设置我希望在我的服务上使用的DbContext
来删除此方法,如:
private readonly EstateService _estateService;
public HomeController()
{
IUnitOfWork uow = new UnitOfWork<MyDbContext>();
_estateService = new EstateService(uow);
}
而不是:
private readonly EstateService _estateService;
public HomeController(IUnitOfWork uow)
{
_estateService = new EstateService(uow);
}
还是有另外一种更好的方法?另外作为一个附带问题,我不想将uow
传递给我的服务 - 还有另一种(更好的)方法吗?
代码
我有这个IDbContext和MyDbContext:
public interface IDbContext
{
DbSet<T> Set<T>() where T : class;
DbEntityEntry<T> Entry<T>(T entity) where T : class;
int SaveChanges();
void Dispose();
}
public class MyDbContext : DbContext, IDbContext
{
public DbSet<Table1> Table1 { get; set; }
public DbSet<Table2> Table1 { get; set; }
public DbSet<Table3> Table1 { get; set; }
public DbSet<Table4> Table1 { get; set; }
public DbSet<Table5> Table1 { get; set; }
/* and so on */
static MyDbContext()
{
Database.SetInitializer<MyDbContext>(new CreateDatabaseIfNotExists<MyDbContext>());
}
public MyDbContext()
: base("MyDbContext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
}
然后我有了这个IRepository和实现:
public interface IRepository<T> where T : class
{
IQueryable<T> GetAll();
void Add(T entity);
void Delete(T entity);
void DeleteAll(IEnumerable<T> entity);
void Update(T entity);
bool Any();
}
public class Repository<T> : IRepository<T> where T : class
{
private readonly IDbContext _context;
private readonly IDbSet<T> _dbset;
public Repository(IDbContext context)
{
_context = context;
_dbset = context.Set<T>();
}
public virtual IQueryable<T> 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<T> 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<TEntity> GetRepository<TEntity>() where TEntity : class;
void Save();
}
public class UnitOfWork<TContext> : IUnitOfWork where TContext : IDbContext, new()
{
private readonly IDbContext _ctx;
private readonly Dictionary<Type, object> _repositories;
private bool _disposed;
public UnitOfWork()
{
_ctx = new TContext();
_repositories = new Dictionary<Type, object>();
_disposed = false;
}
public IRepository<TEntity> GetRepository<TEntity>() 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<TEntity>;
}
// If the repository for that Model class doesn't exist, create it
var repository = new Repository<TEntity>(_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;
}
}
答案 0 :(得分:9)
除非存在逻辑接缝,否则不要将模块化数据块拆分为多个DbContext
。来自DbContextA
的实体无法在DbContextB
中拥有实体的自动导航或集合属性。如果拆分上下文,则代码必须负责手动强制执行约束并在上下文之间加载相关数据。
为了“概述”(a.k.a。保持理智),您仍然可以按模块组织CLR代码和数据库表。对于POCO,将它们保存在不同名称空间下的不同文件夹中。对于表,您可以按模式进行分组。 (但是,在按照SQL模式进行组织时,您可能还应考虑安全性因素。例如,如果有任何数据库用户应该对某些表具有受限访问权限,请根据这些规则设计模式。)然后,您可以执行此操作在构建模型时:
ToTable("TableName", "SchemaName"); // put table under SchemaName, not dbo
当其实体与您的第一个DbContext
中的任何实体没有任何关系时,才会使用单独的DbContext
。
我也同意Wiktor,因为我不喜欢你的界面&amp;实施设计。我特别不喜欢public interface IRepository<T>
。另外,为什么在public DbSet<TableN> TableN { get; set; }
中声明多个MyDbContext
?帮我一个忙,请阅读this article,然后阅读this one。
您可以使用如下的EF界面设计大大简化您的代码:
interface IUnitOfWork
{
int SaveChanges();
}
interface IQueryEntities
{
IQueryable<T> Query<T>(); // implementation returns Set<T>().AsNoTracking()
IQueryable<T> EagerLoad<T>(IQueryable<T> queryable, Expression<Func<T, object>> expression); // implementation returns queryable.Include(expression)
}
interface ICommandEntities : IQueryEntities, IUnitOfWork
{
T Find<T>(params object[] keyValues);
IQueryable<T> FindMany<T>(); // implementation returns Set<T>() without .AsNoTracking()
void Create<T>(T entity); // implementation changes Entry(entity).State
void Update<T>(T entity); // implementation changes Entry(entity).State
void Delete<T>(T entity); // implementation changes Entry(entity).State
void Reload<T>(T entity); // implementation invokes Entry(entity).Reload
}
如果声明MyDbContext : ICommandEntities
,则只需设置几种方法来实现接口(通常是单行)。然后,您可以将3个接口中的任何一个注入到服务实现中:通常ICommandEntities
用于具有副作用的操作,IQueryEntities
用于不具有副作用的操作。任何仅负责保存状态的服务(或服务修饰者)都可以依赖IUnitOfWork
。我不同意Controller
s应该依赖于IUnitOfWork
。使用上述设计,您的服务应该在返回Controller
之前保存更改。
如果您的应用中有多个单独的DbContext
类是有意义的,您可以do as Wiktor suggests并使上述接口通用。然后,您可以依赖注入服务,如下所示:
public SomeServiceClass(IQueryEntities<UserEntities> users,
ICommandEntities<EstateModuleEntities> 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()
这样的方法不属于界面,您可以在服务方法中调用IQueryable<T>
或Query<T>
返回的FindMany<T>
上的扩展程序。
答案 1 :(得分:2)
您的工作单元界面不是通用的,但实现方式是。清理它的最简单方法是决定并遵循相同的惯例。
例如,也要使您的界面通用。这样,您可以将三个不同的接口(具有三个不同通用参数的相同接口)注册到三个不同的实现:
container.Bind( typeof<IUnitOfWork<ContextOne>> ).To( typeof<UnitOfWork<ContextOne>> );
...
是的,将您的工作单元注入控制器/服务是个好主意。