在大型项目中使用通用存储库/工作单元模式
我正在开发一个非常大的应用程序。 该域有大约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"); }