【问题标题】:Pattern for retrieving complex object graphs with Repository Pattern with Entity Framework使用实体框架的存储库模式检索复杂对象图的模式
【发布时间】:2019-11-14 19:44:12
【问题描述】:

我们有一个 ASP.NET MVC 站点,它使用带有 Repository 和 UnitOfWork 模式的实体框架抽象。我想知道的是其他人如何使用这些模式实现复杂对象图的导航。让我举一个我们的控制器的例子:

var model = new EligibilityViewModel
   {
       Country = person.Pathway.Country.Name,
       Pathway = person.Pathway.Name,
       Answers = person.Answers.ToList(),
       ScoreResult = new ScoreResult(person.Score.Value),
       DpaText = person.Pathway.Country.Legal.DPA.Description,
       DpaQuestions = person.Pathway.Country.Legal.DPA.Questions,
       Terms = person.Pathway.Country.Legal.Terms,
       HowHearAboutUsOptions = person.Pathway.Referrers
   };

这是一个注册过程,几乎所有内容都与 POCO 类 Person 无关。在这种情况下,我们通过注册过程缓存人员。我现在已经开始实现注册过程的后半部分,这需要访问对象图中更深的数据。尤其是 DPA 数据,这些数据在 Country 内与 Legal 相关联。

上面的代码只是将模型信息映射为 ViewModel 的更简单格式。我的问题是,您是否认为这种相当深入的图表导航是一种很好的做法,或者您会将在图表下方进一步检索对象的过程抽象到存储库中?

【问题讨论】:

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


    【解决方案1】:

    在我看来,这里的重要问题是 - 您是否禁用了 LazyLoading?

    如果你什么都没做,则默认开启。

    所以当您执行Person.Pathway.Country 时,您将调用另一个对数据库服务器的调用(除非您正在执行预加载,我稍后会谈到)。鉴于您正在使用存储库模式 - 这是一个很大的禁忌。控制器不应直接调用数据库服务器。

    一旦 C 控制器收到来自 M 模型的信息,它应该准备好进行投影(如果需要),并传递到 V 看,不要回到 M 模型。

    这就是为什么在我们的实现中(我们还使用存储库、ef4 和工作单元),我们禁用延迟加载,并允许通过我们的服务层(a一系列“包含”语句,通过枚举和扩展方法变得更甜美)。

    然后我们根据控制器的需要eager-load这些属性。但重要的是,控制器必须明确请求它们。

    这基本上告诉用户界面 - “嘿,你只得到关于这个实体的核心信息。如果你想要其他任何东西,请提出要求”。

    我们还有一个服务层在控制器和存储库之间进行中介(我们的存储库返回IQueryable<T>)。这允许存储库摆脱处理复杂关联的业务。急切加载是在服务层完成的(以及分页等)。

    服务层的好处很简单——更松散的耦合。存储库只处理添加、删除、查找(返回 IQueryable),工作单元处理 DC 的“更新”和提交更改,服务层处理实体到具体集合的具体化。

    这是一种不错的 1-1 堆栈式方法:

    personService.FindSingle(1, "Addresses") // Controller calls service
     |
     --- Person FindSingle(int id, string[] includes) // Service Interface
          |
           --- return personRepository.Find().WithIncludes(includes).WithId(id); // Service calls Repository, adds on "filter" extension methods
               |
                --- IQueryable<T> Find() // Repository
                    |
                     -- return db.Persons; // return's IQueryable of Persons (deferred exec)
    

    我们还没有到 MVC 层(我们正在做 TDD),但服务层可能是另一个可以将核心实体水合到 ViewModel 中的地方。再一次 - 由控制器决定它需要多少信息。

    同样,这完全是关于松散耦合。您的控制器应尽可能简单,不必担心复杂的关联。

    存储库的数量而言,这是一个备受争议的话题。有些人喜欢每个实体有一个(如果你问我,那就太夸张了),有些人喜欢根据功能进行分组(在功能方面有意义,更容易使用),但是我们每个聚合根都有一个。

    我只能在你的模型上猜测“人”应该是我能看到的唯一聚合根。

    因此,当路径总是与特定的“人”相关联时,拥有另一个存储库来处理“路径”没有多大意义。 Person 存储库应处理此问题。

    再一次 - 如果您截屏了 EDMX,我们可以为您提供更多提示。

    根据问题的范围,这个答案可能有点过分了,但我想我会给出一个深入的答案,因为我们现在正在处理这个确切的场景。

    HTH。

    【讨论】:

    • 谢谢,您的回答确实阐明了我的想法。到目前为止的答案帮助我意识到,当我查看我的代码时,我担心 EF 在幕后做了什么来实现这些对象图。我知道我可以启动 SQL Profiler 以查看发生了什么,但我最初的想法是,如果性能或将这些数据传送到远程源的要求成为要求,那么将其从控制器中抽象出来将使我在未来获得更好的控制。您对控制器和存储库之间的服务层的建议是我要研究的。
    • @Daz Lewis - 绝对是。我们的网站有一个 API。这就是我们有服务层的原因。我们的网站和 API 都是“客户”。这允许一个漂亮、流畅(但紧凑)的编程模型。顺便说一句 - 您不必使用 SQL Profiler。 ObjectQuery 上有一个名为.ToTraceString 的方法。我们将它与日志记录一起使用,我们所有的存储库方法都调试这一行(信息级别),因此我们可以看到正在执行的内容。无论如何,请务必查看服务层。我们的存储库非常简单 - 看看我的一些问题,看看我是如何做到的。
    • “控制器不应导致直接调用数据库服务器”,在此之后,您的控制器也不应调用存储库来直接调用数据库服务器。当访问将要延迟加载的属性时,不会像您的存储库导致直接调用数据库服务器一样直接调用数据库服务器。是的,它确实会导致调用数据库服务器,但这是通过使用 ORM 工具的基础架构间接调用的。
    • @Ryan - 是的,现在可以了。不过,我真的不再遵循这种模式了。
    • @RPM1984 - 能否请您扩展一下“不再遵循这种模式”?你找到更好的解决方案了吗?我不确定你的陈述是指什么。您现在允许延迟加载吗?
    【解决方案2】:

    这取决于您在任何时候使用的信息量。

    例如,如果您只想获取某个人的国家/地区名称 (person.Pathway.Country.Name),那么从数据库中合并所有其他对象有什么意义?

    当我只需要一小部分数据时,我倾向于只提取我将要使用的数据。换句话说,我将投影到一个匿名类型(或者如果我必须有一个特制的具体类型)。

    每次您想要访问某些属性时,拉出整个对象以及与该对象相关的所有内容并不是一个好主意。如果您每次回发都执行一次,甚至多次执行此操作怎么办?通过这样做,您可能会在短期内让生活变得更轻松,但代价是您的应用程序在长期内的可扩展性降低。

    正如我在开始时所说的那样,没有一刀切的规则,但我想说你很少需要补充这么多信息。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-03
      • 2010-12-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多