Having trouble with entity framework core many to many relation

Viewed 90

I am designing a database i have three models(user, movie, review).The idea is each user can submit a movie each user can leave a review for a movie.The user model has ICollection and ICollecyion, the movie model has foreign key UserId and ICollection, and the review model has foreign keys for UserId and MovieId. When i try update-database i get this error:

Introducing FOREIGN KEY constraint 'FK_Review_User_UserId' on table 'Review' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.

Here are my models :

 public class User
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Username { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
    public int FavouriteGenre { get; set; }
    public ICollection<Movie> SubmittedMovies { get; set; }
    public ICollection<Review> SubmittedReviews { get; set; }
}

public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public string PosterUrl { get; set; }
    public int Year { get; set; }
    public int Genre { get; set; }
    public DateTime Posted { get; set; }
    public int UserId { get; set; }
    public User User { get; set; }
    public ICollection<Review> Reviews { get; set; }
}

 public class Review
{
    public int Id { get; set; }
    public int Rating { get; set; }
    public string UserReview { get; set; }
    public int MovieId { get; set; }
    public Movie Movie { get; set; }
    public int UserId { get; set; }
    public User User { get; set; }
    public DateTime Posted { get; set; }
}

And my on creating method in the dbContext :

 protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<Review>()
            .HasOne(r => r.User)
            .WithMany(u => u.SubmittedReviews)
            .HasForeignKey(r => r.UserId);

        builder.Entity<Review>()
            .HasOne(r => r.Movie)
            .WithMany(m => m.Reviews)
            .HasForeignKey(r => r.MovieId);
        
    }

I have tried this :

builder.Entity<Review>()
                .HasOne(r => r.User)
                .WithMany(u => u.SubmittedReviews)
                .HasForeignKey(r => r.UserId)
                .IsRequired(false)
                .OnDelete(DeleteBehavior.Restrict);

 builder.Entity<Review>()
                .HasOne(r => r.Movie)
                .WithMany(m => m.Reviews)
                .HasForeignKey(r => r.MovieId)
                .IsRequired(false)
                .OnDelete(DeleteBehavior.Restrict);

I have also tried making the foreign keys in the Review model nullable(i have tried separately and both at the same time) with DeleteBehaviour.Restrict in the context , but nothing seems to work , when i deleted a movie it worked well , but when i tried to delete a user i got this error in SSMS :

The DELETE statement conflicted with the REFERENCE constraint "FK_Review_User_UserId". The conflict occurred in database "MovieDb", table "dbo.Review", column 'UserId'.

I think i understand the problem but i cant find a solution.

1 Answers

From this article

In SQL Server, a table cannot appear more than one time in a list of all the cascading referential actions that are started by either a DELETE or an UPDATE statement. For example, the tree of cascading referential actions must only have one path to a particular table on the cascading referential actions tree

Your schema contains a circular reference because a user can have a review on their own movie.

Here's what's (potentially) happening when you try to delete a user:

  • Reviews related to that user are cascade-deleted
  • Movies related to that user are cascade-deleted
  • Reviews related to that movie are cascade-deleted

The last two points there are what is causing the issue. Since both User and Movie have (I'm assuming) required relationships, you can have a scenario where the DB tries to delete the same record twice because it has a foreign key reference to a User and that same review is on a movie that the User created.

I would make the UserId field nullable/optional on the Reviews table. Based on your description, I'm not sure if you set the property to be nullable as well, but your code should look like this:

public class Review
{
    // Other fields hidden
    public int? UserId { get; set; }
    // ...
}

In your FluentAPI model builder definitions, you can set Movie Reviews to cascade, and SubmittedReviews on the users table to restrict on delete. You can also remove the .IsRequired(false) line from the model builder definition for Movie Reviews

Personally, this is why I just use "Soft Delete" columns (like DeletedBy, DeletedOn) in models with lots of dependent relationships. It's a bit more involved in your API code, as you have to manually go in and delete/mark as deleted the related records, but it prevents headaches like this.

Related