使用实体框架的存储库模式检索复杂对象图的模式

发布于 2024-09-27 02:39:49 字数 809 浏览 3 评论 0原文

我们有一个 ASP.NET MVC 站点,它使用带有存储库和 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 数据与国内法律无关。

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

We have an ASP.NET MVC site that uses Entity Framework abstractions with Repository and UnitOfWork patterns. What I'm wondering is how others have implemented navigation of complex object graphs with these patterns. Let me give an example from one of our controllers:

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

It's a registration process and pretty much everything hangs off the POCO class Person. In this case we're caching the person through the registration process. I've now started implementing the latter part of the registration process which requires access to data deeper in the object graph. Specifically DPA data which hangs off Legal inside Country.

The code above is just mapping out the model information into a simpler format for the ViewModel. My question is do you consider this fairly deep navigation of the graph good practice or would you abstract out the retrieval of the objects further down the graph into repositories?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

缘字诀 2024-10-04 02:39:49

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

如果您没有执行任何操作,则默认情况下它是打开的。

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

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

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

然后,我们急切加载这些属性,因为控制器需要它们。但重要的是,控制器必须明确请求它们。

这基本上告诉 UI - “嘿,您只获得有关此实体的核心信息。如果您想要其他任何信息,请提出要求”。

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

服务层的好处很简单——更松散的耦合。存储库仅处理添加、删除、查找(返回 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。

In my opinion, the important question here is - have you disabled LazyLoading?

If you haven't done anything, then it's on by default.

So when you do Person.Pathway.Country, you will be invoking another call to the database server (unless you're doing eager loading, which i'll speak about in a moment). Given you're using the Repository pattern - this is a big no-no. Controllers should not cause direct calls to the database server.

Once a C ontroller has received the information from the M odel, it should be ready to do projection (if necessary), and pass onto the V iew, not go back to the M odel.

This is why in our implementation (we also use repository, ef4, and unit of work), we disable Lazy Loading, and allow the pass through of the navigational properties via our service layer (a series of "Include" statements, made sweeter by enumerations and extension methods).

We then eager-load these properties as the Controllers require them. But the important thing is, the Controller must explicitly request them.

Which basically tells the UI - "Hey, you're only getting the core information about this entity. If you want anything else, ask for it".

We also have a Service Layer mediating between the controllers and the repository (our repositories return IQueryable<T>). This allows the repository to get out of the business of handling complex associations. The eager loading is done at the service layer (as well as things like paging).

The benefit of the service layer is simple - more loose coupling. The Repository handles only Add, Remove, Find (which returns IQueryable), Unit of Work handles "newing" of DC's, and Commiting of changes, Service layer handles materialization of entities into concrete collections.

It's a nice, 1-1 stack-like approach:

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)

We haven't got up to the MVC layer yet (we're doing TDD), but a service layer could be another place you could hydrate the core entities into ViewModels. And again - it would be up to the controller to decide how much information it wishes.

Again, it's all about loose coupling. Your controllers should be as simplistic as possible, and not have to worry about complex associations.

In terms of how many Repositories, this is a highly debated topic. Some like to have one per entity (overkill if you ask me), some like to group based on functionality (makes sense in terms of functionality, easier to work with), however we have one per aggregate root.

I can only guess on your Model that "Person" should be the only aggregate root i can see.

Therefore, it doesn't make much sense having another repository to handle "Pathways", when a pathway is always associated with a particular "Person". The Person repository should handle this.

Again - maybe if you screencapped your EDMX, we could give you more tips.

This answer might be extending out a little too far based on the scope of the question, but thought i'd give an in-depth answer, as we are dealing with this exact scenario right now.

HTH.

ぃ双果 2024-10-04 02:39:49

这取决于您在某一时间使用的信息量。

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

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

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

正如我在一开始所说的那样,对此没有一刀切的规则,但我想说的是,您很少需要吸收那么多信息。

It depends on how much of the information you're using at any one time.

For example, if you just want to get the country name for a person (person.Pathway.Country.Name) what is the point in hydrating all of the other objects from the database?

When I only need a small part of the data I tend to just pull out what I'm going to use. In other words I will project into an anonymous type (or a specially made concrete type if I must have one).

It's not a good idea to pull out an entire object and everything related to that object every time you want to access some properties. What if you're doing this once every postback, or even multiple times? By doing this you might be making life easier in the short term at the cost of you're making your application less scalable long term.

As I stated at the start though, there isn't a one size fits all rule for this, but I'd say it's rare that you need to hydrate that much information.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文