繁体   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