如何普遍创建从通用类型继承的存储库?

时间:2018-10-21 10:46:12

标签: asp.net-mvc entity-framework repository-pattern factory

我目前正在尝试在我的DbContext之上实现存储库模式。问题是,我最终遇到了必须将几个存储库注入到UnitOfWork构造函数中的情况,如下所示:

public class UnitOfWork
{
    private DbContext _context;
    ICustomerRepository Customers { get; private set; }
    IEmployeeRepository Employees { get; private set; } 
    public UnitOfWork(DbContext context, ICustomerRepository cust, IEmployeeRepository emp)
    {
        _context = context;
        Customers = cust;
        Employees = emp;
    }    
}

但是,由于所有这些对象都必须共享相同的DbContext,因此我看不到将它们作为选项注入。

这就是为什么我在考虑创建RepositoryFactory类的原因,但是由于所有存储库都从通用接口继承,所以我发现无法创建单个Create()方法,因为毕竟它们没有可以用作有效返回类型的实际共同祖先。

为了给您更多的见解,代码就是这样

public interface IRepository<TEntity> where TEntity:class
{
    TEntity Get(int id);
    IEnumerable<TEntity> GetAll();
}

public interface ICustomerRepository : IRepository<Customer>
{
    IEnumerable<Customer> GetSeniorCustomers();
}

public class CustomerRepository : ICustomerRepository
{
    private readonly DbContext _context;
    public CustomerRepository(DbContext context) : base(context)
    {
        _context = context;
    }
// ... implementation of ICustomerRepo here
}

现在,这是事物的当前状态:

我想做的是:

public UnitOfWork(DbContext context, RepositoryFactory fac)
{
     _context = context;
     Customers = fac.Create(context, RepoType.Customer);
     Employees = fac.Create(context, RepoType.Employee);
}  

我知道它并没有给我带来更多的灵活性,但是我认为它确实使代码不太笨拙。

但是,正如我之前提到的,我无法想到Create()方法的有效返回类型。

因此,我想到了在RepositoryFactory类中创建多个方法的想法,而不是像这样的单个参数化方法:

public class RepositoryFactory
{
    public ICustomerRepository CreateCustomerRepo(DbContext context){/*...*/}
    public IEmployeeRepository CreateEmployeeRepo(DbContext context){/*...*/} 
}

所以问题是:

  1. 我正在做的事情甚至可以称为工厂方法吗?
  2. 如果不是,至少是一个有效的解决方案?如果没有,我如何以一种更简洁的方式实现同一件事

通过实现相同的目的我的意思是实现一种以可管理的,简洁的方式创建存储库的方式。

感谢您提前获得的所有帮助。

2 个答案:

答案 0 :(得分:1)

首先,要明确自己的目标。存储库模式存在(至少)3个关键原因:

1)提取数据层。就EF而言,如果这是您的目标,那么存储库模式将不再有用。尝试从Entity Framework中提取应用程序要比其值得多得多的麻烦。您将最终以残破的DAL或两者皆有,而EF无法提供令人敬畏的DAL或效率低下/缓慢,或者以表达式和其他复杂性为参数的非常复杂的存储库方法。尝试将应用程序从EF中抽象出来(例如,如果您可能想更改为另一个ORM)是没有意义的。接受EF就像接受在.Net中编写应用程序这样的事实。要将EF抽象化到可以替换的程度,您最好也不要使用它,因为您将看不到EF真正可以提供的任何好处。

2)使业务逻辑更易于测试。 IMO这对于使用Entity Framework的存储库仍然是有效的参数。是的,可以嘲笑EF DbContexts ,但它们仍然是一团糟。模拟存储库并不比任何其他依赖项难。

3)充当域网守。 DDD之类的模式旨在针对域对象和服务中的数据锁定操作。当使用EF帮助包含负责操作域的方法时,存储库模式可以对此提供帮助。对于纯DDD,我也不推荐使用它们,尽管我也不建议将实体用作DDD域对象。我确实使用存储库模式来管理CRUD的C.R.和D.方面,并依靠视图模型来封装围绕U的域逻辑。

我发现在第2点和第3点服务中最常用的存储库模式是消除通用存储库的非常普遍的概念,而是更合理地对待存储库,就像对待控制器一样在MVC中。除了视图和模型之间,模型和数据之间,不同。存储库是一个服务于Controller(在MVC中)的类,因为它负责创建,读取(在核心级别)和删除实体。这种模式与工作单元一起很好地工作。 (https://github.com/mehdime/DbContextScope是我采用的实现。)

在创建实体时,它负责确保提供了所有必需的(不可为null的值)和引用,并返回了与DbContext相关联的实体,可以使用了。有效的实体工厂。您可以争论关注点的分离,尽管鉴于存储库已经可以访问DbContext来检索相关的实体,但这种情况几乎是工作的最佳场所。

在Reading实体中,它通过提供IQueryable<TEntity>,对诸如软删除方案强制执行.Where(x => x.IsActive)之类的核心级别规则,或为基于身份验证/授权/租用的过滤器提供基本参考例如,依赖关系可以揭示当前用户。通过公开IQueryable,您可以简化存储库的实现,并让使用者(控制器)控制如何使用数据。这可以将延迟执行用于以下目的:

  • 仅选择视图模型所需的数据。
  • 执行计数并存在检查。 (.Any()
  • 针对特定用例在整个实体结构中自定义过滤逻辑。
  • 执行分页。
  • 根据需要获取尽可能多(.ToList()、. Take())或更少(.SingleOrDefault()、. FirstOrDefault())数据。

读取方法非常容易模拟,并且使存储库实现占用的空间很小。消费者需要意识到,他们正在与实体打交道,并处理EF及其代理的细微差别,但是消费者是工作单元(DbContext的寿命)的监护人,因此将这一事实隐藏在其中是很不现实的。 。将复杂的查询表达式作为参数传递到存储库方法中,使消费者同样有责任了解EF的细微差别。调用馈送到通用存储库的私有方法以进入Where子句的表达式将使事情中断的速度同样快。沿着这条路线走,会跳到上面的#1点,请不要从应用程序中提取EF。

在“删除实体”中,它确保正确管理实体及其关联,无论是硬删除还是软删除。

我避免使用通用存储库,因为就像Controller(和视图)将处理任何数量的相关域视图模型一样,这意味着它们将需要处理许多相关的数据实体。针对任何一个实体的行动将始终与针对其他受抚养者的行动联系在一起。对于通用存储库,分离意味着a)数量不断增加的依赖关系; b)进行琐碎处理的通用方法和大量自定义代码来处理有意义的内容,或者复杂的代码尝试以通用(基本)方式对其进行简化。通过每个控制器有一个存储库,也许还有一些用于公共实体的真正公共的共享存储库。 (例如,查找)我的存储库是专门为服务应用程序的一个区域而设计的,并且只有一个更改的理由。当然,可能有2个或多个屏幕需要来自存储库的相同行为,但是随着应用程序或服务的那些方面成熟,它们的存储库可以根据需要成熟/优化而没有副作用。 SRP和KISS轻松胜过DNRY。

泛型类通常都有其用途,但是在几乎所有看到开发人员编写它们的情况下,我都认为这是过早的优化。从非泛型实现开始,然后随着产品的成熟,将泛型优化为代码,而不是尝试围绕它们设计体系结构。结果几乎总是认识到,您需要对它们进行去优化,或者“聪明”地解决模式阻碍开发的已发现限制。

无论如何,除了“ EF不需要存储库”以外,还有一些值得深思的地方:)

答案 1 :(得分:0)

如果您不太在意“打开/关闭”原理,那么“ RepositoryFactory类内部的多个方法”解决方案就非常好。如果这符合您的需求,那么您可以选择这样做。

  

我正在做的事情甚至可以称为工厂方法吗?

是;这些是工厂中的方法。无论如何,只要它满足您的要求而又不产生新问题,就不要太在意。

  

如果不是,那至少是一个有效的解决方案吗?

如上所述,如果它满足您的需求;是的,这是有效的。

  

如果没有,我如何以更清洁的方式实现同​​一目标?

另一种替代方法是使用工厂方法,如下所示:

public T CreateRepository<T>() where T : IRepositoryId
{
    IRepositoryId repository = null;
    if(typeof(T) == typeof(ICustomerRepository))
        repository = new CustomerRepository(context);
    ......
    ......
    else
        throw new XyzException("Repository type is not handled.");
    return (T)repository;
}

public interface IRepositoryId
{
    Guid RepositoryId { get; }
}

您现有的IRepository接口是通用的。使用该接口实现上述方法时,会产生问题。因此,只需创建其他界面即可,如上所示。从此界面派生每个存储库。