.NET ORM、不可变值对象、结构、默认构造函数和只读属性

Posted

技术标签:

【中文标题】.NET ORM、不可变值对象、结构、默认构造函数和只读属性【英文标题】:.NET ORMs, immutable value objects, structs, default constructors, and readonly properties 【发布时间】:2011-03-19 20:12:21 【问题描述】:

我刚刚开始使用 .NET ORM,甚至还没有在 Entity Framework 和 NHibernate 之间做出决定。但是在这两种情况下,我都遇到了一个问题,他们似乎希望我以各种方式损害我的域模型的完整性,尤其是在 C# 对象设计的更精细点上。这是有关该主题的几个问题之一。


我非常习惯于使用如下所示的模式对适当的属性强制执行不变性:

public class Foo

    private readonly string bar;
    public string Bar  return this.bar; 

    public Foo(string bar)
    
        this.bar = bar;
    

这似乎不受 NHibernate 或实体框架的支持。他们想要默认构造函数和public setter;甚至private setter(和默认构造函数)似乎也可以工作(有时?),因为 ORM 可以使用反射。

我想我可以通过使用private setter 和private 默认构造函数来解决这些问题。至少公共 API 没有受到损害。只是我正在修改我所有的类的实现以添加未使用的private 构造函数,并且必须相信future-Domenic,他理解我的设置器上的private 真的意味着“除了在构造函数中之外不要给我打电话。 "持久层正在泄漏到我的域对象设计中。

这似乎也没有必要——为什么 ORM 不知道使用非默认构造函数?也许他们可以,但我只是没有找到正确的博客文章来解释如何。

最后,在某些情况下,我的不可变值对象实际上很适合(不可变的)值类型,即structs。我的猜测是这是可能的,因为在数据库中,我的结构的字段将显示在存储父实体的同一行中。你能确认/拒绝吗? This blog post 看起来很有希望,因为它给出了肯定的答案,但是代码的数量(实际上是特定于所讨论的值类型)令人震惊。


令人沮丧的是,在阅读了诸如 Effective C# 之类的书籍或 Eric Lippert 之类的博客(它们为如何设计富有表现力和防弹的 C# 对象提供了很好的建议)之后,使用 ORM 的必要性是令人沮丧的。让我把很多知识扔出窗外。我希望这里的人能指出我错在哪里,无论是在我对他们能力的掌握中,还是在我对领域建模和 ORM 角色的思考中。

【问题讨论】:

您至少有三个问题可以归结为“我不喜欢 ORM,因为它们损害了我的设计。” ORM 是解决特定问题的工具。如果你不喜欢它们,为什么要使用它们?使用 ADO.NET 编写您自己的数据访问层。在键入样板代码几个月后,您可能会开始欣赏 ORM 引入的权衡取舍。 (几年前我做过类似的事情;我不会回去了。) 是的,当我开始深入研究 ORM 时,这些都是相关的问题。但我更愿意将它们归类为“帮助我了解 ORM 中的妥协”问题。我致力于使用 ORM,因为正如您所说的 ADO.NET 中的数据访问是疯狂的酱汁。并不是我不喜欢它们,而是我仍在学习它们并试图获得帮助,让自己处于正确的心态。 例如,如果我在阅读 EF 与 NHibernate 的比较时会想到“这会在我的域模型中引起更少的妥协?”,那么与与了解通常需要哪些妥协,而是根据特性、成熟度、能力等比较两个框架。 【参考方案1】:

正如 cmets 所指出的,当/如果您采用 ORM 时,您将不得不做出一些妥协。在我看来,要记住的是,生产力的收益远远超过了这些妥协的“成本”。

我确实想向您指出(因为您是 EF 新手),您可以通过customize the T4 templates 生成 EF 实体和上下文。我想如果你玩这个,你可以消除大部分你不喜欢的东西。

【讨论】:

【参考方案2】:

在使用过许多 ORM 之后,我感到很痛苦,使用接口、抽象和不可变对象在大多数 ORM 中都无法正常工作。正是出于这个原因以及我在大约 8 年前自己编写的其他原因,我使用自己的 ORM 发现了其中一些问题并找到了解决这些问题的方法。我会看一下 Micro ORM,看看你能找到什么,那里有很多,有丰富的功能集,而且很多包包更少。

我添加了一些新功能来处理我的一些痛苦。就像返回接口集合或其他抽象类型以及不可变类型的能力一样。

    string sql = "Select * from simpleEntities";
    ISqlQuery query = _factory.CreateSqlQuery(sql, nameof(AbstractFactoryTests.ReadImmutableEntityItems));
    IList<IImmutableEntity> items = loader.ObtainItemsImmutable<IImmutableEntity>(query);

或:

    IList<ISqlQuery> queries = new List<ISqlQuery>();

    // Note, no register types needed here because of returnType parameter

    ISqlQuery s1 = _factory.CreateSqlQuery("Select * from Employee where EmployeeType=1", "LoadAbstract.ParallelLoadItems1", typeof(Employee));
    queries.Add(s1);
    ISqlQuery s2 = _factory.CreateSqlQuery("Select * from Employee where EmployeeType=2", "LoadAbstract.ParallelLoadItems2", typeof(Manager));
    queries.Add(s2);
    ISqlQuery s3 = _factory.CreateSqlQuery("Select * from Employee where EmployeeType=3", "LoadAbstract.ParallelLoadItems3", typeof(DistrictManager));
    queries.Add(s3);

     IList<IEmployee> data = loader.ParallelLoadAbstractItems<IEmployee>(_factory, queries);

【讨论】:

以上是关于.NET ORM、不可变值对象、结构、默认构造函数和只读属性的主要内容,如果未能解决你的问题,请参考以下文章

Python坑:不要使用可变对象作为函数默认值生成器不保留迭代过后的结果嵌套列表创建==和is的区

C# .Net经典面试题目及答案

33条C#.Net经典面试题目及答案

python函数使用参数技巧笔记

不可变类/对象、私有构造函数、工厂方法

JSON.net:如何在不使用默认构造函数的情况下反序列化?