具有 ORM 的丰富域模型

发布于 2024-12-08 17:39:09 字数 827 浏览 0 评论 0原文

我似乎错过了一些东西,广泛使用谷歌并没有帮助提高我的理解......
这是我的问题:
我喜欢以一种持久性无知的方式创建我的域模型,例如:

  1. 如果我不需要的话,我不想添加virtual
  2. 我不喜欢添加默认构造函数,因为我喜欢我的对象始终被完全构造。此外,在依赖注入的上下文中,对默认构造函数的需求是有问题的。
  3. 我不想使用过于复杂的映射,因为我的域模型使用 ORM 不容易支持的接口或其他构造。

对此的一种解决方案是拥有单独的域对象和数据实体。使用存储库模式并根据 ORM 返回的数据实体构建域对象,可以轻松解决构建的域对象的检索问题。使用 AutoMapper,这将是微不足道的,并且不会有太多的代码开销。

但这种方法有一个大问题:如果我自己不编写代码,我似乎无法真正支持延迟加载。此外,我会为相同的“事物”创建很多类,特别是在 WCF 和 UI 的扩展上下文中:

  1. 数据实体(映射到 ORM)
  2. 域模型
  3. WCF DTO
  4. 视图模型

所以,我的问题是:我是什么丢失的?这个问题一般是如何解决的?

更新:
到目前为止的答案表明了我已经担心的事情:看起来我有两个选择:

  1. 在域模型上做出妥协以匹配 ORM 的先决条件,从而拥有 ORM 泄漏的域模型
  2. 创建大量额外的代码

更新:< br> 除了已接受的答案之外,请参阅我的答案,了解有关我如何解决这些问题的具体信息。

I seem to be missing something and extensive use of google didn't help to improve my understanding...
Here is my problem:
I like to create my domain model in a persistence ignorant manner, for example:

  1. I don't want to add virtual if I don't need it otherwise.
  2. I don't like to add a default constructor, because I like my objects to always be fully constructed. Furthermore, the need for a default constructor is problematic in the context of dependency injection.
  3. I don't want to use overly complicated mappings, because my domain model uses interfaces or other constructs not readily supported by the ORM.

One solution to this would be to have separate domain objects and data entities. Retrieval of the constructed domain objects could easily be solved using the repository pattern and building the domain object from the data entity returned by the ORM. Using AutoMapper, this would be trivial and not too much code overhead.

But I have one big problem with this approach: It seems that I can't really support lazy loading without writing code for it myself. Additionally, I would have quite a lot of classes for the same "thing", especially in the extended context of WCF and UI:

  1. Data entity (mapped to the ORM)
  2. Domain model
  3. WCF DTO
  4. View model

So, my question is: What am I missing? How is this problem generally solved?

UPDATE:
The answers so far suggest what I already feared: It looks like I have two options:

  1. Make compromises on the domain model to match the prerequisites of the ORM and thus have a domain model the ORM leaks into
  2. Create a lot of additional code

UPDATE:
In addition to the accepted answer, please see my answer for concrete information on how I solved those problems for me.

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

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

发布评论

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

评论(6

玉环 2024-12-15 17:39:09

我怀疑匹配 ORM 的先决条件是否一定是“做出妥协”。然而,从高度可靠、松散耦合的架构的角度来看,其中一些观点是公平的。

ORM 框架的存在只有一个原因:采用您实现的域模型,并将其持久化到类似的数据库结构中,而无需实现大量容易出现错误、几乎无法进行单元测试的 SQL 字符串或存储过程。它们还可以轻松实现延迟加载等概念;在需要对象之前的最后一刻对对象进行水合,而不是自己构建大型对象图。

如果您想要存储过程,或者拥有它们并且需要使用它们(无论您是否愿意),大多数 ORM 都不是适合该工作的工具。如果您有一个非常复杂的域结构,以至于 ORM 无法映射字段与其数据源之间的关系,我会严重质疑您为什么要使用该域和该数据源。如果您想要 100% POCO 对象,而不了解背后的持久性机制,那么您可能最终会绕过 ORM 的大部分功能,因为如果域没有虚拟成员或子集合可以用代理替换,那么您就被迫急切加载整个对象图(如果您有一个巨大的互连对象图,这很可能是不可能的)。

虽然 ORM 确实需要一些领域设计方面的持久性机制方面的知识,但在我看来,ORM 仍然会带来更加可靠的设计。如果没有 ORM,您可以选择以下选项:

  • 您自己的存储库,其中包含一种方法来生成和保存域中每种类型的“顶级”对象(“上帝对象”反模式)
  • 创建 不同的对象类型。这些类型要求您对 ADO DataReaders 和对象之间的获取和设置进行硬编码;在一般情况下,映射会大大简化该过程。 DAO 还必须相互了解;要持久保存发票,您需要发票的 DAO,发票也需要 InvoiceLine、Customer 和 GeneralLedger 对象的 DAO。并且,所有这些都必须内置一个通用的、抽象的事务控制机制。
  • 设置一个 ActiveRecord 模式,其中对象可以自行持久化(并将更多有关持久化机制的知识放入您的域中)

总体而言,第二个选项是最可靠的,但通常情况下,它会变成野兽和三分之二维护,尤其是在处理包含反向引用和循环引用的域时。例如,为了快速检索和/或遍历,InvoiceLineDetail 记录(可能包含装运单或税务信息)可能直接引用发票及其所属的 InvoiceLine。这会创建一个 3 节点循环引用,需要 O(n^2) 算法来检测对象是否已被处理,或者需要有关反向引用“级联”行为的硬编码逻辑。我之前必须实现“图步行器”;相信我,如果有任何其他方法可以完成这项工作,您就不想这样做。

因此,总而言之,我的观点是,在足够复杂的领域中,ORM 是所有危害中最小的。它们封装了有关持久性机制的许多不可靠的内容,并将有关持久性的域知识减少到非常高级的实现细节,这些细节分解为简单的规则(“所有域对象必须将其所有公共成员标记为虚拟”)。

I would question that matching the prereqs of an ORM is necessarily "making compromises". However, some of these are fair points from the standpoint of a highly SOLID, loosely-coupled architecture.

An ORM framework exists for one sole reason; to take a domain model implemented by you, and persist it into a similar DB structure, without you having to implement a large number of bug-prone, near-impossible-to-unit-test SQL strings or stored procedures. They also easily implement concepts like lazy-loading; hydrating an object at the last minute before that object is needed, instead of building a large object graph yourself.

If you want stored procs, or have them and need to use them (whether you want to or not), most ORMs are not the right tool for the job. If you have a very complex domain structure such that the ORM cannot map the relationship between a field and its data source, I would seriously question why you are using that domain and that data source. And if you want 100% POCO objects, with no knowledge of the persistence mechanism behind, then you will likely end up doing an end run around most of the power of an ORM, because if the domain doesn't have virtual members or child collections that can be replaced with proxies, then you are forced to eager-load the entire object graph (which may well be impossible if you have a massive interlinked object graph).

While ORMs do require some knowledge in the domain of the persistence mechanism in terms of domain design, an ORM still results in much more SOLID designs, IMO. Without an ORM, these are your options:

  • Roll your own Repository that contains a method to produce and persist every type of "top-level" object in your domain (a "God Object" anti-pattern)
  • Create DAOs that each work on a different object type. These types require you to hard-code the get and set between ADO DataReaders and your objects; in the average case a mapping greatly simplifies the process. The DAOs also have to know about each other; to persist an Invoice you need the DAO for the Invoice, which needs a DAO for the InvoiceLine, Customer and GeneralLedger objects as well. And, there must be a common, abstracted transaction control mechanism built into all of this.
  • Set up an ActiveRecord pattern where objects persist themselves (and put even more knowledge about the persistence mechanism into your domain)

Overall, the second option is the most SOLID, but more often than not it turns into a beast-and-two-thirds to maintain, especially when dealing with a domain containing backreferences and circular references. For instance, for fast retrieval and/or traversal, an InvoiceLineDetail record (perhaps containing shipping notes or tax information) might refer directly to the Invoice as well as the InvoiceLine to which it belongs. That creates a 3-node circular reference that requires either an O(n^2) algorithm to detect that the object has been handled already, or hard-coded logic concerning a "cascade" behavior for the backreference. I've had to implement "graph walkers" before; trust me, you DO NOT WANT to do this if there is ANY other way of doing the job.

So, in conclusion, my opinion is that ORMs are the least of all evils given a sufficiently complex domain. They encapsulate much of what is not SOLID about persistence mechanisms, and reduce knowledge of the domain about its persistence to very high-level implementation details that break down to simple rules ("all domain objects must have all their public members marked virtual").

归途 2024-12-15 17:39:09

简而言之 - 它没有解决

(这里有额外的无用字符来发布我很棒的答案)

In short - it is not solved

(here goes additional useless characters to post my awesome answer)

所有的优点。

我没有答案(但是当我决定添加有关存储过程的内容时,评论太长了),除了说我的哲学似乎与您的哲学相同,并且我编码或代码生成。

像部分类这样的事情使得这比早期 .NET 时代容易得多。但是 ORM(作为一个独特的“事物”,而不是仅仅在进出数据库时完成的事情)仍然需要大量的妥协,坦率地说,它们对我来说是一个抽象的漏洞。我不喜欢有很多欺骗类,因为我的设计往往有很长的寿命,并且多年来(甚至几十年)发生了很大的变化。

就数据库方面而言,我认为存储过程是必需的。我知道 ORM 支持它们,但大多数 ORM 用户不会这样做,这对我来说是一个巨大的负面影响 - 因为他们谈论最佳实践,然后他们耦合到基于表的设计,即使它是创建的来自代码优先模型。在我看来,如果他们不想以利用关系数据库优势的方式使用关系数据库,他们应该考虑对象数据存储。我首先相信代码和数据库——即同时来回建模数据库和对象模型,然后从两端向内工作。我将在这里阐述一下:

如果您让开发人员针对您的表编写 ORM 代码,那么您的应用程序将在生存多年时遇到问题。表格需要改变。越来越多的人想要对抗这些实体,现在他们都在使用从表生成的 ORM。随着时间的推移,您将需要重构您的表。此外,只有存储过程才能为您提供任何类型的可用的基于角色的可管理性,而无需在每列授予的基础上处理每个表 - 这是超级痛苦的。如果您能够很好地进行面向对象编程,您就必须了解受控耦合的好处。这就是所有存储过程 - 使用它们,以便您的数据库具有定义良好的接口。或者,如果您只想要一个“哑”数据存储,请不要使用关系数据库。

All good points.

I don't have an answer (but the comment got too long when I decided to add something about stored procs) except to say my philosophy seems to be identical to yours and I code or code generate.

Things like partial classes make this a lot easier than it used to be in the early .NET days. But ORMs (as a distinct "thing" as opposed to something that just gets done in getting to and from the database) still require a LOT of compromises and they are, frankly, too leaky of an abstraction for me. And I'm not big on having a lot of dupe classes because my designs tend to have a very long life and change a lot over the years (decades, even).

As far as the database side, stored procs are a necessity in my view. I know that ORMs support them, but the tendency is not to do so by most ORM users and that is a huge negative for me - because they talk about a best practice and then they couple to a table-based design even if it is created from a code-first model. Seems to me they should look at an object datastore if they don't want to use a relational database in a way which utilizes its strengths. I believe in Code AND Database first - i.e. model the database and the object model simultaneously back and forth and then work inwards from both ends. I'm going to lay it out right here:

If you let your developers code ORM against your tables, your app is going to have problems being able to live for years. Tables need to change. More and more people are going to want to knock up against those entities, and now they all are using an ORM generated from tables. And you are going to want to refactor your tables over time. In addition, only stored procedures are going to give you any kind of usable role-based manageability without dealing with every tabl on a per-column GRANT basis - which is super-painful. If you program well in OO, you have to understand the benefits of controlled coupling. That's all stored procedures are - USE THEM so your database has a well-defined interface. Or don't use a relational database if you just want a "dumb" datastore.

少年亿悲伤 2024-12-15 17:39:09

您看过 Entity Framework 4.1 Code First 吗? IIRC,域对象是纯 POCO。

Have you looked at the Entity Framework 4.1 Code First? IIRC, the domain objects are pure POCOs.

⒈起吃苦の倖褔 2024-12-15 17:39:09

这就是我们在最新项目中所做的,它非常有效地

  1. 使用 EF 4.1 和我们的业务对象的虚拟关键字,并拥有我们自己的 T4 模板的自定义实现。将 ObjectContext 包装在存储库样式数据访问的接口后面。
  2. 使用 automapper 在 Bo 到 DTO 之间进行转换
  3. 使用 autoMapper 在 ViewModel 和 DTO 之间进行转换。

你可能会认为 viewmodel 和 Dto 以及 Business 对象是同一件事,它们可能看起来相同,但它们在关注点方面有非常清晰的分离。
视图模型更多的是关于 UI 屏幕,DTO 更多的是关于你正在完成的任务,而业务对象主要关注领域。

一路上有一些妥协,但如果你想要 EF,那么好处超过你放弃的东西

this what we did on our latest project, and it worked out pretty well

  1. use EF 4.1 with virtual keywords for our business objects and have our own custom implementation of T4 template. Wrapping the ObjectContext behind an interface for repository style dataaccess.
  2. using automapper to convert between Bo To DTO
  3. using autoMapper to convert between ViewModel and DTO.

you would think that viewmodel and Dto and Business objects are same thing, and they might look same, but they have a very clear seperation in terms of concerns.
View Models are more about UI screen, DTO is more about the task you are accomplishing, and Business objects primarily concerned about the domain

There are some comprimises along the way, but if you want EF, then the benfits outweigh things that you give up

浮云落日 2024-12-15 17:39:09

一年多过去了,现在这些问题都已经为我解决了。

使用 NHibernate,我能够将相当复杂的域模型映射到合理的数据库设计,这不会让 DBA 感到畏缩。

有时需要创建 IUserType 接口的新实现,以便 NHibernate 可以正确地保留自定义类型。由于 NHibernate 的可扩展性,这没什么大不了的。

我发现没有办法在不丢失延迟加载的情况下避免将虚拟添加到我的属性中。我仍然不是特别喜欢它,特别是因为代码分析中关于虚拟属性的所有警告,没有派生类覆盖它们,但出于实用主义,我现在可以接受它。

对于默认构造函数,我还找到了一个可以接受的解决方案。我添加了我需要的构造函数作为公共构造函数,并添加了一个过时的受保护构造函数供 NHibernate 使用:

[Obsolete("This constructor exists because of NHibernate. Do not use.")]
protected DataExportForeignKey()
{
}

Over a year later, I have solved these problems for me now.

Using NHibernate, I am able to map fairly complex Domain Models to reasonable database designs that wouldn't make a DBA cringe.

Sometimes it is needed to create a new implementation of the IUserType interface so that NHibernate can correctly persist a custom type. Thanks to NHibernates extensible nature, that is no big deal.

I found no way to avoid adding virtual to my properties without loosing lazy loading. I still don't particularly like it, especially because of all the warnings from Code Analysis about virtual properties without derived classes overriding them, but out of pragmatism, I can now live with it.

For the default constructor I also found a solution I can live with. I add the constructors I need as public constructors and I add an obsolete protected constructor for NHibernate to use:

[Obsolete("This constructor exists because of NHibernate. Do not use.")]
protected DataExportForeignKey()
{
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文