首页 > 解决方案 > 简单注入器:将导航属性注入存储库

问题描述

假设我们有一个具有多个相同类型的多对多导航字段的实体:

class Post : IdProvider<TPrimaryKey>
    where TKey : struct
{
    ...
    public virtual ICollection<User> LikedBy { get; set; }
    public virtual ICollection<User> SharedBy { get; set; }
}

请记住,这纯粹是一个虚构的例子,它可以是任何具有相同类型的两个属性的东西。

我目前正在为以这种方式注册的单个实体运行基本通用存储库类。定义如下所示:

EntityRepository<TKey, T> : EntityRepositoryBase, IEntityRepository<TKey, T>
    where T : class, IIdProvider<TKey> 
    where TKey : struct 

DI 选择正确的 typeparam 并将其注入我的 repos,就像一个魅力。我对 DI 很陌生并且阅读了这篇文章,但仍然很难理解为什么它让我的生活更轻松并将 EF 特定方法隐藏在控制器的视线之外,为什么会如此糟糕。

然后我有 N 对多的基础存储库类来处理两个实体之间的关系。也许这是一种过度简化,但我想认为每个多对多关系都有主体和依赖关系,所以我将在帖子中添加喜欢/共享的用户,反之亦然。在我当前的实现中,基础 repo 类有一个抽象属性

public abstract Expression<Func<TPrincipal, ICollection<TDependent>>> GetDependentCollectionExpression { get; }

所以我不能使基础 repo 类成为非抽象类,目前必须为我的每个明确指定依赖属性的关系创建此类的多个实现。

class PostToLikedUsersRepository : PrincipalToManyDependentRepository<Post, User>
{
    ...
    public override Expression<Func<Post, ICollection<User>>> GetDependentCollectionExpression
    {
        get { return item => item.LikedBy; }
    }
}

摆脱它的一种方法是通过反射搜索具有 TDependent 类型的正确 TPrincipal 的属性,但在当前示例中会有很多这样的属性。我从没想过如何处理同一个 Principal to Dependent 类型的多个 repos 实现,因为我不确定如何让 DI 将每个 repo 的正确实现注入每个控制器:一个负责点赞,一个负责共享.

所以我的直觉和意图是以某种方式参数化属性选择。似乎我需要向我的 repo 注入另一个参数来自动获取正确的属性,但我不知道如何实现它。我应该在这里注入财产吗?但是我应该如何以及在哪里以一般方式为其选择正确的值?它应该是另一个构造函数参数吗?

标签: c#entity-frameworksimple-injector

解决方案


我不同意文章对数据层和应用程序逻辑之间“中介”的解释。调解是一种促进各层之间协商的行为,而不是隐藏/石墙。存储库应该用于简化应用程序逻辑和数据之间的交互。如果您选择了诸如实体框架之类的 ORM 作为您的数据访问层,那么我的建议是采用它为您的代码提供的所有内容:

  • 写起来更简单。
  • 更容易理解。
  • 维护更简单。

通用存储库 IMO 是一种反模式,因为它们鼓励开发人员将实体单独而不是集体视为域结构。如果我有订单、客户和订单行,我是否需要为每个订单提供存储库?如果我创建订单,我还需要创建订单行,并将订单与客户相关联。对于通用存储库,我可能有OrderRepository : Repository<Order>一个Get<T>base 方法,但是我还需要获取一个客户,这是否也意味着对 CustomerRepository 的引用?创建 OrderLines 怎么样?它们依赖于产品和其他元素。如果存储库用于将我的应用程序逻辑与实体隔离,那么这意味着使用 OrderLineRepository、ProductRepository 等。所有这些都只是为了创建订单。为了将应用程序代码与实体等隔离,存储库不应返回实体。如果他们这样做,你仍然依赖于幕后的EF。如果您在 DbContext 的范围之外,延迟加载将失败。返回 DTO 是解决此问题的一种常见解决方法,但这会导致在尝试隐藏 Entity Framework 的同时提供数据时效率低下和不灵活。它还会导致脆弱的代码或大量重复,因为单一用途的存储库在应用程序的不同区域之间共享,每个区域都在不断努力获取比以前的消费者更多的数据,或者以稍微不同的方式获取数据方法。您开始在存储库中看到许多类似的方法,或者方法中的额外参数,或者非常丑陋的模式,例如尝试将表达式树或“包含”字符串列表发送到方法中以尝试抽象 EF 可以为您做什么。

总体而言,虽然这些依赖项可能看起来很简单,但协调和测试应用程序逻辑所需的代码变得非常庞大和复杂。

对我来说,存储库有三个关键目的:

  • 使应用程序逻辑更易于测试。(抽象 DB,而不是EF)
  • 充当工厂以确保正确初始化实体。
  • 集中低级过滤器和功能。(IsActive、授权、多租户、软删除等)

您可能会争辩说,“工厂”的责任应该落在一个单独的类上,该类依赖于一个或多个存储库。从我的角度来看,存储库可以从 DbContext 访问它所需的信息,因此它保持简单。也就是说,我的存储库不是通用类,而是管理一个类似于我构造控制器的垂直方向。存储库满足控制器的需求,即应用程序关键区域的需求。我可能有额外的存储库来服务于其他共享的集中关注点。(查找、身份验证等)这有助于确保存储库使我的代码易于测试,并且易于修改,因为存储库的实现服务于一个目的。对可能对订单感兴趣的应用程序的其他区域进行更改

存储库促进或调解与 EF 的交互,而不是隔离。


推荐阅读