我正在开发一个相当大的应用程序。该域有大约 20-30 种类型,实现为 ORM 类(例如 EF Code First 或 XPO,与问题无关)。我已经阅读了几篇关于存储库模式的通用实现并将其与工作单元模式相结合的文章和建议,产生了如下代码:
public interface IRepository<T> {
IQueryable<T> AsQueryable();
IEnumerable<T> GetAll(Expression<Func<T, bool>> filter);
T GetByID(int id);
T Create();
void Save(T);
void Delete(T);
}
public interface IMyUnitOfWork : IDisposable {
void CommitChanges();
void DropChanges();
IRepository<Product> Products { get; }
IRepository<Customer> Customers { get; }
}
这种模式适合真正的大型应用程序吗?每个示例在工作单元中大约有 2 个,最多 3 个存储库。就我理解的模式而言,在一天结束时,存储库引用的数量(在实现中延迟初始化)等于(或几乎等于)域实体类的数量,因此可以使用工作单元复杂的业务逻辑实现。例如,让我们像这样扩展上面的代码:
public interface IMyUnitOfWork : IDisposable {
...
IRepository<Customer> Customers { get; }
IRepository<Product> Products { get; }
IRepository<Orders> Orders { get; }
IRepository<ProductCategory> ProductCategories { get; }
IRepository<Tag> Tags { get; }
IRepository<CustomerStatistics> CustomerStatistics { get; }
IRepository<User> Users { get; }
IRepository<UserGroup> UserGroups { get; }
IRepository<Event> Events { get; }
...
}
在考虑代码异味之前,会引用多少个存储库?还是这种模式完全正常?我可能会将这个接口(interface)分成 2 或 3 个不同的接口(interface),所有接口(interface)都实现了 IUnitOfWork,但是这样使用起来就不那么舒服了。
更新
我检查了@qujck 推荐的一个基本不错的解决方案 here。我对动态存储库注册和“基于字典”方法的问题是我想享受对我的存储库的直接引用,因为某些存储库将具有特殊行为。因此,当我编写业务代码时,我希望能够像这样使用它,例如:
using (var uow = new MyUnitOfWork()) {
var allowedUsers = uow.Users.GetUsersInRolw("myRole");
// ... or
var clothes = uow.Products.GetInCategories("scarf", "hat", "trousers");
}
所以在这里我受益于我有一个强类型的 IRepository 和 IRepository 引用,因此我可以使用特殊方法(作为扩展方法实现或通过从基接口(interface)继承)。如果我使用动态存储库注册和检索方法,我想我会失去这个,或者至少必须一直做一些丑陋的 Actor 。
对于 DI 问题,我会尝试将存储库工厂注入(inject)到我真正的工作单元中,因此它可以延迟实例化存储库。
最佳答案
我倾向于解决这个问题的方法是将类型约束从存储库类移动到其中的方法。这意味着,而不是这样:
public interface IMyUnitOfWork : IDisposable
{
IRepository<Customer> Customers { get; }
IRepository<Product> Products { get; }
IRepository<Orders> Orders { get; }
...
}
我有这样的事情:public interface IMyUnitOfWork : IDisposable
{
Get<T>(/* some kind of filter expression in T */);
Add<T>(T);
Update<T>(T);
Delete<T>(/* some kind of filter expression in T */);
...
}
这样做的主要好处是您的工作单元只需要一个数据访问对象。缺点是您不再有像 Products.GetInCategories()
这样的特定于类型的方法。这可能是有问题的,所以我对此的解决方案通常是两件事之一。关注点分离
首先,你可以重新思考“数据访问”和“业务逻辑”之间的分离在哪里,这样你就有一个逻辑层类
ProductService
,它有一个方法 GetInCategory()
可以做到这一点:using (var uow = new MyUnitOfWork())
{
var productsInCategory = GetAll<Product>(p => ["scarf", "hat", "trousers"].Contains(u.Category));
}
您的数据访问和业务逻辑代码仍然是分开的。查询的封装
或者,您可以实现规范模式,因此您可以拥有一个命名空间
MyProject.Specifications
,其中有一个基类 Specification<T>
,它在内部某处具有过滤器表达式,以便您可以将其传递给工作单元对象,并且 UoW 可以使用过滤表达式。这让你可以得到派生的规范,你可以传递这些规范,现在你可以这样写:using (var uow = new MyUnitOfWork())
{
var searchCategories = new Specifications.Products.GetInCategories("scarf", "hat", "trousers");
var productsInCategories = GetAll<Product>(searchCategories);
}
如果您想要一个中央位置来保留常用逻辑,例如“按角色获取用户”或“按类别获取产品”,那么不要将其保存在您的存储库中(严格来说应该是纯数据访问),那么您可以拥有而是对象本身的那些扩展方法。例如, Product
可以有一个方法或扩展方法 InCategory(string)
,它返回一个 Specification<Product>
或者甚至只是一个过滤器,例如 Expression<Func<Product, bool>>
,允许您编写这样的查询:using (var uow = new MyUnitOfWork())
{
var productsInCategory = GetAll(Product.InCategories("scarf", "hat", "trousers");
}
(请注意,这仍然是一个通用方法,但类型推断会为您处理。)这将保留正在查询的对象(或该对象的扩展类)上的所有查询逻辑,这仍然使您的数据和逻辑代码按类和文件很好地分开,同时允许您在共享时共享它
IRepository<T>
扩展以前。例子
举一个更具体的例子,我在 EF 中使用这种模式。我不关心规范;我只是在逻辑层中有服务类,它们对每个逻辑操作(“添加新用户”、“获取产品类别”、“保存对产品的更改”等)使用单个工作单元。它的核心看起来像这样(为简洁起见省略了实现,因为它们非常简单):
public class EFUnitOfWork: IUnitOfWork
{
private DbContext _db;
public EntityFrameworkSourceAdapter(DbContext context) {...}
public void Add<T>(T item) where T : class, new() {...}
public void AddAll<T>(IEnumerable<T> items) where T : class, new() {...}
public T Get<T>(Expression<Func<T, bool>> filter) where T : class, new() {...}
public IQueryable<T> GetAll<T>(Expression<Func<T, bool>> filter = null) where T : class, new() {...}
public void Update<T>(T item) where T : class, new() {...}
public void Remove<T>(Expression<Func<T, bool>> filter) where T : class, new() {...}
public void Commit() {...}
public void Dispose() {...}
}
大多数这些方法使用 _db.Set<T>()
来获取相关的 DbSet
,然后使用提供的 Expression<Func<T, bool>>
使用 LINQ 查询它。关于c# - 在大型项目中使用通用存储库/工作单元模式,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/24906548/