实体框架:首先是代码中的外键

时间:2016-08-05 15:46:53

标签: c# entity-framework foreign-keys entity-framework-6 code-first

我的代码中出现以下错误:

  

无法确定相关操作的有效排序。由于外键约束,模型要求或存储生成的值

,可能存在依赖关系

代码:

类食品:

public class Food
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public short Id { get; set; }

    //some Property
    public string Name { get; set; }

    public virtual ICollection<Person> Persons { get; set; }
}

班主任:

public class Person
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    //some Property
    public string FirstName { get; set; }

    [ForeignKey("BestFoodId")]
    public Food BestFood { get; set; }
    public short BestFoodId { get; set; }

    public virtual ICollection<Food> FavoriteFoods { get; set; }
}

种子方法:

 protected override void Seed(MyContext context)
    {
        Food food1 = new Food() { Name = "foo1" };
        Food food2 = new Food() { Name = "foo2" };
        Food food3 = new Food() { Name = "foo3" };

        context.Persons.AddOrUpdate(new Person()
        {
            FirstName = "Jack",
            BestFood = food2,
            FavoriteFoods = new List<Food>() { food1, food2, food3 }
        });

    }

2 个答案:

答案 0 :(得分:5)

错误原因:混淆关联

这是因为按照惯例,实体框架假定Person.BestFoodId反向属性Food.Persons。换句话说:Person.BestFoodFood.Persons被假定为一对多关联的两端,以Person.BestFoodId作为外键。

您可以通过向[InverseProperty]添加BestFood属性来验证这一点:

public class Person
{
    ...
    [ForeignKey("BestFoodId")]
    [InverseProperty("Persons")]
    public Food BestFood { get; set; }
    ...
}

这会导致同样的错误。

此错误 - 无效订单 - 始终表示鸡与蛋的问题。在您的情况下,EF尝试插入食物,其中需要将插入的人的生成的Id作为外键,而插入的人需要插入的foo2食物的生成的Id。

解决方案:显式映射关联

实际上,PersonFood有两种关联:

  • 1-n:Food可以是n个人的BestFood
  • n-m:n Food s可以是m个人的FavoriteFoods

在你的模型中,BestFood没有反向属性, 可能是......

public virtual ICollection<Person> BestFoodOf { get; set; }

...但这不是必要的,因为它缺失了,它掩盖了EF如何推断关联。

您可以通过显式映射关联来解决此问题,例如在OnModelCreating子类的DbContext覆盖中:

modelBuilder.Entity<Person>()
            .HasRequired(p => p.BestFood)
            .WithMany() // No inverse property
            .HasForeignKey(p => p.BestFoodId)
            //.WillCascadeOnDelete(false)
            ;

modelBuilder.Entity<Person>()
            .HasMany(p => p.FavoriteFoods)
            .WithMany(f => f.Persons)
            .Map(m => m.MapLeftKey("PersonId")
                       .MapRightKey("FoodId")
                       .ToTable("PersonFavoriteFood"));

我已评论WillCascadeOnDelete(false)。您必须添加此行,或添加...

modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();

...防止多个级联删除路径(SQL Server限制)。

有了这个,EF知道如何确定插入的有效排序:它将首先插入食物,然后插入人(使用生成的foo2 Id作为外键)然后结点PersonFavoriteFood表中的记录。

答案 1 :(得分:1)

看起来你有一个循环依赖。

答案在这里:

可选改进:

  • 您应该将navigation property声明为virtual

  • 如果您使用的是C#6.0或更高版本,请将[ForeignKeyAttribute]数据注释定义更改为[ForeignKey([nameof(BestFoodId))],以避免硬编码属性名称出错。 nameof是一个非常酷的编译器功能! :)