在大型项目中使用通用存储库/工作单元模式

我正在开发一个非常大的应用程序。 该域有大约20-30种类型,实现为ORM类(例如EF Code First或XPO,对于该问题无关紧要)。 我已经阅读了几篇关于存储库模式的通用实现的文章和建议,并将它与工作单元模式相结合,产生了类似这样的代码:

public interface IRepository { IQueryable AsQueryable(); IEnumerable GetAll(Expression<Func> filter); T GetByID(int id); T Create(); void Save(T); void Delete(T); } public interface IMyUnitOfWork : IDisposable { void CommitChanges(); void DropChanges(); IRepository Products { get; } IRepository Customers { get; } } 

这种模式适合大型应用吗? 每个示例在工作单元中有大约2个,最多3个存储库。 据我了解的模式,在一天结束时,存储库引用的数量(在实现中初始化的延迟)与域实体类的数量相等(或几乎相等),因此可以使用工作单元复杂的业务逻辑实现。 例如,让我们像这样扩展上面的代码:

 public interface IMyUnitOfWork : IDisposable { ... IRepository Customers { get; } IRepository Products { get; } IRepository Orders { get; } IRepository ProductCategories { get; } IRepository Tags { get; } IRepository CustomerStatistics { get; } IRepository Users { get; } IRepository UserGroups { get; } IRepository Events { get; } ... } 

在考虑代码气味之前,会引用多少个存储库? 或者这种模式完全正常? 我可以将这个接口分成2个或3个不同的接口,所有接口都实现了IUnitOfWork,但随后使用起来就不那么舒服了。

UPDATE

我已经检查了@qujck推荐的基本上很好的解决方案。 我的动态存储库注册和“基于字典”方法的问题是我想享受对我的存储库的直接引用,因为一些存储库将具有特殊行为。 因此,当我编写业务代码时,我希望能够像这样使用它,例如:

 using (var uow = new MyUnitOfWork()) { var allowedUsers = uow.Users.GetUsersInRolw("myRole"); // ... or var clothes = uow.Products.GetInCategories("scarf", "hat", "trousers"); } 

所以我在这里受益于我有一个强类型的IRepository和IRepository引用,因此我可以使用特殊方法(实现为扩展方法或从基接口inheritance)。 如果我使用动态存储库注册和检索方法,我想我会放松它,或者至少不得不一直做一些丑陋的演员。

对于DI的问题,我会尝试将一个存储库工厂注入我真正的工作单元,因此它可以懒惰地实例化存储库。

我倾向于采用的方法是将类型约束从存储库类移动到其中的方法。 这意味着,而不是:

 public interface IMyUnitOfWork : IDisposable { IRepository Customers { get; } IRepository Products { get; } IRepository Orders { get; } ... } 

我有这样的事情:

 public interface IMyUnitOfWork : IDisposable { Get(/* some kind of filter expression in T */); Add(T); Update(T); Delete(/* some kind of filter expression in T */); ... } 

这样做的主要好处是您的工作单元上只需要一个数据访问对象。 缺点是你没有像Products.GetInCategories()这样的特定于类型的方法。 这可能有问题,所以我的解决方案通常是两件事之一。

关注点分离

首先,您可以重新考虑“数据访问”和“业务逻辑”之间的分离,以便您拥有一个逻辑层类ProductService ,它具有可以执行此操作的方法GetInCategory()

 using (var uow = new MyUnitOfWork()) { var productsInCategory = GetAll(p => ["scarf", "hat", "trousers"].Contains(u.Category)); } 

您的数据访问和业务逻辑代码仍然是独立的。

封装查询

或者,您可以实现规范模式,因此您可以拥有一个名称空间MyProject.Specifications ,其中有一个基类Specification在内部某处有一个filter表达式,因此您可以将它传递给工作对象单元UoW可以使用filter表达式。 这使您可以获得可以传递的派生规范,现在您可以这样写:

 using (var uow = new MyUnitOfWork()) { var searchCategories = new Specifications.Products.GetInCategories("scarf", "hat", "trousers"); var productsInCategories = GetAll(searchCategories); } 

如果你想要一个中心位置来保存常用的逻辑,比如“按角色获取用户”或“获取类别中的产品”,那么你可以拥有而不是将它保存在你的存储库中(这应该是纯粹的数据访问)那些对象本身的扩展方法。 例如, Product可以有一个方法或扩展方法InCategory(string)返回一个Specification甚至只是一个filter,如Expression> ,允许你像这样编写查询:

 using (var uow = new MyUnitOfWork()) { var productsInCategory = GetAll(Product.InCategories("scarf", "hat", "trousers"); } 

(请注意,这仍然是一种通用方法,但类型推断将为您处理。)

这使得所查询对象的所有查询逻辑(或该对象的扩展类)都保持不变,这仍然可以保持您的数据和逻辑代码按类和文件很好地分开,同时允许您在共享时共享它IRepository扩展。

为了给出一个更具体的例子,我在EF中使用这种模式。 我没有打扰规格; 我只是在逻辑层中有服务类,它为每个逻辑操作使用单个工作单元(“添加新用户”,“获取产品类别”,“保存对产品的更改”等)。 它的核心看起来像这样(为简洁省略了实现,因为它们非常简单):

 public class EFUnitOfWork: IUnitOfWork { private DbContext _db; public EntityFrameworkSourceAdapter(DbContext context) {...} public void Add(T item) where T : class, new() {...} public void AddAll(IEnumerable items) where T : class, new() {...} public T Get(Expression> filter) where T : class, new() {...} public IQueryable GetAll(Expression> filter = null) where T : class, new() {...} public void Update(T item) where T : class, new() {...} public void Remove(Expression> filter) where T : class, new() {...} public void Commit() {...} public void Dispose() {...} } 

这些方法中的大多数使用_db.Set()来获取相关的DbSet ,然后使用提供的Expression>使用LINQ查询它。

建立在我上面的评论和答案之上。

稍加修改的工作单元抽象

 public interface IMyUnitOfWork { void CommitChanges(); void DropChanges(); IRepository Repository(); } 

您可以使用扩展方法公开命名存储库和特定存储库方法

 public static class MyRepositories { public static IRepository Users(this IMyUnitOfWork uow) { return uow.Repository(); } public static IRepository Products(this IMyUnitOfWork uow) { return uow.Repository(); } public static IEnumerable GetUsersInRole( this IRepository users, string role) { return users.AsQueryable().Where(x => true).ToList(); } public static IEnumerable GetInCategories( this IRepository products, params string[] categories) { return products.AsQueryable().Where(x => true).ToList(); } } 

这提供了根据需要访问数据

 using(var uow = new MyUnitOfWork()) { var allowedUsers = uow.Users().GetUsersInRole("myRole"); var result = uow.Products().GetInCategories("scarf", "hat", "trousers"); }