由于对此主题的大量阅读,我将要>爆炸<:)...
我的头很痛,我需要一些诚实的意见...
我有一个类似的问题/评论注意到了,但是我相信我的方法有些不同,所以我想问一下。

我们正在着手一个非常大的项目,该项目将结合使用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;
    }
}


评论

我不喜欢您在工作单元和存储库之间的关系。每次需要添加存储库时,都需要触摸UnitOfWork类,这对我来说是一种代码味道。

@Markust我同意您的看法-自从我5个月前发布以来,我一直在修改我的“设计”,而我所做的更改之一就是将UoW与存储库分离。
@ zam6ak您如何将回购与UOW分离?我一直在尝试与此相似的模式,发现尽管我需要编辑UOW以添加新的存储库,但它仍然很干净。

@SecretDeveloper创建一个公共接口,然后具有ORM特定的实现。当然,您获得的摘要越多,丢失的ORM特定功能就越多。

#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