【问题标题】:Simple Injector: injecting navigation properties into repositories简单注入器:将导航属性注入存储库
【发布时间】:2018-07-11 19:36:25
【问题描述】:

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

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

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

我目前正在为this manner 中注册的单个实体运行基本通用存储库类。定义如下所示:

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

DI 选择正确的 typeparam 并将其注入我的 repos,就像一个魅力。我对 DI 很陌生并且阅读了this article,但仍然很难理解为什么它让我的生活更轻松并将 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-framework simple-injector


    【解决方案1】:

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

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

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

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

    对我来说,存储库有三个主要目的:

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

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

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

    【讨论】:

    • 但是如果我们需要特定控制器的一些特定行为,我们可以从泛型类继承并覆盖它的虚拟方法吗?如果我的大多数控制器都有类似的 CRUDL 操作,那么类似的方法有什么问题?这不是一遍又一遍地重复几乎相同的代码吗?
    • 您回答了自己的问题。如果您有“类似”行为,则需要覆盖(重复)或传递参数。 (复杂)关于代码重用的担忧是关于“相同”的行为。吻。胜过 D.N.R.Y。如果您有相同的需求,请利用公共基础或共享的单一用途依赖项。重构以提高效率,而不是预先优化和重构以降低效率。
    猜你喜欢
    • 1970-01-01
    • 2011-09-07
    • 1970-01-01
    • 2016-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多