无法确定 X 关系的主体端。多个添加的实体可能具有相同的主键
Unable to determine the principal end of the X relationship. Multiple added entities may have the same primary key
我知道还有其他人问过同样的问题,答案是处理引用而不是 ID。
在我的例子中,我有一个奇怪的行为 entity framework :它在一种情况下(父子)有效,但在另一种情况下(子孙)无效。
这是我的模型:
public class Parent
{
public int ID { get; set; }
public string Name { get; set; }
public List<Child> Children { get; set; } = new List<Child>();
}
public class Child
{
public int ID { get; set; }
public int ParentID { get; set; }
public string Name { get; set; }
public List<GrandChild> GrandChildren { get; set; } = new List<GrandChild>();
public Parent Parent { get; set; }
}
public class GrandChild
{
public int ID { get; set; }
public int ChildID { get; set; }
public String Name { get; set; }
public Child Child { get; set; }
}
这是我的映射:
public class ParentConfig : EntityTypeConfiguration<Parent>
{
public ParentConfig()
{
HasKey(e => e.ID);
Property(e => e.ID).HasColumnName("ID");
Property(e => e.Name).HasColumnName("Name");
HasMany(e => e.Children).WithRequired(c => c.Parent).HasForeignKey(c => c.ParentID);
ToTable("Parent");
}
}
public class ChildMap : EntityTypeConfiguration<Child>
{
public ChildMap()
{
HasKey(e => e.ID);
Property(e => e.ID).HasColumnName("ID");
Property(e => e.Name).HasColumnName("Name");
Property(e => e.ParentID).HasColumnName("ParentID");
HasMany(c => c.GrandChildren).WithRequired().HasForeignKey(c => c.ChildID);
HasRequired(e => e.Parent).WithMany().HasForeignKey(e => e.ParentID);
ToTable("Child");
}
}
public class GrandChildMap : EntityTypeConfiguration<GrandChild>
{
public GrandChildMap()
{
HasKey(e => e.ID);
Property(e => e.ID).HasColumnName("ID");
Property(e => e.ChildID).HasColumnName("ChildID");
Property(e => e.Name).HasColumnName("Name");
HasRequired(e => e.Child).WithMany().HasForeignKey(e => e.ChildID);
ToTable("GrandChild");
}
}
这是我的代码:
Parent parent = new Parent { Name = "Parent", };
Child child_1 = new Child { Name = "Child 1", Parent = parent };
Child child_2 = new Child { Name = "Child 2", Parent = parent };
GrandChild grandChild_1 = new GrandChild { Name = "GrandChild 1", Child = child_2 };
GrandChild grandChild_2 = new GrandChild { Name = "GrandChild 2", Child = child_2 };
context.Parents.Add(parent);
//no need to call SaveChanges
context.Children.Add(child_1);
context.Children.Add(child_2);
//SaveChanges() is needed here
context.GrandChildren.Add(grandChild_1);
context.GrandChildren.Add(grandChild_2);
context.SaveChanges();
此代码失败并显示消息
'Unable to determine the principal end of the Child_GrandChildren
relationship. Multiple added entities may have the same primary key'
但是如果我在添加子项后保存就可以工作,而我确实需要在添加父项后调用 SaveChanges()
。
编辑:如果我删除 属性 List<GrandChild> GrandChildren
它会起作用,但我真的需要它。
这是一个错误吗?
您应该将 Child 对象添加到 Parent 对象,而不是直接将它们添加到上下文中。 GrandChild 对象应添加到相应的 Child 对象。
In this case only the Parent object should be added to the context, in this way the entities will be created in the database in the correct order and the FKs will be resolved correctly.
您的代码应如下所示:
Parent parent = new Parent { Name = "Parent" };
Child child_1 = new Child { Name = "Child 1" };
parent.Children.Add(child_1);
Child child_2 = new Child { Name = "Child 2" };
parent.Children.Add(child_2);
GrandChild grandChild_1 = new GrandChild { Name = "GrandChild 1" };
GrandChild grandChild_2 = new GrandChild { Name = "GrandChild 2" };
child_2.GrandChildren.Add(grandChild_1);
child_2.GrandChildren.Add(grandChild_2);
context.Parents.Add(parent);
context.SaveChanges();
如果不需要引用添加的对象,则可以使用这种 fluent 风格的初始化代码:
Parent parent = new Parent
{
Name = "Parent"
Children = new List<Child>
{
new Child { Name = "Child 1" },
new Child
{
Name = "Child 2",
GrandChildren = new List<GrandChild>
{
new GrandChild { Name = "GrandChild 1" },
new GrandChild { Name = "GrandChild 2" }
}
}
}
};
context.Parents.Add(parent);
context.SaveChanges();
直接将子对象添加到上下文的主要问题是,当您像这样声明它们时,尤其是在逻辑流畅,运算顺序不能乱
您需要将 ChildMap
中的关系配置更改为:
HasMany(c => c.GrandChildren).WithRequired(gc=>gc.Child).HasForeignKey(c => c.ChildID);
// the second one is not necessary, you already configure that relationship in ParentConfig
//HasRequired(e => e.Parent).WithMany().HasForeignKey(e => e.ParentID);
在最后一种情况下您可能会遇到此异常,即当您具有递归或层次关系时。
When Recursive or Hierachical data needs to be saved with deep links, you should save the data in multiple steps. Do not be afraid of calling SaveChanges()
multiple times, the overheads are minimal, and for larger sets of data it will actually improve performance to save frequently rather than trying to save as a single action at the end of a process.
If you are concerned about ACID principals or handling failure and that is why you have avoided calling SaveChanges()
then you should wrap your logic in a transaction:
using (var trans = context.Database.BeginTransaction())
{
...
context.SaveChanges();
...
context.SaveChanges();
...
trans.Commit();
}
Its not even necessary to catch and handle exceptions if you use the IDisposable
using pattern.
- NOTE: Unlike pure SQL, EF does not support nested transactions. You can
在原始 post 的上下文中,如果 Parent 有 最爱 Child
[=62=,则可能会出现这种情况] 最爱 GrandChild
:
public class Parent
{
public int ID { get; set; }
public string Name { get; set; }
public List<Child> Children { get; set; } = new List<Child>();
public int? Favourite_ChildID { get; set; }
public Child FavouriteChild { get;set; }
public int? Favourite_GrandChildID { get; set; }
public GrandChild FavouriteGrandChild { get;set; }
}
在这种情况下,确保正确定义关系很重要并且您需要分两步保存数据。
// Parent Config
HasMany(p => p.Children)
.WithRequired(c => c.Parent)
.HasForeignKey(c => c.ParentID);
HasOptional(p => p.FavouriteChild)
.WithMany()
.HasForeignKey(p => p.Favourite_ChildID);
HasOptional(p => p.FavouriteGrandChild)
.WithMany()
.HasForeignKey(p => p.Favourite_GrandChildID);
// Child Config
HasMany(c => c.GrandChildren)
.WithRequired(gc => gc.Child)
.HasForeignKey(gc => gc.ChildID);
保存数据需要分两步完成。这个模型非常适合这个。一开始Parent
没有children,后来加了一个Child
,此时可能不是最喜欢的...
稍后添加另一个 Child
。不过,Parent
尚未决定谁是热门。后来最喜欢的 child 已被选中。
Lets overlook the real-world fact that having a child is what defines a Parent
vs a Person
...
您的数据逻辑需要尊重同样的思维过程。如果我们尝试将 child 定义为新 parent 的 faviourite 和 child我们有一个难题:要将 parent object 保存在数据库中,我们需要 Child 记录的 ID,但要保存 Child进入数据库,我们需要 Parent 记录的 ID...也许我们应该调用这些 Chicken
和 Egg
...
解决方法是先保存Primary关系,然后再回来保存任何递归关系链接:
Parent parent = new Parent
{
Name = "Parent"
Children = new List<Child>
{
new Child { Name = "Child 1" },
new Child
{
Name = "Child 2",
GrandChildren = new List<GrandChild>
{
new GrandChild { Name = "GrandChild 1" },
new GrandChild { Name = "GrandChild 2" }
}
}
}
};
// using transaction scope here to demonstrate how to manage multiple SaveChanges with a rollback
using (var trans = context.Database.BeginTransaction())
{
context.Parents.Add(parent);
context.SaveChanges();
parent.FavouriteChild = parent.Children.Single(child => child.Name == "Child 1");
parent.FavouriteGrandChild = parent.Children.SelectMany(child => child.GrandChildren).Single(gc => gc.Name == "GrandChild 2");
context.SaveChanges();
trans.Commit();
}
This selection logic won't work if your kids start naming their kids with names that their siblings have decided to use... but you get the point, we should be kind to our Parents as use unique names for out children :)
或者回到 OP 的原始脚本。只需在赋值之间调用 SaveChanges()
,那么所有这一切都可以避免,在这里使用事务范围可以解决 ACID 主体,如果引发异常或对 SaveChanges()
的调用之一失败,可能会违反 ACID 主体.
using (var trans = context.Database.BeginTransaction())
{
Parent parent = new Parent { Name = "Parent", };
context.Parents.Add(parent);
context.SaveChanges();
Child child_1 = new Child { Name = "Child 1", Parent = parent };
Child child_2 = new Child { Name = "Child 2", Parent = parent };
context.Children.Add(child_1);
context.Children.Add(child_2);
context.SaveChanges();
parent.FavouriteChild = child_1;
// we can save this next time, no Ids need to be forced.
GrandChild grandChild_1 = new GrandChild { Name = "GrandChild 1", Child = child_2 };
GrandChild grandChild_2 = new GrandChild { Name = "GrandChild 2", Child = child_2 };
context.GrandChildren.Add(grandChild_1);
context.GrandChildren.Add(grandChild_2);
context.SaveChanges();
// Now we can assign the faviourite GrandChild
parent.FavouriteGrandChild = grandChild_2;
context.SaveChanges();
// Actually commit the changes to the database
trans.Commit();
}
我知道还有其他人问过同样的问题,答案是处理引用而不是 ID。
在我的例子中,我有一个奇怪的行为 entity framework :它在一种情况下(父子)有效,但在另一种情况下(子孙)无效。
这是我的模型:
public class Parent
{
public int ID { get; set; }
public string Name { get; set; }
public List<Child> Children { get; set; } = new List<Child>();
}
public class Child
{
public int ID { get; set; }
public int ParentID { get; set; }
public string Name { get; set; }
public List<GrandChild> GrandChildren { get; set; } = new List<GrandChild>();
public Parent Parent { get; set; }
}
public class GrandChild
{
public int ID { get; set; }
public int ChildID { get; set; }
public String Name { get; set; }
public Child Child { get; set; }
}
这是我的映射:
public class ParentConfig : EntityTypeConfiguration<Parent>
{
public ParentConfig()
{
HasKey(e => e.ID);
Property(e => e.ID).HasColumnName("ID");
Property(e => e.Name).HasColumnName("Name");
HasMany(e => e.Children).WithRequired(c => c.Parent).HasForeignKey(c => c.ParentID);
ToTable("Parent");
}
}
public class ChildMap : EntityTypeConfiguration<Child>
{
public ChildMap()
{
HasKey(e => e.ID);
Property(e => e.ID).HasColumnName("ID");
Property(e => e.Name).HasColumnName("Name");
Property(e => e.ParentID).HasColumnName("ParentID");
HasMany(c => c.GrandChildren).WithRequired().HasForeignKey(c => c.ChildID);
HasRequired(e => e.Parent).WithMany().HasForeignKey(e => e.ParentID);
ToTable("Child");
}
}
public class GrandChildMap : EntityTypeConfiguration<GrandChild>
{
public GrandChildMap()
{
HasKey(e => e.ID);
Property(e => e.ID).HasColumnName("ID");
Property(e => e.ChildID).HasColumnName("ChildID");
Property(e => e.Name).HasColumnName("Name");
HasRequired(e => e.Child).WithMany().HasForeignKey(e => e.ChildID);
ToTable("GrandChild");
}
}
这是我的代码:
Parent parent = new Parent { Name = "Parent", };
Child child_1 = new Child { Name = "Child 1", Parent = parent };
Child child_2 = new Child { Name = "Child 2", Parent = parent };
GrandChild grandChild_1 = new GrandChild { Name = "GrandChild 1", Child = child_2 };
GrandChild grandChild_2 = new GrandChild { Name = "GrandChild 2", Child = child_2 };
context.Parents.Add(parent);
//no need to call SaveChanges
context.Children.Add(child_1);
context.Children.Add(child_2);
//SaveChanges() is needed here
context.GrandChildren.Add(grandChild_1);
context.GrandChildren.Add(grandChild_2);
context.SaveChanges();
此代码失败并显示消息
'Unable to determine the principal end of the Child_GrandChildren relationship. Multiple added entities may have the same primary key'
但是如果我在添加子项后保存就可以工作,而我确实需要在添加父项后调用 SaveChanges()
。
编辑:如果我删除 属性 List<GrandChild> GrandChildren
它会起作用,但我真的需要它。
这是一个错误吗?
您应该将 Child 对象添加到 Parent 对象,而不是直接将它们添加到上下文中。 GrandChild 对象应添加到相应的 Child 对象。
In this case only the Parent object should be added to the context, in this way the entities will be created in the database in the correct order and the FKs will be resolved correctly.
您的代码应如下所示:
Parent parent = new Parent { Name = "Parent" };
Child child_1 = new Child { Name = "Child 1" };
parent.Children.Add(child_1);
Child child_2 = new Child { Name = "Child 2" };
parent.Children.Add(child_2);
GrandChild grandChild_1 = new GrandChild { Name = "GrandChild 1" };
GrandChild grandChild_2 = new GrandChild { Name = "GrandChild 2" };
child_2.GrandChildren.Add(grandChild_1);
child_2.GrandChildren.Add(grandChild_2);
context.Parents.Add(parent);
context.SaveChanges();
如果不需要引用添加的对象,则可以使用这种 fluent 风格的初始化代码:
Parent parent = new Parent
{
Name = "Parent"
Children = new List<Child>
{
new Child { Name = "Child 1" },
new Child
{
Name = "Child 2",
GrandChildren = new List<GrandChild>
{
new GrandChild { Name = "GrandChild 1" },
new GrandChild { Name = "GrandChild 2" }
}
}
}
};
context.Parents.Add(parent);
context.SaveChanges();
直接将子对象添加到上下文的主要问题是,当您像这样声明它们时,尤其是在逻辑流畅,运算顺序不能乱
您需要将 ChildMap
中的关系配置更改为:
HasMany(c => c.GrandChildren).WithRequired(gc=>gc.Child).HasForeignKey(c => c.ChildID);
// the second one is not necessary, you already configure that relationship in ParentConfig
//HasRequired(e => e.Parent).WithMany().HasForeignKey(e => e.ParentID);
在最后一种情况下您可能会遇到此异常,即当您具有递归或层次关系时。
When Recursive or Hierachical data needs to be saved with deep links, you should save the data in multiple steps. Do not be afraid of calling
SaveChanges()
multiple times, the overheads are minimal, and for larger sets of data it will actually improve performance to save frequently rather than trying to save as a single action at the end of a process.
If you are concerned about ACID principals or handling failure and that is why you have avoided calling
SaveChanges()
then you should wrap your logic in a transaction:using (var trans = context.Database.BeginTransaction()) { ... context.SaveChanges(); ... context.SaveChanges(); ... trans.Commit(); }
Its not even necessary to catch and handle exceptions if you use the
IDisposable
using pattern.
- NOTE: Unlike pure SQL, EF does not support nested transactions. You can
在原始 post 的上下文中,如果 Parent 有 最爱 Child
[=62=,则可能会出现这种情况] 最爱 GrandChild
:
public class Parent
{
public int ID { get; set; }
public string Name { get; set; }
public List<Child> Children { get; set; } = new List<Child>();
public int? Favourite_ChildID { get; set; }
public Child FavouriteChild { get;set; }
public int? Favourite_GrandChildID { get; set; }
public GrandChild FavouriteGrandChild { get;set; }
}
在这种情况下,确保正确定义关系很重要并且您需要分两步保存数据。
// Parent Config
HasMany(p => p.Children)
.WithRequired(c => c.Parent)
.HasForeignKey(c => c.ParentID);
HasOptional(p => p.FavouriteChild)
.WithMany()
.HasForeignKey(p => p.Favourite_ChildID);
HasOptional(p => p.FavouriteGrandChild)
.WithMany()
.HasForeignKey(p => p.Favourite_GrandChildID);
// Child Config
HasMany(c => c.GrandChildren)
.WithRequired(gc => gc.Child)
.HasForeignKey(gc => gc.ChildID);
保存数据需要分两步完成。这个模型非常适合这个。一开始Parent
没有children,后来加了一个Child
,此时可能不是最喜欢的...
稍后添加另一个 Child
。不过,Parent
尚未决定谁是热门。后来最喜欢的 child 已被选中。
Lets overlook the real-world fact that having a child is what defines a
Parent
vs aPerson
...
您的数据逻辑需要尊重同样的思维过程。如果我们尝试将 child 定义为新 parent 的 faviourite 和 child我们有一个难题:要将 parent object 保存在数据库中,我们需要 Child 记录的 ID,但要保存 Child进入数据库,我们需要 Parent 记录的 ID...也许我们应该调用这些 Chicken
和 Egg
...
解决方法是先保存Primary关系,然后再回来保存任何递归关系链接:
Parent parent = new Parent
{
Name = "Parent"
Children = new List<Child>
{
new Child { Name = "Child 1" },
new Child
{
Name = "Child 2",
GrandChildren = new List<GrandChild>
{
new GrandChild { Name = "GrandChild 1" },
new GrandChild { Name = "GrandChild 2" }
}
}
}
};
// using transaction scope here to demonstrate how to manage multiple SaveChanges with a rollback
using (var trans = context.Database.BeginTransaction())
{
context.Parents.Add(parent);
context.SaveChanges();
parent.FavouriteChild = parent.Children.Single(child => child.Name == "Child 1");
parent.FavouriteGrandChild = parent.Children.SelectMany(child => child.GrandChildren).Single(gc => gc.Name == "GrandChild 2");
context.SaveChanges();
trans.Commit();
}
This selection logic won't work if your kids start naming their kids with names that their siblings have decided to use... but you get the point, we should be kind to our Parents as use unique names for out children :)
或者回到 OP 的原始脚本。只需在赋值之间调用 SaveChanges()
,那么所有这一切都可以避免,在这里使用事务范围可以解决 ACID 主体,如果引发异常或对 SaveChanges()
的调用之一失败,可能会违反 ACID 主体.
using (var trans = context.Database.BeginTransaction())
{
Parent parent = new Parent { Name = "Parent", };
context.Parents.Add(parent);
context.SaveChanges();
Child child_1 = new Child { Name = "Child 1", Parent = parent };
Child child_2 = new Child { Name = "Child 2", Parent = parent };
context.Children.Add(child_1);
context.Children.Add(child_2);
context.SaveChanges();
parent.FavouriteChild = child_1;
// we can save this next time, no Ids need to be forced.
GrandChild grandChild_1 = new GrandChild { Name = "GrandChild 1", Child = child_2 };
GrandChild grandChild_2 = new GrandChild { Name = "GrandChild 2", Child = child_2 };
context.GrandChildren.Add(grandChild_1);
context.GrandChildren.Add(grandChild_2);
context.SaveChanges();
// Now we can assign the faviourite GrandChild
parent.FavouriteGrandChild = grandChild_2;
context.SaveChanges();
// Actually commit the changes to the database
trans.Commit();
}