任何.NET ORM都“正确”使用构造函数吗?

时间:2009-05-29 15:41:04

标签: .net orm

这在概念上与我的问题here有关。但是,我一直在玩NHibernate,并意识到我的问题的真正核心是什么。

在经典的OO设计中,为了正确封装数据,将值传递给存储在数据成员(字段)中的对象构造函数是一种常见模式。应该更改的那些值仅使用访问器(只读属性)公开。允许更改的那些具有访问器和更改器(读写属性)。在我看来,正确的 O / RM应该遵守这些约定,并在创建对象时使用可用的构造函数。依赖于读写属性,反射或其他,hackish(恕我直言)方法似乎......错误。

那里有一个.NET O / RM解决方案吗?

修改

为了解决Praveen的观点,我知道有些项目有一个用于选择构造函数的“默认”算法 - 例如,StructureMap总是使用具有最多参数的构造函数,除非标记具有自定义属性的构造函数。我可以看到这是处理这种情况的有效方法。除了 ORM之外,使用IoC容器可能会提供我需要的那种解决方案 - 虽然在我看来,这虽然本质上不是很糟糕,但是使用ORM是一个不必要的额外步骤

8 个答案:

答案 0 :(得分:3)

我认为大多数ORM实际上支持这个概念,至少DataObject.Net会这样做。此代码按预期工作:

[HierarchyRoot(typeof(KeyGenerator), "Id")]
public class Message : Entity
{
  [Field]
  public int Id { get; private set; }

  [Field(Length = 100)]
  public string Text { get; private set; }

  public Message(string Text)
  {
    Text = text;
  }
}

编辑: DataObjects以内部事务状态存储数据,并使用由PostSharp生成的特殊实现构造函数。当然,如果ORM使用poco对象,那也不是那么简单。

答案 1 :(得分:2)

不幸的是,如果不以某种方式标记构造函数,这在.NET中是不可能的。

为程序集元数据中的每个构造函数存储的方法签名仅包含构造函数的每个参数的类型。任何.NET ORM都无法真正知道要使用哪个构造函数。所有ORM看到的都是这样的:

.ctor()
.ctor(string, string)
.ctor(string, string, string)

ORM无法知道哪个.ctor参数对应于您的Customer对象的FirstName,LastName和MiddleName。

为了向您提供此支持,.NET ORM必须支持读取您为每个参数定义的自定义属性。你需要像这样标记你的构造函数:

public Customer([Property(“FirstName”)] string FirstName,[Property(“LastName”)] string LastName,[Property(“MiddleName”)] string MiddleName)

这有两个缺点:

  1. 没有办法(我能想到,有人可能会纠正我),这可以进入映射文件。
  2. 您仍然需要像往常一样编写相同的映射,因为ORM仍然需要能够为每个属性获取单独的值。
  3. 因此,您需要完成标记构造函数的所有额外工作,同时,您仍然需要像以前一样完全映射您的类。

答案 2 :(得分:1)

为什么你认为这感觉不对? 在重构对象时,您是否希望OR / M执行业务逻辑?恕我直言,没有。

当您从数据库加载对象时,无论如何,OR / M都应该能够重新构建对象。在重构时设置一些值不应该导致触发某种改变另一个值的逻辑(也许应该由ORM给出一个值......)

即便如此,我认为构造函数应该只包含那些在对象创建时必须使对象处于“有效”状态的字段的参数。 除此之外,您可能拥有通过某种计算获得值的公共只读属性,并且还需要保留。 如果你觉得反射是重组物体的“气味”,你将如何应对这种情况?你必须以某种方式创建一个公共方法,它能够设置'只读'值,但这打破了封装,你也不会那样。

答案 3 :(得分:1)

我同意之前的一个海报,构造函数应该用于在其生命周期的开始创建一个对象。使用构造函数来水合当前处于存档状态的现有对象充其量是反直觉的。

需要考虑的是,此时所有主要的ORM似乎都缺失的是,从存储在数据库或任何其他数据存储中的最后已知状态重新构建域对象本身并不是构建或修改操作;因此,不应使用建造者或财产制定者。相反,最接近这种操作的框架机制是序列化!已经识别出用于存储对象状态的模式,然后通过ISerializable接口,标准序列化构造函数等重构它。将对象持久保存到数据库与将其持久保存到流中没有什么不同;实际上,序列化期间使用的StreamingContextStates枚举的值之一是Persistence!。恕我直言,这应该是设计任何持久性机制时的标准方法。不幸的是,我不知道任何开箱即用的ORM。

还应该注意,为序列化而设计的对象仍然是POCO;持久性无知并未受到侵犯。这里的关键点是域对象应该最好地知道需要什么数据来保存和恢复它,以及应该以什么顺序恢复数据(这通常很重要)。对象不应该知道的是存储它的具体机制:关系数据库,平面文件,二进制blob等。

答案 4 :(得分:1)

通过Jove,我想我已经拥有了!

感谢大家的投入,但我将不得不回答我自己的问题。我花了一个小时左右的时间挖掘PoEAA catalog,考虑OO原则,并深入思考C#语言和.NET框架。

我提出的答案, one 需要我无法使用构造函数正确解决,最终最终与构造函数本身无关。这是lazy loading

基本上,如果没有在域类中实现延迟加载(持久性无知和灵活性的主要禁忌),没有子类化域类就没有办法做到这一点。这个子类化是NHibernate需要虚拟属性的原因。

我仍然认为使用构造函数而不是反射或其他方法来填充父类的字段会更好(至少对于非集合...延迟加载确实有它的位置),但我肯定看到no-arg构造函数所在的位置。

答案 5 :(得分:0)

这取决于你认为不应该改变的值。自动增量,计算列等等都是很好的选择。

肯定可能,我使用我编写的ORM,如果您尝试设置只读属性的值,它会抛出异常。

<强>更新

记住构造函数也用于持久化数据。让对象接受构造函数中的PK是一种常见的模式,它会自动获取该记录。

答案 6 :(得分:0)

在一般情况下,OR映射器不能仅使用构造函数。假设一个对象由传递给构造函数的值初始化,然后通过方法调用修改状态。在这种情况下,对象可能会持久化,并且状态不是有效的初始状态,因此被构造函数拒绝。

所以有可能是这样的OR映射器,但它肯定会有局限性。如给出的示例所示,我不会考虑将OR映射器绕过对象封装作为错误的设计,但在某些情况下是必需的。

答案 7 :(得分:0)

先生。 Skeet不久前提出了一种“建设者”模式。我把它变成了POCO课程中的公共课。它具有与POCO相同的属性,但都是读/写。 POCO只在必要时是readonly,但是PRIVATE SET。这样,构建器可以在POCO实例化时设置适当的,并且没有构造函数的时髦args。那里有'popcicle immutability'。