Entity Framework Code first - FOREIGN KEY constraint problem

rjovic picture rjovic · Sep 9, 2011 · Viewed 9.2k times · Source

I'm new to EF code first principal and currently with no clue what to do.. I have 2 POCO classes..

public class Problem
{
    public int ProblemID { get; set; }
    public int UserID { get; set; }
    public int CategoryID { get; set; }
    public int RatingID { get; set; }

    public string Title { get; set; }
    public string Description { get; set; }        
    public double Latitude { get; set; }
    public double Longitude { get; set; }
    public int State { get; set; }
    public DateTime CreatedOn { get; set; }

    public virtual Rating Rating { get; set; }
    public virtual Category Category { get; set; }
    public virtual User User { get; set; }

    public virtual ICollection<Picture> Pictures { get; set; }
    public virtual ICollection<Comment> Comments { get; set; }
}

and

public class User
{
    public int UserID { get; set; }
    public int RoleID { get; set; }

    public string Name { get; set; }
    public string Surname { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
    public string SocialHandle { get; set; }       
    public DateTime RegisteredOn { get; set; }

    public virtual Role Role { get; set; }

    public virtual ICollection<Point> Points { get; set; } 
    public virtual ICollection<Problem> Problems { get; set; }
    public virtual ICollection<Comment> Comments { get; set; }               
}

My database creation is ok, but when I try to init some data in it using custom initializer, I'm getting following error :

Introducing FOREIGN KEY constraint 'Problem_User' on table 'Problems' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint. See previous errors.

This is my initializer :

protected override void Seed(CleanStreets context)
    {
        var adminRole = new Role { Name = "Administrator", RoleID = 0 };            
        var moderatorRole = new Role { Name = "Moderator", RoleID = 1 };
        var userRole = new Role { Name = "User", RoleID = 2 };

        context.Roles.Add(adminRole);
        context.Roles.Add(userRole);
        context.Roles.Add(moderatorRole);

        var admin = new User { Name = "admin", Surname = "admin", Role = adminRole, Email = "[email protected]", Password = "", RegisteredOn = DateTime.Now, SocialHandle = null };
        var user = new User { Name = "user", Surname = "user", Role = userRole, Email = "[email protected]", Password = "", RegisteredOn = DateTime.Now, SocialHandle = null };

        context.Users.Add(admin);
        context.Users.Add(user);

        var categoryOne = new Category { Title = "Komunalni problemi", CategoryID = 0 };
        var categoryTwo = new Category { Title = "Ostali problemi", CategoryID = 1 };

        context.Categories.Add(categoryOne);
        context.Categories.Add(categoryTwo);

        var problem = new Problem { Category = categoryOne, Title = "Prvi testni problem", Description = "Ovo je testni opis", Latitude = 45.5, Longitude = 15.5, State = 0, CreatedOn = DateTime.Now, User = user };

        context.Problems.Add(problem);

        base.Seed(context);
    }

What I'm doing wrong? Thank you in advance!

Answer

Ladislav Mrnka picture Ladislav Mrnka · Sep 9, 2011

That will be because of Comments. EF by default uses cascade deletes on references. In your case the cascade delete will be created from User -> Problem, User -> Comment but also from Problem -> Comment. If you deleted User cascading to the same comment record can come from both Problem and User. That is not allowed in SQL Server. Each record can be accessible by only single cascade delete path.

To avoid this you must use fluent mapping to turn of cascade delete on one relation (you must make choice which one). The example for User -> Comment

public class Context : DbContext
{
    public DbSet<User> Users { get; set; }
    public DbSet<Comment> Comments { get; set; }
    public DbSet<Problem> Problems { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>()
                    .HasMany(u => u.Comments)
                    .HasRequired(c => c.User)
                    .HasForeignKey(c => c.UserId)
                    .WillCascadeOnDelete(false);

        base.OnModelCreating(modelBuilder);
    }
} 

There can be other entities and relations which can cause this problem in your model but Comment looks obvious from your example.