簡體   English   中英

無法確定 X 關系的主端。 多個添加的實體可能具有相同的主鍵

[英]Unable to determine the principal end of the X relationship. Multiple added entities may have the same primary key

我知道還有其他人問過同樣的問題,答案是處理引用而不是 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<GrandChild> GrandChildren它可以工作,但我真的需要它。

這是一個錯誤嗎?

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

您需要執行以下操作:

parent.Children.Add(child1);
parent.Children.Add(child2);
child1.GrandChildren.Add(grandChild1);
....
context.SaveChanges();

在最后一種情況下,您可能會遇到此異常,即當您具有遞歸或層次關系時。

當Recursive或Hierachical數據需要用深層鏈接保存時,應該分多步保存數據。 不要害怕多次調用SaveChanges() ,開銷很小,並且對於較大的數據集,它實際上會提高性能以頻繁保存而不是嘗試在進程結束時保存為單個操作。

如果您擔心 ACID 主體或處理失敗,這就是您避免調用SaveChanges()那么您應該將您的邏輯包裝在一個事務中

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

如果您使用IDisposable using模式,則甚至不需要捕獲和處理異常。

  • 注意:與純 SQL 不同,EF 不支持嵌套事務。 你可以

在原始帖子的上下文中,如果 Parent 有一個最喜歡的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; }
}

在這種情況下這將是非常重要的肯定使該關系正確定義的您需要將數據保存在2個步驟。

// 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 ......

您的數據邏輯需要遵循相同的思維過程。 如果我們嘗試將子項定義為新父項的最愛子項,我們會遇到一個難題:要在數據庫中保存父對象,我們需要子記錄的 Id,但對於子項保存到數據庫中,我們需要父記錄的 ID...也許我們應該將這些命名為ChickenEgg ...

解決辦法是先保存關系,然后再回來保存任何遞歸關系鏈接:

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() ,那么所有這些都可以避免,這里使用事務范圍解決 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();
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM