C# .NET 中的数据映射器问题
Posted
技术标签:
【中文标题】C# .NET 中的数据映射器问题【英文标题】:Data Mapper question in C# .NET 【发布时间】:2011-06-27 17:40:31 【问题描述】:似乎在 DDD、存储库、数据映射器等周围有很多“噪音”……而很少有“真实世界”实现代码向像我这样的新手展示什么是“好”,什么是“好” “坏”。
我刚刚读完Architecting Applications for the Enterprise 这本书,让我大开眼界。我目前在工作项目中使用 Active Record 模式,并且一直在努力将其更改为使用域模型。我使用了本书中的大量架构示例,以及本书随附的 Northwind Starter Kit 代码下载。
一切都很顺利,但现在我遇到了我的第一个“现实世界”数据映射器问题……也就是说,我的映射器不仅仅负责获取一个实体对象并将其持久化到数据库中,我现在有一个 Entity 对象,该对象有一个 IList 集合,该集合也需要映射。
主要的Entity对象是Expert,代码如下:
public class Expert
public int ID get; set;
public string FirstName get; set;
public string LastName get; set;
public virtual IList<Case> Cases get; protected set;
这里是来自 Expert 的集合的实现,即 Case 对象:
public class Case
public int ID get; set;
public string Name get; set;
没有比这更简单的了。
现在,每个实体项都有一个 DataMapper,但我的问题是,当我在 ExpertDataMapper 代码中映射 Case 集合时,什么被认为是“正确”的方法?
在本书中,ExpertDataMapper 中嵌入了实际的 SQL 代码,该代码调用了 ADO.NET 代码,该代码获取 IList 集合中的所有 Case 项目,并为每个项目调用一次该代码。这是一些伪代码:
public virtual void Create(Expert expert)
// Insert expert in the Expert table
SqlHelper.ExecuteNonQuery(ProviderHelper.ConnectionString, CommandType.Text,
"SQL TO INSERT EXPERT", this.BuildParamsFromEntity(expert));
// Insert individual Case items in the Case table
foreach (Case c in expert.Cases)
SqlHelper.ExecuteNonQuery(ProviderHelper.ConnectionString, CommandType.Text,
"SQL TO INSERT CASE", this.BuildParamsFromEntity(order));
所以这有两点不对劲:
-
为什么 ExpertDataMapper 有 SQL 调用(或存储的 proc 调用)来插入嵌入自身的 Case?对我来说,这打破了封装的想法...... ExpertDataMapper 不应该知道如何将 Case 插入数据库,这就是 CaseDataMapper 的工作。
我认为 ExpertDatMapper 应该将插入 Case 的任务委托给 CaseDataMapper...但我不知道一个 DataMapper 实例化另一个 DataMapper 是“正确”还是“错误”。这被认为是 DataMappers 的常规问题吗?我找不到关于这个问题的指导,我认为这是相当普遍的。
-
如果我有 100 个案例与此专家相关联,那就是创建了 100 个案例对象,以及针对数据库的 100 个插入语句?这件事让我感觉“不对”,违背了我更好的判断。现在,如果我有幸能够使用 SQL Server 2008,我可以使用表值参数,它不会那么糟糕,但我们现在是 2005 年。
所以,当谈到 DataMappers 时,我还没有看到在实体对象的 DataMaper 上使用简单的集合关联进行简单创建、更新等的任何具体实现。我正在阅读的书没有支持它的代码(而且那里的代码对我来说看起来很可疑)。
我已经阅读并拥有 Martin Fowler 的 P of EAA 书,所以请不要让我看那里。我也知道我可以使用的 ORM 工具,所以我不需要自己负责实现 DAL。我玩过 EF 4.0,我喜欢它。但是对于这个项目,我没有使用 ORM 工具的选项。
大多数书籍/示例似乎都停留在相同的基本前提上,即只要我的 Entity 对象与通过 DataMapper 持久化到的表之间存在一对一关联,世界就是玫瑰色的使用领域模型方法...
如果我的所有实体都是一对一的,我还不如只使用 Active Record 并完成它。
有任何指导、建议和见解吗?抱歉,这是一篇很长的帖子,但我在这里阅读了其他类似问题的帖子,但没有关于如何处理此处提出的问题的真正具体答案或建议。
【问题讨论】:
【参考方案1】:从“SOLID”的角度来看,第一点是正确的。与其处理持久性本身,不如实现 ExpertDataMapper 使用的 CaseDataMapper 更易于维护且冗余更少。这可能有几个原因,主要与简单性有关。如果你有一个单独的类应该在同一个事务中工作,你必须传递事务。这本身并不可怕,但它引入了更多关于如何使实现架构独立的问题。如果你只是传递事务,你会耦合到 vanilla ADO.NET,并且以后不能升级到像 MSEF、NHibernate、Linq2SQL 等这样的 ORM。所以你需要一个 UnitOfWork 模式,它允许你隐藏实际Transaction 或 Session 或 DataContext 或存储库中的任何内容。到现在为止,这个相对简单的代码 sn-p 现在是两个完整的类定义,内容很多。
为了说明一个专家的目的,为了避免所有这些,他们只是将用于保存案例的代码放在专家中。这基本上假设 Case 将始终被引用为 Expert 的子项,因此将功能分割成可以重用的东西是不值得的。吻。这个假设可能是正确的;如果不是,则留给读者作为练习,将该逻辑重构为帮助程序。
关于第二点,您也是正确的,而且确实没有办法绕过它。从 SQL Server 尚不知道的数据创建的每一行都必须一次插入一行。您可能以某种方式能够设置批量插入,但我可以保证在您进入数千条记录之前,这比它的价值更麻烦。您使用的任何 ORM 都会产生相同的 SQL。
关于不使用 ORM:对于足够复杂的域/数据模型,如果您不使用预制 ORM,如果您希望它符合 SOLID 等设计方法,您最终将自行开发。
【讨论】:
基思,感谢您的回答。很高兴知道上述这些项目是领域模型设计的常见挑战。在我看来,数据映射器确实是最难的部分。 基思,这是另一个踢球者。假设专家实体有一个方法 AddCase(Case c)。它允许用户将案例添加到专家对象。现在假设一个 Case 对象是新的,它是用一个空白构造函数创建的……所以它本质上是一个空对象。现在假设另一个 Case 对象是完全填充的(现有)Case 对象。当您点击数据映射层并且 ExpertDatMapper 正在遍历 Case 集合时,数据映射器不知道给定的 Case 对象是新的还是现有的。这似乎是另一个争论点。有什么想法/建议吗? 在这种情况下,您需要一个 CaseID。这将是数据库中的一个自动递增的标识列,从 1 开始,不会改变(将其作为表的 PK 是个好主意)。因此,从数据库中提取到 Case 对象的任何记录都将具有非零 ID。任何“新”对象都将为零。然后映射器知道是插入还是更新。【参考方案2】:这真的取决于个人喜好和意见。一些开发人员会说您显示的代码没有任何问题。其他人会说每个类只负责自己的状态和属性。其他人也会说其他东西。你应该使用适合你的东西,即:你觉得用起来舒服的东西。就个人而言,我更喜欢使用 EF Code First,它允许我轻松创建模型类和数据库上下文类。然后我简单地使用存储库模式和一个负责提交事务的 UnitOfWork 类来抽象它。此外,依赖注入模式是松散耦合类的好方法。我的类紧密耦合存在重大问题,如果不使用 IoC 容器,我无法解决这些问题。
This article 是迄今为止我所见过的关于该主题的最佳资源。作者在创建一个完全可用且最重要的可扩展框架以供使用方面做得非常出色。
祝你好运:)
【讨论】:
卡西姆,感谢您的文章。我仍在努力解决它。似乎 EF 4.0 终于“长大”了,有了对真正 POCO 的支持,就有了实现层间真正分离的能力。同样,我是“自己动手”,所以我不能为此使用 EF 4.0 或任何其他 ORM。但是,看看该示例中的代码是如何实现的确实很有帮助。 Kassem 不是在玩,这是一篇非常好的文章,包含大量有用的代码示例。以上是关于C# .NET 中的数据映射器问题的主要内容,如果未能解决你的问题,请参考以下文章