简体   繁体   English

带有ORM的丰富域模型

[英]Rich domain model with ORM

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. 如果我不需要,我不想添加virtual
  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. 我不想使用过于复杂的映射,因为我的域模型使用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. 使用存储库模式可以轻松地检索构造的域对象,并从ORM返回的数据实体构建域对象。 Using AutoMapper, this would be trivial and not too much code overhead. 使用AutoMapper,这将是微不足道的,而不是太多的代码开销。

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: 另外,对于相同的“事物”,我会有很多类,特别是在WCF和UI的扩展上下文中:

  1. Data entity (mapped to the ORM) 数据实体(映射到ORM)
  2. Domain model 领域模型
  3. WCF DTO 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 在域模型上做出妥协以匹配ORM的先决条件,从而具有ORM泄漏到的域模型
  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. 除了接受的答案,请参阅我的答案 ,了解我如何为我解决这些问题的具体信息。

In short - it is not solved 简而言之 - 它没有解决

(here goes additional useless characters to post my awesome answer) (这里有额外无用的字符来发布我真棒的答案)

I would question that matching the prereqs of an ORM is necessarily "making compromises". 我想问一下,匹配ORM的先决条件必然是“妥协”。 However, some of these are fair points from the standpoint of a highly SOLID, loosely-coupled architecture. 然而,从高度SOLID,松散耦合的架构的角度来看,其中一些是公平的。

An ORM framework exists for one sole reason; ORM框架存在的唯一原因; 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. 采用您实现的域模型,并将其保存到类似的数据库结构中,而不必实现大量容易出错,几乎不可能的单元测试SQL字符串或存储过程。 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. 如果您想要存储过程,或者需要它们并且需要使用它们(无论您是否想要),大多数ORM都不是正确的工具。 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. 如果您有一个非常复杂的域结构,以至于ORM无法映射字段与其数据源之间的关系,我会严重质疑您使用该域和该数据源的原因。 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). 如果您想要100%POCO对象,而不了解后面的持久性机制,那么您可能最终会围绕ORM的大部分功能进行最终运行,因为如果域没有虚拟成员或子集合可以用代理替换,然后你被迫加载整个对象图(如果你有一个庞大的相互关联的对象图,这可能是不可能的)。

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. 虽然ORM在域设计方面确实需要持久性机制领域的一些知识,但ORM仍然会产生更多的SOLID设计,即IMO。 Without an ORM, these are your options: 没有ORM,这些是您的选择:

  • 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. 创建每个处理不同对象类型的DAO。 These types require you to hard-code the get and set between ADO DataReaders and your objects; 这些类型要求您对ADO DataReader和对象之间的get和set进行硬编码; in the average case a mapping greatly simplifies the process. 在一般情况下,映射大大简化了过程。 The DAOs also have to know about each other; DAO也必须彼此了解; to persist an Invoice you need the DAO for the Invoice, which needs a DAO for the InvoiceLine, Customer and GeneralLedger objects as well. 要保留发票,您需要发票的DAO,它还需要为InvoiceLine,Customer和GeneralLedger对象提供DAO。 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) 设置一个ActiveRecord模式,其中对象可以自行保存(并将更多有关持久性机制的知识放入您的域中)

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. 总的来说,第二个选项是最SOLID,但更多时候它变成了野兽和三分之二的维护,特别是在处理包含反向引用和循环引用的域时。 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. 例如,对于快速检索和/或遍历,InvoiceLineDetail记录(可能包含运输说明或税务信息)可能直接引用Invoice以及它所属的InvoiceLine。 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. 这创建了一个3节点循环引用,要求O(n ^ 2)算法检测已经处理了对象,或者需要关于反向引用的“级联”行为的硬编码逻辑。 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. 因此,总而言之,我认为,鉴于域名足够复杂,ORM是所有邪恶中最少的。 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"). 它们封装了很多关于持久性机制的非SOLID,并将关于其持久性的域的知识减少到分解为简单规则的非常高级的实现细节(“所有域对象必须将所有公共成员标记为虚拟”)。

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. 像部分类这样的东西比以前的.NET时代要容易得多。 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. 但是ORM(作为一种独特的“事物”,而不是只是在进出数据库时所做的事情)仍然需要很多妥协,而且坦率地说,它们对我来说太抽象了。 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. 我知道ORM支持它们,但大多数ORM用户不倾向于这样做,这对我来说是一个巨大的负面因素 - 因为他们谈论最佳实践,然后他们偶然会遇到基于表格的设计,即使它是创建的从代码优先模型。 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 - ie 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. 如果您允许开发人员针对您的表编写ORM代码,那么您的应用将会遇到多年无法生存的问题。 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. 越来越多的人想要敲击这些实体,现在他们都在使用从表生成的ORM。 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. 此外,只有存储过程才能为您提供任何类型的可用的基于角色的可管理性,而无需处理每列GRANT基础上的每个tabl - 这是非常痛苦的。 If you program well in OO, you have to understand the benefits of controlled coupling. 如果您在OO中编程良好,则必须了解受控耦合的好处。 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. 或者,如果您只想要一个“哑”数据存储区,请不要使用关系数据库。

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. 使用NHibernate,我能够将相当复杂的域模型映射到合理的数据库设计,这些设计不会让DBA感到畏缩。

Sometimes it is needed to create a new implementation of the IUserType interface so that NHibernate can correctly persist a custom type. 有时需要创建IUserType接口的新实现,以便NHibernate可以正确地保持自定义类型。 Thanks to NHibernates extensible nature, that is no big deal. 感谢NHibernates的可扩展性,这没什么大不了的。

I found no way to avoid adding virtual to my properties without loosing lazy loading. 我发现没有办法避免在不丢失延迟加载的情况下向我的属性添加virtual 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: 我添加了我需要的构造函数作为公共构造函数,并为NHibernate添加了一个过时的受保护构造函数:

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

Have you looked at the Entity Framework 4.1 Code First? 您是否先查看了Entity Framework 4.1 Code? IIRC, the domain objects are pure POCOs. IIRC,域对象是纯POCO。

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. 将EF 4.1与虚拟关键字一起用于我们的业务对象,并拥有我们自己的T4模板自定义实现。 Wrapping the ObjectContext behind an interface for repository style dataaccess. 将ObjectContext包装在存储库样式dataaccess的接口后面。
  2. using automapper to convert between Bo To DTO 使用automapper在Bo To DTO之间进行转换
  3. using autoMapper to convert between ViewModel and DTO. 使用autoMapper在ViewModel和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. 你会认为viewmodel和Dto和Business对象是相同的东西,它们可能看起来相同,但它们在关注方面有一个非常明确的分离。 View Models are more about UI screen, DTO is more about the task you are accomplishing, and Business objects primarily concerned about the domain 视图模型更多地是关于UI屏幕,DTO更多地是关于您正在完成的任务,而Business对象主要关注域

There are some comprimises along the way, but if you want EF, then the benfits outweigh things that you give up 一路上有一些混淆,但如果你想要EF,那么你的好处就会超过你所放弃的东西

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM