在 EF 中处理循环引用的干净方法?

Posted

技术标签:

【中文标题】在 EF 中处理循环引用的干净方法?【英文标题】:Clean way to deal with circular references in EF? 【发布时间】:2011-12-27 09:15:05 【问题描述】:

假设我有这个表结构:

Client
-----------
ClientId                     int            not null    (identity)
CurrentDemographicId         int            null        (FK to ClientDemographic)
OtherClientFields            varchar(100)   null


ClientDemographic
------------------
ClientDemographicId          int            not null    (identity)
ClientId                     int            not null    (FK to Client)
OtherClientDemographicFields varchar(100)   null

这个想法是客户端(在 EF 中)将有一个 ClientDemographics 列表和一个 CurrentDemographic 属性。

问题是当我设置对象结构并尝试保存它时,我得到了这个错误:

无法确定相关操作的有效顺序。由于外键约束、模型要求或存储生成的值,可能存在依赖关系

这个错误是有道理的。我的表格设置中有一个循环引用。它不知道先插入哪个实体(因为它同时需要两个表中的 Id)。

所以,我拼凑了一个如下所示的解决方案:

// Save off the unchanged ClientDemograpic
ClientDemographic originalClientDemographic = client.CurrentClientDemographic;

// Merge the contract into the client object
Mapper.Map(contract, client);

// If this is a new client then add as new to the list.
if (client.ClientId == 0)

    dataAccess.Add(client);


// Restore the original ClientDemographic so that EF will not choke
// on the circular reference.
ClientDemographic newClientDemographic = null;
if (client.CurrentClientDemographic != originalClientDemographic)

    newCurrentClientDemographic = client.CurrentClientDemographic;
    client.CurrentClientDemographic = originalClientDemographic;


// save our changes to the db.
dataAccess.SaveChanges();

// Restore updates to ClientDemographics and save (if needed)
if (newClientDemographic != null)

    client.CurrentClientDemographic = newCurrentClientDemographic;
    dataAccess.SaveChanges();

但是将引用更改回以前的值,保存,然后再次设置它以便我可以再次保存感觉就像一个 hack。

是否有更简洁的方法来处理 EF 中的循环引用?

【问题讨论】:

【参考方案1】:

我会说答案是:“不是真的”。处理循环引用的唯一干净方法是再次查看设计并将其删除。

在这种情况下——从领域驱动设计的角度来看——我会说Client 是聚合的根,ClientDemographic 是一个值对象; ClientDemographics 由其“其他客户人口统计字段”的值定义。因此,您可以从ClientDemographic 中删除ClientId,从而防止而不是解决问题。

也就是说,如果您确定了这种结构,那么不幸的是,我认为在 EF 中没有一种巧妙的方法来处理它,不。

编辑:要给Client 多个ClientDemographics 以及一个CurrentClientDemographic 属性,您可以采用其他方式;从Client 中删除CurrentClientDemographicId,并将IsCurrent 二进制字段添加到ClientDemographic。然后,EF 会为您提供一个 ClientDemographics 集合属性,您可以自己将以下内容添加到新的部分类中:

public partial class Client

    public ClientDemographic CurrentDemogaphic
    
        get  return ClientDemographics.First(cd => cd.IsCurrent); 
    

【讨论】:

嗯,我想更改设计,但客户可以在数据库中拥有多个人口统计数据。尽管 Client 是根,但有时我需要一个客户端的所有 ClientDemographics(有时只需要当前的)。 感谢您的回答和为我寻找解决方案的努力。不幸的是,我有其他依赖项不允许我采用此解决方案。简而言之,我正在使用 WCF 数据服务 (OData) 来发布我的数据。 WCF 数据服务只能发布 EF 模型中的实际内容。不发布部分课程和此类内容。 (在我看来,这是一个重要的缺失功能,但 OData 的好处超过了这个问题。) 不客气 :) 正如我在对this question 的回答中提到的,我建议不要将域模型对象与 WCF 混合。相反,我会创建一个单独的数据传输对象层,专门为您的 WCF 需求量身定制,并在它们和您的 EF 对象之间进行映射。你的 DTO 可以有循环依赖,EF 不需要知道它:)【参考方案2】:

避免此错误的简单方法是先创建主对象 SaveChanges,然后在再次调用 SaveChanges 之前创建依赖对象。

在这种情况下,首先创建 Client,SaveChanges,然后创建 ClientDemographic 对象,将其添加到集合中并将其设置为 CurrentDemographic,然后再次 SaveChanges。

【讨论】:

这不是一个简洁的解决方案。 UoW 中的SaveChanges() 为数据库事务发出提交。在第一个SaveChanges() 之后,执行第二个失败可能会破坏数据库一致性,或者至少是域逻辑一致性。 这不是一个整洁的解决方案,但如上所述,没有一个整洁的解决方案。您的选择是使用我的方法并手动处理回滚,或者更改您的模型。 @MassoodKhaari 在SaveChanges 期间发生故障时,事务可以帮助保持数据库一致性? @ProfK @Nathan Phillips EF DbContext 所基于的工作单元模式的一项职责是确保其中的所有数据更改都被提交或完全丢弃。如果SaveChanges 失败,则不会将任何更改提交给数据库,因此不会危及数据库的一致性。将业务事务拆分为两个单独的 UoW(两个 SaveChanges)是不明智的,因为您可能保存了一半的事务,这可能会导致业务损失并需要手动更正。 @MassoodKhaari 我同意你所说的,但如果数据库不是你要更改的,并且你仍然不想提交或回滚,我建议包装链接和多次调用以保存事务范围的变化。

以上是关于在 EF 中处理循环引用的干净方法?的主要内容,如果未能解决你的问题,请参考以下文章

解决MVC Json序列化的循环引用问题/EF Json序列化循引用问题---Newtonsoft.Json

解决MVC Json序列化的循环引用问题/EF Json序列化循引用问题---Newtonsoft.Json

EF 序列化实体为Json时的循环引用问题(不用自己写实体,不用匿名类型,不用EF的上下文属性)

EF中Json序列化对象时检测到循环引用的解决办法

通过T4模板解决EF模型序列号的循环引用问题

如何删除实体框架中的循环引用?