C# .NET 中的数据映射器问题

发布于 2024-10-17 12:23:23 字数 2662 浏览 0 评论 0原文

围绕 DDD、存储库、数据映射器等似乎存在很多“噪音”......并且很少有“现实世界”实现代码来向像我这样的新手展示什么是“好”,什么是“坏” 。

我刚刚读完为企业构建应用程序这本书,它让我大开眼界。我目前在工作项目中使用活动记录模式,并一直致力于将其更改为使用域模型。我在书中使用了很多架构示例,以及本书随附的 Northwind Starter Kit 代码下载。

一切都进展顺利,但现在我遇到了我的第一个“现实世界”数据映射器问题...也就是说,我的映射器不再只负责获取一个实体对象并将其保存到数据库,我现在有了一个实体具有 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));
          }
    }

因此,我立即想到了两件事:

  1. 为什么 ExpertDataMapper 有 SQL 调用(或存储过程调用)来插入嵌入在其自身中的 Case?对我来说,这打破了封装的想法...... ExpertDataMapper 应该对如何将 Case 插入数据库一无所知,这是 CaseDataMapper 的工作。

我认为 ExpertDatMapper 应该将插入 Case 的任务委托给 CaseDataMapper...但我不知道一个 DataMapper 实例化另一个 DataMapper 是“正确”还是“错误”。这被认为是 DataMappers 的常见问题吗?我找不到关于这个问题的指导,我认为这个问题相当普遍。

  1. 如果我有 100 个与该 Expert 关联的案例,那么是否创建了 100 个案例对象,以及针对数据库的 100 个插入语句?我觉得这件事有些“不对劲”,并且违背了我更好的判断。现在,如果我足够幸运能够使用 SQL Server 2008,我就可以使用表值参数,而且情况不会那么糟糕,但我们现在是 2005 年。

因此,当谈到 DataMappers 时,我还没有看到任何在实体对象的 DataMapper 上使用简单的集合关联进行简单创建、更新等的具体实现。我正在读的书没有代码支持它(并且那里的代码对我来说看起来很可疑)。

我读过并拥有 Martin Fowler 的 P of EAA 书,所以请不要让我看那里。我还了解我可以使用的 ORM 工具,因此我不需要自己负责实现 DAL。我玩过 EF 4.0,我喜欢它。但对于这个项目,我无法选择使用 ORM 工具。

大多数书籍/示例似乎都停留在相同的基本前提上,即只要我的实体对象和它通过 DataMapper 持久保存的表之间存在一对一的关联,世界就只是使用域的玫瑰色模型方法...

如果我的所有实体都是一对一的,我不妨只使用 Active Record 并完成它。

有什么指导、建议、见解吗?抱歉,这是一篇很长的文章,但我在这里阅读了其他有类似问题的文章,并且没有关于如何处理此处提出的问题的真正具体答案或建议。

There seems to be a lot of "noise" going on around DDD, repositories, data mappers, etc... and very little "real world" implementation code to show newbies like myself what is "good", and what is "bad".

I've just finished reading the book Architecting Applications for the Enterprise, and it's been a huge eye-opener. I currently use the Active Record pattern on a project at work, and have been working on changing it to use Domain Model. I've used a lot of the examples of architecture in the book, as well as the Northwind Starter Kit code download that is a companion to the book.

Everything has been going great, but now I've hit my first "real world" data mapper problem... aka, instead of my mapper just being responsible for taking one Entity object and persisting it to the database, I now have an Entity object that has an IList<> collection that also needs to be mapped.

The main Entity object is Expert, here is the code:

   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; }
   }

Here is the implementation of the collection from Expert, the Case object:

   public class Case
   {
          public int ID { get; set; }
          public string Name { get; set; }
   }

Can't get much simpler than that.

Now, I have a DataMapper for each Entity Item, but my question is, when I go to map the Case collection in my ExpertDataMapper code, what is considered the "right" way to do that?

In the book, there is actual SQL code that is embedded in the ExpertDataMapper that makes a call out to ADO.NET code that takes all Case items in the IList collection and calls that code once per item. Here is some pseudo-code:

   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));
          }
    }

So two things wrong with this jump out at me immediately:

  1. Why does the ExpertDataMapper have the SQL call (or stored proc call) to insert a Case embedded in itself? To me, this breaks the idea of encapsulation... the ExpertDataMapper should know nothing about how a Case gets inserted into the database, that is the job of the CaseDataMapper.

I would assume the ExpertDatMapper should DELEGATE the task of inserting a Case to the CaseDataMapper... but I don't know if it's "right" or "wrong" for one DataMapper to instantiate another. Is that considered a regular problem with DataMappers? I can find no guidance on this problem which I assume is fairly common.

  1. If I have 100 Cases associated with this Expert, that's 100 Case objects created, and 100 insert statements against the database? Something about this feels "wrong" to me, and goes against my better judgment. Now, if I were lucky enough to be able to use SQL Server 2008, I could use Table Valued Parameters, and it would not be as bad, but we're on 2005 right now.

So, when it comes to DataMappers, I have not seen any concrete implementations of a simple create, update, etc.. on an entity object's DataMaper with a simple collection association. The book I'm reading has no code to support it (and the code that is there looks suspect to me).

I have read, and own Martin Fowler's P of EAA book, so please don't tell me to look there. I am also aware of the ORM tools that are available to me, so I don't need to take care of implementing a DAL myself. I've played with EF 4.0, and I like it. But for this project, I don't have the option of using an ORM tool.

Most books/examples seem to stop at the same basic premise that as long as I have a one-to-one association between my Entity object and the table it gets persisted to via the DataMapper, the world is just rose-colored using a Domain Model approach...

If all my entities were one-to-one, I might as well just use Active Record and be done with it.

Any guidance, suggestions, insight? Sorry, It's a long post, but I've read other posts here with similar problems, and no really concrete answers or suggestions of how to deal with the problem presented here.

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

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

发布评论

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

评论(2

也只是曾经 2024-10-24 12:23:24

这实际上取决于个人喜好和意见。一些开发人员会说您所显示的代码没有任何问题。其他人会说每个类只负责它自己的状态和属性。其他人也会说其他的话。您应该使用适合您的东西,即:您觉得工作起来很舒服的东西。就我个人而言,我更喜欢使用 EF Code First,它允许我轻松创建模型类和数据库上下文类。然后,我简单地使用存储库模式和负责提交事务的 UnitOfWork 类进行抽象。此外,依赖注入模式是松散类耦合的好方法。我的类紧密耦合时遇到了一些重大问题,如果不使用 IoC 容器就无法解决这些问题。

本文是迄今为止我所见过的关于该主题的最佳资源。作者在创建一个完全可用且最重要的可扩展框架方面做得非常出色。

祝你好运 :)

This really depends on personal preference and opinions. Some developers would say there's nothing wrong with the code you've shown. Others would say each class is only responsible of its own state and properties. Others would say other stuff as well. You should use what's right for you, i.e: what you feel comfortable working with. Personally, I prefer using EF Code First which allows me to easily create my model classes and my database context class. Then I simply abstract that using the repository pattern and a UnitOfWork class that takes care of committing the transactions. Moreover, the dependency injection pattern is a great way to loose couple your classes. I had major issues with my classes being tightly coupled which I couldn't resolve without using an IoC container.

This article is by far the best resource on the subject I've seen. The author does a great job in creating a fully working and most importantly scalable framework to work with.

Good luck :)

挽袖吟 2024-10-24 12:23:23

对于第一点,从“SOLID”的角度来看,您是正确的。实现 ExpertDataMapper 使用的 CaseDataMapper 会更易于维护且冗余更少,而不是处理持久性本身。之所以没有这样做,可能有几个原因,其中大部分与简单性有关。如果您有一个单独的类应该在同一事务中工作,则必须传递该事务。这本身并不可怕,但它引入了更多关于如何使实现架构独立的问题。如果您只是传递事务,那么您将耦合到普通的 ADO.NET,并且以后无法升级到 ORM,如 MSEF、NHibernate、Linq2SQL 等。因此您需要一个 UnitOfWork 模式,它允许您隐藏实际的事务或会话或数据上下文或存储库中的任何内容。到目前为止,这个相对简单的代码片段现在是两个包含大量内容的完整类定义。

为了避免这一切,为了说明一个 Expert,他们只是将保存案例的代码放在 Expert 中。这基本上假设案例将始终被引用为专家的子项,因此将功能分割成可以重用的东西是不值得的。吻。这个假设可能是正确的;如果没有,则将其作为练习留给读者将该逻辑重构为助手。

关于第二点,你也是对的,确实没有办法。从 SQL Server 尚不知道的数据创建的每一行都必须一次插入一行。您可能可以以某种方式设置批量插入,但我可以保证,在您进入数千条记录之前,这比它的价值更麻烦。您使用的任何 ORM 都会产生相同的 SQL。

关于不使用 ORM:对于足够复杂的域/数据模型,如果您不使用预制 ORM,那么如果您希望它符合 SOLID 等设计方法,您最终将自行推出。

On the first point, you are correct, from a "SOLID" perspective. Instead of handling the persistence itself, it would be more maintainable and less redundant to implement a CaseDataMapper that is used by the ExpertDataMapper. That was probably not done for several reasons, mostly tied to simplicity. If you have a separate class that should do work within the same transaction, you have to pass the transaction around. This in itself is not terrible, but it introduces more questions about how to make the implementation architecture-independent. If you just pass the Transaction around, you're coupled to vanilla ADO.NET, and can't upgrade later to an ORM like MSEF, NHibernate, Linq2SQL, etc. So you need a UnitOfWork pattern, which allows you to hide the actual Transaction or Session or DataContext or whatever in the Repository. By now, this relatively simple code snippet is now two full class definitions with plenty of stuff.

To avoid all this for the purposes of illustrating one Expert, they just put the code to save Cases inside the Expert. This basically makes the assumption that a Case will always be referenced as a child of an Expert, and so it wouldn't be worth it to sever the functionality into something that can be reused. KISS. This assumption may be true; if not, it is left as an exercise to the reader to refactor that logic out into a helper.

On the second point, you are also correct, and there really is no way around it. Every row created from data the SQL Server can't yet know about must be inserted one row at a time. You MIGHT somehow be able to set up a bulk insert, but I can guarantee that's more trouble than its worth until you get into the thousands of records. Any ORM you'd use would result in the same SQL.

On not using an ORM: With a sufficiently complex domain/data model, if you do not use a prefab ORM, you will end up rolling your own if you want it to conform to design methodologies like SOLID.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文