从数据库重构域对象:身份问题

时间:2009-05-11 10:20:19

标签: c# orm domain-driven-design

我们正在使用Linq to SQL来读取和编写我们的域对象到SQL Server数据库。

我们正在公开许多服务(通过WCF)来执行各种操作。实际上,这些操作的实现包括三个步骤:从数据库重构必要的域对象;对域对象执行操作;将(现在已更改的)域对象保留回数据库。

问题在于,有时,同一个实体对象有两个或更多个实例,这会在将对象保存回db时导致不一致。一个小小的例子:

public void Move(string sourceLocationid, destinationLocationId, itemId);

应该将具有给定id的项目从源位置移动到目标位置(实际服务更复杂,通常涉及许多位置,项目等)。现在,可能源和目标位置id都是相同的 - 一个天真的实现只会重构实体对象的两个实例,这将导致问题。

现在通过手动检查来解决这个问题,即我们重新构建第一个位置,检查第二个位置的id是否与之不同,如果是,则重新确定第二个位置,依此类推。这显然是困难且容易出错。

无论如何,我真的很惊讶在域驱动设计中似乎没有这个“标准”解决方案。特别是,存储库或工厂似乎没有解决这个问题(除非他们维护自己的缓存,然后需要更新等)。

我的想法是为每个操作创建一个DomainContext对象,该对象跟踪和缓存该特定方法中使用的域对象。这样的对象不是重新构建和保存单个域对象,而是重构并保存为整体(可能使用存储库),并且它可以充当该特定操作中使用的域对象的缓存。

无论如何,这似乎是一个常见的问题,所以这通常是如何处理的?您如何看待上述想法?

2 个答案:

答案 0 :(得分:1)

许多ORM使用两个概念,如果我了解您,请解决您的问题。第一个也是最相关的是Context,它负责确保只有一个对象代表一个实体(数据库表行,在简单的情况下)无论多少次或数据库请求的方式。第二个是工作单位;这可确保对一组实体的数据库更新全部成功或全部失败。

这两个都是由我最熟悉的ORM(LLBLGen Pro)实现的,但我相信NHibernate和其他人也实现了这些概念。

答案 1 :(得分:1)

Linq-To-Sql中的DataContext支持开箱即用的Identity Map概念,应该缓存您检索的对象。只有当为每个GetById()操作使用相同的DataContext时,对象才会有所不同。

Linq to Sql对象在DataContext的生命周期之外并不真正有效。你可能会发现Rick Strahl的Linq to SQL DataContext Lifetime Management背景很好。

此外,ORM不对域中的逻辑负责。它不会禁止您的示例Move操作。域名决定了这意味着什么。它会忽略它吗?还是错误?这是您的域逻辑,需要在您正在创建的服务边界上实现。

但是,Linq-To-Sql确实知道对象何时发生变化,而且从我所看到的情况来看,如果您重新分配相同的值,它将不会记录更改。例如如果Item.LocationID = 12,则再次将locationID设置为12将不会在调用SubmitChanges()时触发更新。

根据给出的示例,如果源和目标相同,我很想在没有加载对象的情况下提前返回。

public void Move(string sourceLocationId, destinationLocationId, itemId)
{
    if( sourceLocationId == destinationLocationId )
        return;

    using( DataContext ctx = new DataContext() )
    {
       Item item = ctx.Items.First( o => o.ItemID == itemId );
       Location destination = 
          ctx.Locations.First( o => o.LocationID == destinationLocationID );
       item.Location = destination;

       ctx.SubmitChanges();
    }
}

另一个可能适用或可能不适用的小问题是,您应该尽可能使界面变得粗糙。例如如果您通常要一次执行10个移动操作,最好一次调用1个服务方法来执行所有10个操作,而不是一次执行1个操作。参考:chunky vs chatty