【问题标题】:Unable to determine the principal end of the X relationship. Multiple added entities may have the same primary key无法确定 X 关系的主体端。多个添加的实体可能具有相同的主键
【发布时间】:2016-04-20 18:41:42
【问题描述】:

我知道还有其他人问过同样的问题,答案是处理引用而不是 ID。

就我而言,我的实体框架有一个奇怪的行为:它在一种情况下(父子)有效,但在另一种情况下(子孙)无效。

这是我的模型:

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();

此代码失败并显示消息

'无法确定 Child_GrandChildren 的主体端 关系。多个添加的实体可能具有相同的主键'

但如果我在添加孩子后保存,但我确实需要在添加父母后调用SaveChanges()

编辑:如果我删除属性 List&lt;GrandChild&gt; GrandChildren 它可以工作,但我真的需要它。

这是一个错误吗?

【问题讨论】:

    标签: c# entity-framework relationship


    【解决方案1】:

    您需要将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);
    

    【讨论】:

    • 同时从 GrandChildMap 中删除 HasRequred(...)
    • 哦,我没有滚动到最后,是的,OP也在配置第二个关系两次。从GrandChildMap类中删除关系配置。谢谢@IvanStoev
    • 欢迎您@octavioccl。不用说你得到了我的 +1 :)
    • 非常感谢 ;),并祝贺 @IvanStoev 获得 20K
    【解决方案2】:

    您应该将 Child 对象添加到 Parent 对象,而不是直接将它们添加到上下文中。 GrandChild 对象应添加到相应的 Child 对象中。

    在这种情况下,只应将 Parent 对象添加到上下文中,这样实体将在数据库中以正确的顺序创建,并且 FK 将被正确解析。

    您的代码应如下所示:

    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();
    

    直接将子对象添加到上下文的主要问题是,当您像这样声明它们时,不容易推断出将对象添加到数据库应遵循的顺序,尤其是在 流畅逻辑,操作顺序不能混淆。

    【讨论】:

    • 对于 OP,接受的答案是正确的,但对于正确配置了他们的关系的每个人,此答案针对的是您将看到此错误的下一个最常见的原因。
    【解决方案3】:

    在最后一种情况下,您可能会遇到此异常,即当您具有递归或层次关系时。

    当需要使用深层链接保存递归或分层数据时,您应该分多个步骤保存数据。 不要害怕调用SaveChanges()多次,开销很小,而且对于更大的数据集,它实际上会提高性能以频繁保存而不是尝试保存作为流程结束时的单个操作。

    如果您担心 ACID 主体或处理失败,这就是您避免调用 SaveChanges() 的原因,那么您应该将您的逻辑包装在一个事务中:

      using (var trans = context.Database.BeginTransaction())
      {
          ...
          context.SaveChanges();
          ...
          context.SaveChanges();
          ...
          trans.Commit();
      }          
    

    如果您使用IDisposable using 模式,甚至不需要捕获和处理异常。

    • 注意: 与纯 SQL 不同,EF 不支持嵌套事务。你可以

    在原始帖子的上下文中,如果家长有最喜欢的Child 和/或最喜欢的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 没有孩子,后来添加了一个Child,此时,它可能不是最喜欢的... 后来又添加了一个Child。尽管如此,Parent 还没有决定谁是最喜欢的。后来最喜欢的孩子被选中

    让我们忽略一个现实世界的事实,即拥有孩子是定义ParentPerson...

    您的数据逻辑也需要尊重同样的思维过程。如果我们尝试将子对象定义为新父对象的 faviouritechild,我们会遇到一个难题:要将父对象保存在数据库中我们需要子记录的 ID,但是要将子记录保存到数据库中,我们需要父记录的 ID...也许我们应该调用这些 ChickenEgg...

    解决方法是先保存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();
    }
    

    如果您的孩子开始使用他们的兄弟姐妹决定使用的名字来命名他们的孩子,那么这个选择逻辑将不起作用......但您明白了,我们应该善待我们的父母,因为为外出的孩子使用唯一的名字: )

    或者回到 OP 的原始脚本。只需在分配之间调用SaveChanges(),那么所有这些都可以避免,在这里使用事务范围解决如果引发异常或对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();
    }
    

    【讨论】:

      猜你喜欢
      • 2013-09-15
      • 2016-06-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-16
      • 2011-08-27
      • 2018-02-08
      相关资源
      最近更新 更多