Ali Mottaghi Pour Ali Mottaghi Pour - 4 months ago 29
C# Question

Entity Framework: Foreign Key in code first

What is wrong in my code that i get below error:


Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values


Code:

Class Food:

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; }
}


Class Person:

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; }
}


Seed Method:

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

}

Answer

Cause of the error: confused associations

This happens because Entity Framework, by convention, assumes that the inverse property of Person.BestFoodId is Food.Persons. Stated differently: Person.BestFood and Food.Persons are assumed to be the two ends of a one-to-many association, having Person.BestFoodId as foreign key.

You can verify that by adding an [InverseProperty] attribute to BestFood:

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

This causes the same error.

This error --no valid ordering-- always indicates a chicken-and-egg problem. In your case, EF tries to insert the foods, which need the generated Id of the inserted person as foreign key, while the inserted person needs the generated Id of the inserted foo2 food.

Solution: explicitly mapped association

In reality, Person and Food have two associations:

  • 1-n: Food can be BestFood of n people.
  • n-m: n Foods can be the FavoriteFoods of m people.

In your model, BestFood doesn't have an inverse property, which could have been something as ...

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

... but it isn't necessary and because it's missing, it obscures how EF infers the associations.

You can fix this by explicitly mapping the associations, for instance in the OnModelCreating override of your DbContext subclass:

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

I have commented out WillCascadeOnDelete(false). You either have to add this line, or add ...

modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();

... to prevent multiple cascaded delete paths (a SQL Server restrictions).

Having this in place, EF knows how to determine a valid ordering for the inserts: it will will first insert the foods, then insert the person (using the generated foo2 Id as foreign key) and then the junction records in the PersonFavoriteFood table.