在大型项目中使用的工作模式的通用库/股 [英] Using the Generic repository/Unit of work pattern in large projects

查看:145
本文介绍了在大型项目中使用的工作模式的通用库/股的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我工作的一个相当大的应用程序。域中有大约20-30类型,ORM类实现(例如EF代码优先或XPO,对于这个问题并不重要)。我读过有关通用实施资源库模式,并与工作模式的单位相结合的多篇文章和建议,产生的代码是这样的:

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



是这种模式适用于真正的大的应用程序?每个实例有大约2,在工作单位最多3个仓库。据我理解的图案,在一天结束时储存库的引用的数目(在执行懒惰初始化)等于(或接近等于)域实体类的数量,这样就可以利用工作的单元复杂的业务逻辑的实现。因此,例如,让我们扩展这样上面的代码:

Is this pattern suitable for really large applications? Every example has about 2, maximum 3 repositories in the unit of work. 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; }

  ...   
}

有多少出租车库被引用,直到一个想着代码味道?或者是完全正常的,这种模式?我这个界面大概分为所有执行IUnitOfWork,但随后的用法是不舒服2或3个不同的接口。

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.

更新

我查了基本很好的解决方案这里建议@qujck。我与动态库登记和问题基于字典的做法,我想享受直接引用到我的仓库,因为有些信息库都会有特殊的行为。所以,当我写我的业务代码,我想可以这样使用它,例如:使用(VAR UOW

I've checked a basically nice solution here recommended by @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");
}



所以在这里,我受益,我有一个强类型IRepository和IRepository参考,所以我可以使用特殊的方法(如扩展方法或从基接口继承实现)。如果我使用一个动态库登记和检索方法,我想我会松动这一点,或者至少必须做一些丑陋的铸件所有的时间。

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). 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.

有关的DI的事,我会尝试一个仓库工厂注入到我的实际工作单位,所以它可以懒洋洋地实例化的存储库。

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.

推荐答案

我倾向于解决这个问题的方法是将类型约束从仓储类移动到它里面的方法。这意味着,而不是这样的:

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



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

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. This can be problematic, so my solution to this is usually one of two things.

第一次分离,可以重新思考,其中数据访问和商业逻辑的分离谎言,让你有一个逻辑层类 ProductService ,有一个方法 GetInCategory()可以做到这一点

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:

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.

另外的封装,可以实现一个规范的模式,所以你可以有一个命名空间 MyProject的.Specifications ,其中有一个基类规范< T> ,有一个筛选表达式内部的某个地方,这样就可以将它传递给单元工作对象和UOW可以使用过滤器表达式。这让你有所得规格,您可以通过身边,现在你可以这样写:

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

如果你想有一个集中的地方,以保持常用的逻辑,如获取用户按角色或获取产品类别,然后,而不是保持它在你的仓库(这应该是纯粹的数据访问,严格地说),那么你可以对对象本身,而不是那些扩展方法。例如,产品可以有一个方法或者返回 InCategory(串) >规格及LT;产品> 甚至只是一个过滤器,例如表达式来; Func键<产品,布尔>> ,允许你写这样的查询这样的:

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:

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

(注意,这仍然是一个通用的方法,但类型推断会照顾它。)

这使所有查询在对象上的逻辑被查询(或在扩展类的对象),仍然保持您的数据和逻辑代码很好地按类和文件分开,同时允许你为你一直分享你的 IRepository< T> 扩展以前

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.

要给出一个更具体的例子,我使用的是这种模式与EF。我没有理会规范;我只是在使用单个工作单位的每个逻辑操作逻辑层服务类(添加新用户,得到的产品类别,更改保存到产品等)。它的核心看起来像这样(为简洁起见省略实现,并且,因为他们非常简单):

To give a more specific example, I'm using this pattern with 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() {...}
}

大多数这些方法使用 _db.Set< T>()来得到相关 DbSet ,然后只需使用所提供的表达式来使用LINQ查询它; Func键< T,BOOL>>

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>>.

这篇关于在大型项目中使用的工作模式的通用库/股的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆