我的头很痛,我需要一些诚实的意见...
我有一个类似的问题/评论注意到了,但是我相信我的方法有些不同,所以我想问一下。
我们正在着手一个非常大的项目,该项目将结合使用ASP.NET MVC 4和Web API,并希望使用Entity Framework首先编写代码。
在发布代码进行审核之前,请考虑以下内容
我的项目将很大(可能有100多个域实体)
需要SOLID方法到架构
,到现在为止,我了解到Entity Framework
DbContext
正在(完全或某种程度上)实现工作单元和存储库模式,因此我可以跳过所有这些;)直接进入Controller构造函数(可测试性,关注点分离等)我也知道实现UoW和存储库模式的方法不止一种。
我不担心或想要来构建抽象结构,使我可以“交换” ORM(例如为NHiberante等交换实体框架。
存储库通过构造函数(由
DbContext
提供)需要UnitOfWork
。UnitOfWork
负责管理对所有存储库的访问,并确保上下文在它们之间共享。UnitOfWork
是一次性的,存储库不是... 为了“隐藏”
DbContext
,请使用UnitOfWork
构造IDbContextFactory
。 问题
这似乎对我有用,我看到的好处是每个Controller都只需要注入UoW,这很好。
有些控制器除域服务外还需要2-3个存储库,因此这使事情变得很不错...我认为...
随着时间的流逝,UoW将随存储库一起增长(可能有65个以上的聚合根,每个根都有一个存储库)。关于如何更好地管理这一点有什么想法?我是否应该以某种方式注入存储库,而不是在
UnitOfWork
中对它们进行od new()插入?我很想能够创建一个IoC模块(Autofac是我的毒药)以某种方式连接所有存储库(以某种方式)使用
IDbContextFactory
会产生过大杀伤力,还是应该将DbContext
注入UnitOfWork
的构造函数中?现在,我的Web应用程序不直接依赖于Entity Framework,它仅依赖于n DAL(后者又依赖于EF)。另一方面,DbContextFactory new()增加了MyAppDbContext
,并且未被IoC处理。有人注意到任何其他“代码异味”吗?
一些问题在代码注释中,使它们更加相关。 。
好,这是具有2个存储库和示例用法的代码(为简洁起见,省略了所有名称空间)。 br />
IRepository,Repository和2个具有额外接口(车辆和库存)的特定Repository
/// <summary>
/// Creates instance of specific DbContext
/// </summary>
public interface IDbContextFactory //: IDisposable //NOTE: Since UnitOfWork is disposable I am not sure if context factory has to be also...
{
DbContext GetDbContext();
}
public class DbContextFactory : IDbContextFactory
{
private readonly DbContext _context;
public DbContextFactory()
{
// the context is new()ed up instead of being injected to avoid direct dependency on EF
// not sure if this is good approach...but it removes direct dependency on EF from web tier
_context = new MyAppDbContext();
}
public DbContext GetDbContext()
{
return _context;
}
// see comment in IDbContextFactory inteface...
//public void Dispose()
//{
// if (_context != null)
// {
// _context.Dispose();
// GC.SuppressFinalize(this);
// }
//}
}
IUnitOfWork,UnitOfWork
>
public interface IRepository<T> where T : class
{
/// <summary>
/// Get the total objects count.
/// </summary>
int Count { get; }
/// <summary>
/// Gets all objects from database
/// </summary>
IQueryable<T> All();
/// <summary>
/// Gets object by primary key.
/// </summary>
/// <param name="id"> primary key </param>
/// <returns> </returns>
T GetById(object id);
/// <summary>
/// Gets objects via optional filter, sort order, and includes
/// </summary>
/// <param name="filter"> </param>
/// <param name="orderBy"> </param>
/// <param name="includeProperties"> </param>
/// <returns> </returns>
IQueryable<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = "");
/// <summary>
/// Gets objects from database by filter.
/// </summary>
/// <param name="predicate"> Specified a filter </param>
IQueryable<T> Filter(Expression<Func<T, bool>> predicate);
/// <summary>
/// Gets objects from database with filting and paging.
/// </summary>
/// <param name="filter"> Specified a filter </param>
/// <param name="total"> Returns the total records count of the filter. </param>
/// <param name="index"> Specified the page index. </param>
/// <param name="size"> Specified the page size </param>
IQueryable<T> Filter(Expression<Func<T, bool>> filter, out int total, int index = 0, int size = 50);
/// <summary>
/// Gets the object(s) is exists in database by specified filter.
/// </summary>
/// <param name="predicate"> Specified the filter expression </param>
bool Contains(Expression<Func<T, bool>> predicate);
/// <summary>
/// Find object by keys.
/// </summary>
/// <param name="keys"> Specified the search keys. </param>
T Find(params object[] keys);
/// <summary>
/// Find object by specified expression.
/// </summary>
/// <param name="predicate"> </param>
T Find(Expression<Func<T, bool>> predicate);
/// <summary>
/// Create a new object to database.
/// </summary>
/// <param name="entity"> Specified a new object to create. </param>
T Create(T entity);
/// <summary>
/// Deletes the object by primary key
/// </summary>
/// <param name="id"> </param>
void Delete(object id);
/// <summary>
/// Delete the object from database.
/// </summary>
/// <param name="entity"> Specified a existing object to delete. </param>
void Delete(T entity);
/// <summary>
/// Delete objects from database by specified filter expression.
/// </summary>
/// <param name="predicate"> </param>
void Delete(Expression<Func<T, bool>> predicate);
/// <summary>
/// Update object changes and save to database.
/// </summary>
/// <param name="entity"> Specified the object to save. </param>
void Update(T entity);
}
public class Repository<T> : IRepository<T> where T : class
{
protected readonly DbContext _dbContext;
protected readonly DbSet<T> _dbSet;
public Repository(DbContext dbContext)
{
_dbContext = dbContext;
_dbSet = _dbContext.Set<T>();
}
public virtual int Count
{
get { return _dbSet.Count(); }
}
public virtual IQueryable<T> All()
{
return _dbSet.AsQueryable();
}
public virtual T GetById(object id)
{
return _dbSet.Find(id);
}
public virtual IQueryable<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = "")
{
IQueryable<T> query = _dbSet;
if (filter != null)
{
query = query.Where(filter);
}
if (!String.IsNullOrWhiteSpace(includeProperties))
{
foreach (var includeProperty in includeProperties.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
}
if (orderBy != null)
{
return orderBy(query).AsQueryable();
}
else
{
return query.AsQueryable();
}
}
public virtual IQueryable<T> Filter(Expression<Func<T, bool>> predicate)
{
return _dbSet.Where(predicate).AsQueryable();
}
public virtual IQueryable<T> Filter(Expression<Func<T, bool>> filter, out int total, int index = 0, int size = 50)
{
int skipCount = index*size;
var resetSet = filter != null ? _dbSet.Where(filter).AsQueryable() : _dbSet.AsQueryable();
resetSet = skipCount == 0 ? resetSet.Take(size) : resetSet.Skip(skipCount).Take(size);
total = resetSet.Count();
return resetSet.AsQueryable();
}
public bool Contains(Expression<Func<T, bool>> predicate)
{
return _dbSet.Count(predicate) > 0;
}
public virtual T Find(params object[] keys)
{
return _dbSet.Find(keys);
}
public virtual T Find(Expression<Func<T, bool>> predicate)
{
return _dbSet.FirstOrDefault(predicate);
}
public virtual T Create(T entity)
{
var newEntry = _dbSet.Add(entity);
return newEntry;
}
public virtual void Delete(object id)
{
var entityToDelete = _dbSet.Find(id);
Delete(entityToDelete);
}
public virtual void Delete(T entity)
{
if (_dbContext.Entry(entity).State == EntityState.Detached)
{
_dbSet.Attach(entity);
}
_dbSet.Remove(entity);
}
public virtual void Delete(Expression<Func<T, bool>> predicate)
{
var entitiesToDelete = Filter(predicate);
foreach (var entity in entitiesToDelete)
{
if (_dbContext.Entry(entity).State == EntityState.Detached)
{
_dbSet.Attach(entity);
}
_dbSet.Remove(entity);
}
}
public virtual void Update(T entity)
{
var entry = _dbContext.Entry(entity);
_dbSet.Attach(entity);
entry.State = EntityState.Modified;
}
}
public class VehicleRepository : Repository<Vehicle>, IVehicleRepository
{
public VehicleRepository(DbContext dbContext) : base(dbContext)
{
}
}
public interface IVehicleRepository : IRepository<Vehicle>
{
//RFU
}
public interface IInventoryRepository : IRepository<InventoryItem>
{
IList<InventoryItem> GetByVehicleId(string vehicleId); // NOTE: InventoryItem.VehicleId != InventoryItem.Id
}
public class InventoryItemRepository : Repository<InventoryItem>, IInventoryItemRepository
{
public InventoryItemRepository(DbContext dbContext) : base(dbContext)
{
}
public IList<InventoryItem> GetByVehicleId(string vehicleId)
{
return Filter(vii => vii.Vehicle.Id == vehicleId).ToList();
}
}
ASP.NET MVC4 + Web API中的示例用法
Global.asax.cs(应用程序启动)
br /> InventoryController
public interface IUnitOfWork : IDisposable
{
InventoryItemRepository InventoryItemRepository { get; }
VehicleRepository VehicleRepository { get; }
void Save();
}
public class UnitOfWork : IUnitOfWork
{
private readonly DbContext _dbContext;
private bool _disposed;
private InventoryItemRepository _inventoryItemRepository;
private VehicleRepository _vehicleRepository;
/// <summary>
/// NOTE: repository getters instantiate repositories as needed (lazily)...
/// i wish I knew of IoC "way" of wiring up repository getters...
/// </summary>
/// <param name="dbContextFactory"></param>
public UnitOfWork(IDbContextFactory dbContextFactory)
{
_dbContext = dbContextFactory.GetDbContext();
}
public void Save()
{
if (_dbContext.GetValidationErrors().Any())
{
// TODO: move validation errors into domain level exception and then throw it instead of EF related one
}
_dbContext.SaveChanges();
}
public InventoryItemRepository InventoryItemRepository
{
get { return _inventoryItemRepository ?? (_inventoryItemRepository = new InventoryItemRepository(_dbContext)); }
}
public VehicleRepository VehicleRepository
{
get { return _vehicleRepository ?? (_vehicleRepository = new VehicleRepository(_dbContext)); }
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_dbContext.Dispose();
}
}
_disposed = true;
}
}
#1 楼
如果您可能会增长到100多个域对象,那么我假设您将有很多逻辑。我不会直接通过控制器访问存储库,而是添加一个控制器通过UnitOfWork检索到的服务层。因为您将拥有许多锅炉代码(例如,获取,更新,保存)。
服务层将使您避免违反DRY原理,因为您不会重复典型的存储库代码。可以使用Facade模式对需要多个存储库的常见任务进行分组
http://en.wikipedia.org/wiki/Facade_pattern
您的业务逻辑将在服务层中,从而可以在其他地方重用,加上您的控制器将与您的业务逻辑松散耦合
我构建的示例:https://gist.github.com/3025099
#2 楼
我同意@Mike的通过服务避免控制器膨胀的推理。包装DbContext是一个泄漏的抽象。不管怎样,您最终都将在服务/控制器层中对EF产生某种依赖性。
每个MSDN:
DbContext类
代表工作单元和存储库模式的组合
,使您可以查询数据库并将更改分组在一起
然后将其作为一个单元写回到商店。
如果使用任何DI框架,都可以非常轻松地管理DbContext和服务的生存期。 :
厄运中的建筑:存储库抽象层的弊端
评论
\ $ \ begingroup \ $
好链接。我也关注Ayende的博客,非常了解他在UoW和Repository中的“位置” :)没错,EF的DbContext已经是一个抽象,但是我仍然认为您不应该在控制器中直接使用它(而是通过服务使用你提到的)。
\ $ \ endgroup \ $
– Zam6ak
2012年10月15日13:57
#3 楼
看起来不错,除了用于分页的筛选器会通过error。仅在LINQ to Entities中的排序输入中支持方法'Skip'。必须在方法“跳过”之前调用方法“ OrderBy”。
评论
\ $ \ begingroup \ $
这里也是..任何想法如何解决?
\ $ \ endgroup \ $
–冥王
13年8月16日在22:38
#4 楼
这可能只是一个错字,但我可能会建议让IUnitOfWork
公开接口而不是具体的实现,例如IVehicleRepository
代替了VehicleRepository
。我非常喜欢在这方面使用
IDBContextFactory
,这并不过分,但可以很好地将DBContext
抽象化。但是,在执行此操作后,您是否会把工厂传递给Repository<T>
构造函数而不是DBContext
本身呢?有兴趣看到其他人公开UnitOfWork
上所有存储库的评论。评论
\ $ \ begingroup \ $
您不想将IDBContextFactory传递给每个存储库。实体框架负责跟踪更改,并通过将实体附加到其DBContext来完成。如果您不与每个存储库共享IDBContextFactory创建的DBContext,则将失去此功能,并且在更新链接的实体(例如联系人和联系人的-> EmailAddress)时会遇到问题。想要为每个存储库创建一个新的DBContext实例是很自然的选择,但是我犯了这个错误,并且相信我您不想这样做。
\ $ \ endgroup \ $
–user17129
2012年10月5日在22:02
\ $ \ begingroup \ $
@咖喱。是的,我同意dbcontexts应该共享。我见过您提到的相同问题。但是如果不将contextfactory用于其他地方,则使用它似乎是过大的。否则,最好在设置注入规则时注入dbcontext,尤其是在使用Unity或Ninject等框架时。
\ $ \ endgroup \ $
– dreza
2012年10月6日4:58
评论
我不喜欢您在工作单元和存储库之间的关系。每次需要添加存储库时,都需要触摸UnitOfWork类,这对我来说是一种代码味道。@Markust我同意您的看法-自从我5个月前发布以来,我一直在修改我的“设计”,而我所做的更改之一就是将UoW与存储库分离。
@ zam6ak您如何将回购与UOW分离?我一直在尝试与此相似的模式,发现尽管我需要编辑UOW以添加新的存储库,但它仍然很干净。
@SecretDeveloper创建一个公共接口,然后具有ORM特定的实现。当然,您获得的摘要越多,丢失的ORM特定功能就越多。