实体框架、Code First 建模和循环引用
我已经花了好几天的时间来尝试解决这个问题。在制作一个简单的项目来说明我的问题时,我偶然发现了一个可能的解决方案。所以,这是一个双重问题。
但首先,有一些背景信息:
我刚刚开始使用 Entity Framework 4.1 (EF) 和 Code First 为我的 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 专家,他同意这并没有什么特别之处。至少就我们所知。
但是当我运行代码时,我得到了这个异常:
System.Data.SqlServerCe.SqlCeException:引用关系将导致不允许的循环引用。 [ 约束名称 = Mother_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/circular-reference- test-app.zip,然后打开 TestSolution.sln。示例项目中的属性已被注释掉。取消注释 TestModels.cs 中的行以添加属性,从而导致循环引用异常。
注意:该解决方案正在创建并播种位于 c:\TestApp.sdf 的 SQL CE 数据库
2011 年 12 月更新: 我从未从技术上解决过这个问题,但我辞掉了工作,找到了另一份不需要使用微软技术的工作。这解决了我的问题:)
正如老地方的技术支持在解决问题时常常写的那样:“已经提供了解决方法或解决方案”。
I've spend several days now, trying to solve this problem. While making a simple project to exemplify my problem, I stumbled upon a possible solution. So, this is sort of a double question.
But first, a little background info:
I just started using Entity Framework 4.1 (EF) and Code First to create the models for my ASP.NET MVC project. I need some models similar to this:
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; }
}
}
And the 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; }
}
}
(Please excuse the lame example, that's what my Friday fried brain was able to come up with.)
A family can have several mothers and several fathers. And a child has a mother and a father. I checked with one of the .NET gurus at my work, who agreed that there is nothing extraordinary in this. At least as far as we can see.
But when I run the code, I get this Exception:
System.Data.SqlServerCe.SqlCeException: The referential relationship will result in a cyclical reference that is not allowed. [ Constraint name = Mother_Family ]
I do see the cycle: Family - Mother - Child - Father - Family
. But if I created the database tables myself (which I prefer not to, that's what I like about Code First) it would be a perfectly valid data structure, as far as I can tell.
So, my first question is: Why is this a problem when using code first? Is there a way to tell EF how to properly handle the cycle?
Then, as I write initially, while creating a simple project to exemplify my problem, I incidentally stumbled upon a possible solution. I simply forgot some of the properties when defining my models. For clarity in the following example, instead of removing them, I've commented out the parts of the models I forgot:
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; }
}
}
So, removing these SomethingID
reference properties seems to solve my problem. As you can see in the controller of the sample project I'm linking to in the end of this post, I'm still able to cycle all the way around and do stuff like mothers.First().Family.Fathers.First().Children.First().Mother.Family.Name
without any problems. But all tutorials and examples about EF and Code First modeling I've been looking at (e.g. this one by Scott Guthrie) include these properties, so it feels wrong not to use them.
And so, my second question is: Will there be any drawbacks and problems I haven't discovered yet doing this?
Download example project here: http://blackfin.cannedtuna.org/cyclical-reference-test-app.zip, and open TestSolution.sln. The properties are commented out in the example project. Uncomment the lines in TestModels.cs to add the properties, resulting in the cyclical reference exception.
NB: The solution is creating and seeding a SQL CE database located at c:\TestApp.sdf
Update, December 2011:
I never solved this problem technically, but I quit my job and found another job where I don't have to use Microsoft technologies. That sort of solved my problem :)
As the tech support at the old place used to write when fixing issues: "A workaround or solution has been provided".
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这是你应该仔细检查的事情。异常直接来自数据库而不是来自实体框架。手动创建的具有相同约束的表结构也可能是无效的。请记住,您的外键属性
Mother.FamilyID
、Father.FamilyID
、Child.MotherID
和Child.FatherID
不可为空,因此它们表示所需的关系,并且数据库中的相应列也不可为空。当您从模型类中删除所有这些属性时,您的关系突然变得可选,因为导航属性可以为
null
。现在这是另一个模型,因为数据库中的 FK 列可以为空!显然这是一个允许的模型。如果您希望在模型中仍然具有表示可选关系而不是必需关系的外键属性,您可以使用可以为 null 的类型:
public int? FamilyID { 获取;放; }
,公共 int? MotherID { 获取;放; }
等This is something you should double check. The exception comes directly from the database and not from Entity Framework. It's likely that also a table structure with the same constraints created by hand will be invalid. Keep in mind that your foreign key properties
Mother.FamilyID
,Father.FamilyID
,Child.MotherID
andChild.FatherID
are not nullable, so they represent required relationships and the corresponding columns in the database are also not nullable.When you remove all these properties from your model classes your relationships become suddenly optional because the navigation properties can be
null
. This is another model now since the FK columns in the DB can be nullable! Apparently this is an allowed model.If you want to have still foreign key properties in your model which represent optional instead of required relationship you can use nullable types:
public int? FamilyID { get; set; }
,public int? MotherID { get; set; }
, etc.这是一个已知问题,您并不是第一个遇到它的人。据我所知,他们正在即将推出的 WCF 版本中开发更好的解决方案,但目前根据我的经验,您最好创建代表要通过线路发送的数据的 DataContracts,从而更改数据结构删除循环引用。
我知道这很痛苦,但是还有其他好处,因为您很可能希望对客户端使用的结构进行其他更改,而不是让他们使用数据库中存在的对象
This is a known problem and you're not the first to bump into it. From what I've heard they are working on a better solution in the upcoming version of WCF, however for the time being from my experience you are much better off creating DataContracts that represent the data to be sent over the wire thereby changing the data structure to remove the cyclic reference.
I know it's a pain, but there are other benefits to be had in that you most likely will want to make other changes to structures that your clients consume anyway instead of letting them play with the objects as they exist in your db
我有很多同样的问题,但是我使用这个答案中的建议解决了它 Entity Framework Code First - 来自同一个表的两个外键,这比将键列的类型更改为可选更好。
I had much the same problem however I solved it using the advice in this answer Entity Framework Code First - two Foreign Keys from same table which works better than changing the type of the key columns to optional.