我正在处理一个非常大的应用程序。该域有大约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; }
...
}
在考虑代码味道之前,会引用多少个存储库?或者这种模式完全正常吗?我可以将这个接口分成2个或3个不同的接口,所有接口都实现了IUnitOfWork,但是使用起来会不那么舒服。
更新
我已经检查了@qujck推荐的基本上很好的解决方案here。我的动态存储库注册问题和基于&#34;字典的#34;方法是我想享受对我的存储库的直接引用,因为一些存储库将具有特殊行为。因此,当我编写业务代码时,我希望能够像这样使用它,例如:
using (var uow = new MyUnitOfWork()) {
var allowedUsers = uow.Users.GetUsersInRolw("myRole");
// ... or
var clothes = uow.Products.GetInCategories("scarf", "hat", "trousers");
}
所以我在这里得益于我有一个强类型的IRepository和IRepository引用,因此我可以使用特殊方法(作为扩展方法实现或通过从基接口继承)。如果我使用动态存储库注册和检索方法,我想我会松开这个,或者至少不得不一直做一些丑陋的演员。
关于DI的问题,我会尝试将一个存储库工厂注入我真正的工作单元,因此它可以懒惰地实例化存储库。
答案 0 :(得分:3)
根据我上面的评论和答案here。
略微修改工作抽象单元
public interface IMyUnitOfWork
{
void CommitChanges();
void DropChanges();
IRepository<T> Repository<T>();
}
您可以使用扩展方法公开命名存储库和特定存储库方法
public static class MyRepositories
{
public static IRepository<User> Users(this IMyUnitOfWork uow)
{
return uow.Repository<User>();
}
public static IRepository<Product> Products(this IMyUnitOfWork uow)
{
return uow.Repository<Product>();
}
public static IEnumerable<User> GetUsersInRole(
this IRepository<User> users, string role)
{
return users.AsQueryable().Where(x => true).ToList();
}
public static IEnumerable<Product> GetInCategories(
this IRepository<Product> 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");
}
答案 1 :(得分:2)
我倾向于采用的方法是将类型约束从存储库类移动到其中的方法。这意味着,而不是:
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查询它。