我想在EF Code First中存储两个类。建筑物有维护者,以及在那里工作的人员名单。 mainainer不需要在建筑物内工作。
在我第一次尝试时,我只是
public class Person
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
和
public class Building
{
public virtual Guid Id { get; set; }
public virtual List<Person> WorksHere { get; set; }
public virtual Person MaintainedBy { get; set; }
public virtual String Address { get; set; }
}
这给了我两个带有基本属性的表,一个关于People的Building_Id和一个关于建筑物的MaintainedBy_Id。
当我运行测试程序时
using (TestContext tc = new TestContext())
{
Person m1 = tc.persons.Create();
m1.Name = "maintainB1";
m1.Id = Guid.NewGuid();
Person m2 = tc.persons.Create();
m2.Name = "maintainB2";
m2.Id = Guid.NewGuid();
Building b1 = tc.buildings.Create();
b1.Address = "building1";
b1.Id = Guid.NewGuid();
tc.buildings.Add(b1);
Building b2 = tc.buildings.Create();
b2.Address = "building1";
b2.Id = Guid.NewGuid();
tc.buildings.Add(b2);
b1.MaintainedBy = m1;
b2.MaintainedBy = m2;
if (b1.WorksHere == null) b1.WorksHere = new List<Person>();
if (b2.WorksHere == null) b2.WorksHere = new List<Person>();
b1.WorksHere.AddRange(new List<String>() { "e11", "e12", "e13" }.Select(s =>
{
Person p = new Person();
p.Id = Guid.NewGuid();
p.Name = s;
return p;
}));
b2.WorksHere.AddRange(new List<String>() { "e21", "e22", "e23" }.Select(s =>
{
Person p = new Person();
p.Id = Guid.NewGuid();
p.Name = s;
return p;
}));
b1.WorksHere.Add(m2);
b2.WorksHere.Add(m1);
tc.SaveChanges();
}
}
我得到一个异常:“保存不公开其关系的外键属性的实体时发生错误.EntityEntries属性将返回null,因为无法将单个实体标识为异常的来源。通过在实体类型中公开外键属性可以使保存变得更容易。有关详细信息,请参阅InnerException。“
带有不良情绪: “无法确定依赖操作的有效排序。由于外键约束,模型要求或存储生成的值,可能存在依赖关系。”
我不想在我的Poco上公开任何更多的属性,因为,它们是Poco的,我对这些属性毫无用处。如果我必须满足Code First模型生成,那么就必须这样做,但如果可能的话,我希望在映射类中远离我的Poco。
我该如何解决这个问题?
答案 0 :(得分:2)
因此,出现此问题的原因是您的实体之间存在循环关系,导致EF在尝试解析单SaveChanges
次调用中的所有插入时放弃,并引发您看到的异常。
要理解为什么它无法处理这种情况,让我们考虑一下在尝试保存实体时数据库中会发生什么。
使用您的代码,您可以通过在调用SaveChanges
之前注释掉最后一行来使其无误地运行,但是m1
人员不会在构建b2
时工作,所以这不是你想要的。
b1.WorksHere.Add(m2);
//b2.WorksHere.Add(m1); <-- When this is removed it works..
tc.SaveChanges();
但是,EF可以通过在数据库中创建以下插入来运行它:
m1
。将FK保留为buildings表为null,因为m1
无处可用。m1
维护b1
。m2
。使用b1
的ID作为FK,因为b1
是m2
的工作地点。b2
。使用m2
的ID作为FK,因为m2
维护b2
。现在,当您在m1
中加入使b2
工作的行时,很容易理解为什么它不起作用。
在上面的第一个插入中,EF无法将FK保留为null,因为您告诉它它需要指向一个建筑物,但该建筑尚未插入,因此它无法创建FK指向回到它。
当实体具有循环依赖性时,这始终是EF中的问题。当两者相互依赖时,无法在单个提交中创建插入。
问题的解决方案只是拨打SaveChanges
两次。如果您在m1
中b2
之前调用它,然后再次使用,那么您将获得正确的行为。
b1.WorksHere.Add(m2);
tc.SaveChanges(); <-- Create inserts. FK in m1 is null because he works nowhere yet.
b2.WorksHere.Add(m1);
tc.SaveChanges(); <-- Updates FK in m1 to point to b2.
实体框架中的未来支持
似乎这个问题将在EF的未来版本中得到解决。
从ORM中得到它应该能够处理插入父项,插入子项然后使用新创建的子ID更新父项是合理的。
您可以阅读有关它的更多信息,并投票赞成要在Microsoft Connect上实施的功能。
还有一些信息答案 1 :(得分:0)
尝试映射关系。在从DbContext派生的上下文中,重写OnModelCreating方法:
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Entity<Building>()
.HasMany(m => m.LivesHere)
.WithRequired()
.Map(n => n.MapKey("Home_Id"));
}
我也会尝试使用[Key]来表示实体中的主键,但我不确定你是否必须这样做。
像这样:
public class Building
{
[Key]
public virtual Guid Id { get; set; }
public virtual List<Person> WorksHere { get; set; }
public virtual Person MaintainedBy { get; set; }
public virtual String Address { get; set; }
}
我现在无法尝试,但我希望它有所帮助。