简体   繁体   English

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

[英]Using the Generic repository/Unit of work pattern in large projects

I'm working on a quite large application. 我正在开发一个非常大的应用程序。 The domain has about 20-30 types, implemented as ORM classes (for example EF Code First or XPO, doesn't matter for the question). 该域有大约20-30种类型,实现为ORM类(例如EF Code First或XPO,对于该问题无关紧要)。 I've read several articles and suggestions about a generic implementation of the repository pattern and combining it with the unit of work pattern, resulting a code something like this: 我已经阅读了几篇关于存储库模式的通用实现的文章和建议,并将它与工作单元模式相结合,产生了类似这样的代码:

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; }
}

Is this pattern suitable for really large applications? 这种模式适合大型应用吗? Every example has about 2, maximum 3 repositories in the unit of work. 每个示例在工作单元中都有大约2个,最多3个存储库。 As far as I understood the pattern, at the end of the day the number of repository references (lazy initialized in the implementation) equal (or nearly equal) to the number of domain entity classes, so that one can use the unit of work for complex business logic implementation. 据我了解的模式,在一天结束时,存储库引用的数量(在实现中初始化的延迟)与域实体类的数量相等(或几乎相等),因此可以使用工作单元复杂的业务逻辑实现。 So for example let's extend the above code like this: 例如,让我们像这样扩展上面的代码:

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; }

  ...   
}

How many repositories cab be referenced until one thinks about code smell? 在考虑代码气味之前,会引用多少个存储库? Or is it totally normal for this pattern? 或者这种模式完全正常吗? I could probably separate this interface into 2 or 3 different interfaces all implementing IUnitOfWork, but then the usage would be less comfortable. 我可以将这个接口分成2个或3个不同的接口,所有接口都实现了IUnitOfWork,但是使用起来会不那么舒服。

UPDATE UPDATE

I've checked a basically nice solution here recommended by @qujck. 我查了基本很好的解决方案在这里由@qujck建议。 My problem with the dynamic repository registration and "dictionary based" approach is that I would like to enjoy the direct references to my repositories, because some of the repositories will have special behaviour. 我的动态存储库注册和“基于字典”方法的问题是我想享受对我的存储库的直接引用,因为一些存储库将具有特殊行为。 So when I write my business code I would like to be able to use it like this for example: 因此,当我编写业务代码时,我希望能够像这样使用它,例如:

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

So here I'm benefiting that I have a strongly typed IRepository and IRepository reference, hence I can use the special methods (implemented as extension methods or by inheriting from the base interface). 所以我在这里受益于我有一个强类型的IRepository和IRepository引用,因此我可以使用特殊方法(实现为扩展方法或从基接口继承)。 If I use a dynamic repository registration and retrieval method, I think I'm gonna loose this, or at least have to do some ugly castings all the time. 如果我使用动态存储库注册和检索方法,我想我会松开这个,或者至少不得不一直做一些丑陋的演员。

For the matter of DI, I would try to inject a repository factory to my real unit of work, so it can lazily instantiate the repositories. 对于DI的问题,我会尝试将存储库工厂注入我真正的工作单元,因此它可以懒惰地实例化存储库。

Building on my comments above and on top of the answer here . 建立在上边以及答案的顶我的意见在这里

With a slightly modified unit of work abstraction 稍加修改的工作单元抽象

public interface IMyUnitOfWork
{
    void CommitChanges();
    void DropChanges();

    IRepository<T> Repository<T>();
}

You can expose named repositories and specific repository methods with extension methods 您可以使用扩展方法公开命名存储库和特定存储库方法

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();
    }
}

That provide access the data as required 这提供了根据需要访问数据

using(var uow = new MyUnitOfWork())
{
    var allowedUsers = uow.Users().GetUsersInRole("myRole");

    var result = uow.Products().GetInCategories("scarf", "hat", "trousers");
}

The way I tend to approach this is to move the type constraint from the repository class to the methods inside it. 我倾向于采用的方法是将类型约束从存储库类移动到其中的方法。 That means that instead of this: 这意味着,而不是这个:

public interface IMyUnitOfWork : IDisposable
{
    IRepository<Customer> Customers { get; }
    IRepository<Product> Products { get; }
    IRepository<Orders> Orders { get; }
    ...
}

I have something like this: 我有这样的事情:

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 */);
    ...
}

The main benefit of this is that you only need one data access object on your unit of work. 这样做的主要好处是您的工作单元上只需要一个数据访问对象。 The downside is that you don't have type-specific methods like Products.GetInCategories() any more. 缺点是你没有像Products.GetInCategories()这样的特定于类型的方法。 This can be problematic, so my solution to this is usually one of two things. 这可能有问题,所以我的解决方案通常是两件事之一。

Separation of concerns 关注点分离

First, you can rethink where the separation between "data access" and "business logic" lies, so that you have a logic-layer class ProductService that has a method GetInCategory() that can do this: 首先,您可以重新考虑“数据访问”和“业务逻辑”之间的分离,以便您拥有一个逻辑层类ProductService ,它具有可以执行此操作的方法GetInCategory()

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

Your data access and business logic code is still separate. 您的数据访问和业务逻辑代码仍然是独立的。

Encapsulation of queries 封装查询

Alternatively, you can implement a specification pattern, so you can have a namespace MyProject.Specifications in which there is a base class Specification<T> that has a filter expression somewhere internally, so that you can pass it to the unit of work object and that UoW can use the filter expression. 或者,您可以实现规范模式,因此您可以拥有一个名称空间MyProject.Specifications ,其中有一个基类Specification<T>在内部某处有一个过滤器表达式,因此您可以将它传递给工作对象单元UoW可以使用过滤器表达式。 This lets you have derived specifications, which you can pass around, and now you can write this: 这使您可以获得可以传递的派生规范,现在您可以编写:

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

If you want a central place to keep commonly-used logic like "get user by role" or "get products in category", then instead of keeping it in your repository (which should be pure data access, strictly speaking) then you could have those extension methods on the objects themselves instead. 如果你想要一个集中的地方来保留常用的逻辑,比如“按角色获取用户”或“获取类别中的产品”,那么你可以拥有而不是将它保存在你的存储库中(这应该是纯粹的数据访问)而不是对象本身的那些扩展方法。 For example, Product could have a method or an extension method InCategory(string) that returns a Specification<Product> or even just a filter such as Expression<Func<Product, bool>> , allowing you to write the query like this: 例如, Product可以有一个方法或扩展方法InCategory(string)返回一个Specification<Product>甚至只是一个过滤器,如Expression<Func<Product, bool>> ,允许你像这样编写查询:

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

(Note that this is still a generic method, but type inference will take care of it for you.) (请注意,这仍然是一种通用方法,但类型推断将为您处理。)

This keeps all the query logic on the object being queried (or on an extensions class for that object), which still keeps your data and logic code nicely separated by class and by file, whilst allowing you to share it as you have been sharing your IRepository<T> extensions previously. 这使得所查询对象上的所有查询逻辑(或该对象的扩展类)保持不变,这仍然保持您的数据和逻辑代码按类和文件很好地分开,同时允许您在共享时共享它IRepository<T>扩展。

Example

To give a more specific example, I'm using this pattern with EF. 为了给出一个更具体的例子,我在EF中使用这种模式。 I didn't bother with specifications; 我没有打扰规格; I just have service classes in the logic layer that use a single unit of work for each logical operation ("add a new user", "get a category of products", "save changes to a product" etc). 我只是在逻辑层中有服务类,它为每个逻辑操作使用单个工作单元(“添加新用户”,“获取产品类别”,“保存对产品的更改”等)。 The core of it looks like this (implementations omitted for brevity and because they're pretty trivial): 它的核心看起来像这样(为简洁而省略了实现,因为它们非常简单):

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() {...}
}

Most of those methods use _db.Set<T>() to get the relevant DbSet , and then just query it with LINQ using the provided Expression<Func<T, bool>> . 大多数这些方法使用_db.Set<T>()来获取相关的DbSet ,然后使用提供的Expression<Func<T, bool>>使用LINQ查询它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 使用Moq进行单元测试的工作单元和通用存储库模式框架 - Unit Testing Unit of Work and Generic Repository Pattern framework using Moq 单元测试使用Moq的通用工作单元和存储库模式框架 - Unit Testing Generic Unit of Work and Repository Pattern framework using Moq 洋葱架构,工作单元和通用存储库模式 - Onion Architecture, Unit of Work and a generic Repository pattern 使用单元测试、工作单元和通用存储库模式框架从 MOQ 中获取单个对象 - Get single object from MOQ using Unit Testing, Unit of Work and Generic Repository Pattern framework MVC通用存储库/工作单元模式 - 扩展存储库 - MVC Generic Repository/Unit of Work pattern - Extending a repository 将多个DbContext与通用存储库和工作单元一起使用 - Using multiple DbContexts with a generic repository and unit of work 通用存储库中的工作单元 - Unit Of Work in Generic Repository CRUD的通用类 - 使用存储库和工作单元模式的C#中的依赖注入 - Generic Class for CRUD - Dependency Injection in C# using Repository and Unit Of Work Pattern 通用工作单元和存储库模式不会加载相关实体 - Generic Unit of Work and Repository pattern does not load related entities 具有EF示例代码的MVC4和工作单元,通用存储库模式? - MVC4 with EF sample code with Unit of work, generic repository pattern?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM