实体框架、代码优先建模和循环引用
本文关键字:建模 循环 引用 框架 代码 实体 | 更新日期: 2023-09-27 18:04:53
我已经花了好几天时间来解决这个问题。在做一个简单的项目来说明我的问题时,我偶然发现了一个可能的解决方案。这是一个双重问题。
但首先,一点背景信息:
我刚刚开始使用实体框架4.1 (EF)和代码第一为我的ASP创建模型。. NET MVC项目。我需要一些类似的模型:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace TestApp.Models
{
public class Family
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Father> Fathers { get; set; }
public virtual ICollection<Mother> Mothers { get; set; }
}
public class Mother
{
public int ID { get; set; }
public string Name { get; set; }
public int FamilyID { get; set; }
public virtual ICollection<Child> Children { get; set; }
public virtual Family Family { get; set; }
}
public class Father
{
public int ID { get; set; }
public string Name { get; set; }
public int FamilyID { get; set; }
public virtual ICollection<Child> Children { get; set; }
public virtual Family Family { get; set; }
}
public class Child
{
public int ID { get; set; }
public string Name { get; set; }
public int MotherID { get; set; }
public int FatherID { get; set; }
public virtual Mother Mother { get; set; }
public virtual Father Father { get; set; }
}
}
和DbContext:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
namespace TestApp.Models
{
public class TestContext : DbContext
{
public DbSet<Family> Families { get; set; }
public DbSet<Mother> Mothers { get; set; }
public DbSet<Father> Fathers { get; set; }
public DbSet<Child> Children { get; set; }
}
}
(请原谅这个蹩脚的例子,这是我周五的大脑能想出的。)
一个家庭可以有几个母亲和几个父亲。一个孩子有一个母亲和一个父亲。我咨询了工作中的一位。net大师,他也认为这没有什么特别之处。至少在我们看来是这样。
但是当我运行代码时,我得到了这个Exception:
System.Data.SqlServerCe。SqlCeException:引用关系将导致不允许的循环引用。[约束名称= Mother_Family]
我确实看到了循环:Family - Mother - Child - Father - Family
。但是,如果我自己创建数据库表(我不喜欢这样做,这就是我喜欢Code First的原因),它将是一个完全有效的数据结构,据我所知。
所以,我的第一个问题是:为什么在使用代码时这是一个问题?有没有办法告诉EF如何正确处理这个周期?
然后,正如我最初写的那样,在创建一个简单的项目来举例说明我的问题时,我偶然发现了一个可能的解决方案。我只是在定义模型时忘记了一些属性。为了清楚起见,在下面的示例中,我没有删除它们,而是注释掉了我忘记的模型部分:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace TestApp.Models
{
public class Family
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Father> Fathers { get; set; }
public virtual ICollection<Mother> Mothers { get; set; }
}
public class Mother
{
public int ID { get; set; }
public string Name { get; set; }
// public int FamilyID { get; set; }
public virtual ICollection<Child> Children { get; set; }
public virtual Family Family { get; set; }
}
public class Father
{
public int ID { get; set; }
public string Name { get; set; }
// public int FamilyID { get; set; }
public virtual ICollection<Child> Children { get; set; }
public virtual Family Family { get; set; }
}
public class Child
{
public int ID { get; set; }
public string Name { get; set; }
// public int MotherID { get; set; }
// public int FatherID { get; set; }
public virtual Mother Mother { get; set; }
public virtual Father Father { get; set; }
}
}
所以,删除这些SomethingID
参考属性似乎解决了我的问题。正如你在我在这篇文章的最后所链接的示例项目的控制器中所看到的那样,我仍然能够循环所有的方式,并且没有任何问题地做像mothers.First().Family.Fathers.First().Children.First().Mother.Family.Name
这样的事情。但是我一直在看的关于EF和Code First建模的所有教程和示例(例如Scott Guthrie的这个)都包括这些属性,所以不使用它们感觉是错误的。
所以,我的第二个问题是:会有任何我还没有发现的缺点和问题吗?
在这里下载示例项目:http://blackfin.cannedtuna.org/cyclical-reference-test-app.zip,并打开TestSolution.sln。在示例项目中,这些属性被注释掉了。取消TestModels.cs中的注释以添加属性,这会导致循环引用异常。
NB:解决方案是创建并播种位于c:'TestApp.sdf的SQL CE数据库
2011年12月更新:我从来没有从技术上解决过这个问题,但我辞职了,找到了另一份不用使用微软技术的工作。这样就解决了我的问题:)
就像老地方的技术支持在修复问题时经常写的那样:"已经提供了一个工作区或解决方案"。
但是如果我自己创建数据库表(我不喜欢这样做),这就是我喜欢"代码优先"的原因),这将是完全有效的数据结构,据我所知
这是你应该仔细检查的。异常直接来自数据库,而不是来自实体框架。手工创建的具有相同约束的表结构也可能无效。请记住,您的外键属性Mother.FamilyID
、Father.FamilyID
、Child.MotherID
和Child.FatherID
不可为空,因此它们表示所需的关系,数据库中相应的列也不可为空。
当您从模型类中删除所有这些属性时,您的关系突然变得可选,因为导航属性可以是null
。这是另一个模型,因为数据库中的FK列可以为空!显然这是一个允许的模型。
如果你想在你的模型中仍然有外键属性,它代表可选的而不是必需的关系,你可以使用可空类型:public int? FamilyID { get; set; }
, public int? MotherID { get; set; }
等
这是一个已知的问题,你不是第一个碰到它。据我所知,他们正在为即将到来的WCF版本开发一个更好的解决方案,但就目前而言,根据我的经验,您最好创建代表要通过网络发送的数据的数据合约,从而更改数据结构以删除循环引用。
我知道这很痛苦,但是还有其他好处,因为你很可能会想要对客户端消费的结构进行其他更改,而不是让他们玩对象,因为它们存在于你的db
我有很多相同的问题,但是我使用这个答案中的建议解决了它实体框架代码优先-来自同一表的两个外键比将键列的类型更改为可选的效果更好。