1

First of all, apologies if I'm missing some basic stuff here but I'm new to EF and still getting my head around setting up the DB code first....

I'm having a similar problem to this Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths but can't seem to work out from the comments there what I need to do with my particular model. When I attempt to update database after adding in public virtual Actor actor { get; set; } to my UseCase class, I get this error:

Introducing FOREIGN KEY constraint 'FK_dbo.UseCase_dbo.Actor_ActorID' on table 'UseCase' 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.

I know it must be something to do with the way that my FK constraints are set up (probably something to do with deleting a use case meaning that I'll end up deleting data from multiple other tables).

I tried turning off cascading delete, but still get the error:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            //prevent table names created by entity framework being pluralised
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
            //Turn off delete cascades between parent child tables. May need to put this back in future, but for the time being it is stopping the database being updated through the package manager console (error is that a foregin key constraint may cause cycles or multiple cascade paths) 
            modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
            modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
        }

Here are my models. What should happen is that only if a project is deleted should it's use cases or actors be deleted. Actors should not be deleted when a UseCase is, because they may be involved in other UseCases. Can anyone point to what I need to change?

Finally, the correct model indeed is this Project > Actors > Use Cases. I assume that I should just remove public virtual int ProjectID { get; set; } and public virtual Project project { get; set; } from UseCase?

Learning hurts!

 public class Project
    {
        public virtual int ID {get; set;}
        [DisplayName ("Project Name")]
        public virtual string ProjectName { get; set; }
        [DisplayName("Client")]    
        public virtual string ClientID { get; set; }
        public virtual string Description { get; set; }
        [DisplayName("Use Cases")]
        public virtual ICollection <UseCase> UseCases { get; set; } 

   }

public class UseCase

{
    public virtual int ID { get; set; }
    [Required]
    public virtual int ProjectID { get; set; }
    public virtual int ActorID { get; set; }
    [Required]
    public virtual Actor actor { get; set; }
    public virtual string Title { get; set; }
    public virtual Level? Level { get; set; }
    public virtual string Precondition { get; set; }
    public virtual string MinimalGuarantee { get; set; }
    public virtual string SuccessGuarantee { get; set; }
    public virtual ICollection<Step> Steps { get; set; }
    public virtual ICollection<Extension> Extensions { get; set; }
    public virtual ICollection<Query> Queries { get; set; }

}

public class Actor
{
    public virtual int ID { get; set; }
    public virtual int projectID { get; set; }
    public virtual Project project { get; set; } 
    public virtual string Title { get; set; }

    [DataType(DataType.MultilineText)]
    public virtual string Description { get; set; }

}

UPDATED So, here is my modified code based on feedback below. I'm still getting the same error, either when I run the application and it tries to create the DB or when I try to update the database through package manager Update-Database. Driving me crazy.

To me, the code below says if I delete an actor, delete the use cases for that actor too. If I delete a project, delete the actors for the project and therefore delete the use cases for each actor too. But if I delete a project, don't delete the use cases. Clearly, I'm misunderstanding something quite badly :-(

    modelBuilder.Entity<Actor>()
    .HasMany(a => a.useCases)
    .WithRequired(uc => uc.actor)
    .HasForeignKey(uc => uc.ActorID)
    .WillCascadeOnDelete(true); // and this works

    modelBuilder.Entity<Project>()
    .HasMany(p => p.actors)
    .WithRequired(a => a.project)
    .HasForeignKey(a => a.projectID)
    .WillCascadeOnDelete(true); // this works

    modelBuilder.Entity<Project>()
    .HasMany(p => p.UseCases)
    .WithRequired(uc => uc.project)
    .HasForeignKey(uc => uc.ProjectID)
    .WillCascadeOnDelete(false); // disable this cascading delete
Community
  • 1
  • 1
gazrolo4
  • 161
  • 2
  • 17
  • 1
    are you using migrations? – mga911 Nov 11 '14 at 18:40
  • 1
    I overlooked this comment originally (and wasted hours as a consequence!) - the problem did turn out to be due to migrations (in part anyway). Due to my inexperience with EF, I was implementing changes to my model as suggested below but not updating the DB - I had wrongly assumed this was automatic. I obviously now realise that automatic migrations need to be turn on explicitly. I've also got the hang of doing them manually too :-) After applying changes below and updating DB, all is now working. – gazrolo4 Nov 23 '14 at 09:14

2 Answers2

2

You need to disable cascade deletes for all but one of the possible paths. In your case you have the following paths:

Project -> UseCase
Project -> Actor -> UseCase

You can allow a single path for cascading deletion of UseCase - via the Project entity or Actor entity. However, if we disable cascading deletes in the Project -> UseCase path, we'll still achieve a cascading delete via Actor:

modelBuilder.Entity<Project>()
    .HasMany( p => p.UseCases )
    .WithRequired( uc => uc.Project )
    .HasForeignKey( uc => uc.ProjectID )
    .WillCascadeOnDelete( false ); // disable this cascading delete

modelBuilder.Entity<Project>()
    .HasMany( p => p.Actors )
    .WithRequired( a => a.Project )
    .HasForeignKey( a => a.ProjectID )
    .WillCascadeOnDelete( true ); // this works

modelBuilder.Entity<Actor>()
    .HasMany( a => a.UseCases )
    .WithRequired( uc => uc.Actor )
    .HasForeignKey( uc => uc.ActorID )
    .WillCascadeOnDelete( true ); // and this works

Side note:

Your model has a data inconsistency hazard - both Actor and UseCase have a FK to Project via ProjectID, but there is nothing in the model to enforce the Actor referenced by a UseCase has the same ProjectID - an Actor from "Project 1" could be reference by a UseCase from "Project 2". You could include the ProjectID in the Actor PK and then in the UseCase->Actor FK, ensuring that the Actor referenced by a UseCase belongs to the same Project, but this would technically violate the 2NF.

The 'proper' model is probably a Project->Actors->UseCases hierarchy, simply requiring you to join through Actors to get a Project's UseCases

Moho
  • 15,457
  • 1
  • 30
  • 31
  • Really struggling here...updated the original question above. Still need some help as still getting the same error despite changes. I know I'm obviously missing something but I have no idea what – gazrolo4 Nov 13 '14 at 19:08
  • Not sure what your issue is, then; my solution works when I tested it. I recommend getting rid of the reference to `Project` in `UseCase` and simply use `Project.Actors.SelectMany( a => a.UseCases )` if you want to get all `UseCase`s associated with a `Project` (you would need to add the `Actors` collection nav property to `Project`) – Moho Nov 14 '14 at 14:29
  • Thanks Moho I'm away from my machine at the minute but I'll have another fiddle this evening – gazrolo4 Nov 14 '14 at 14:55
  • 1
    Marking this as the answer - this wasn't working initially as I wasn't updating the DB through adding a migration, so no matter what I changed I was still getting the error message. Once I finally realised this the changes above work and there is now no issue with cascading deletes. Thanks @moho – gazrolo4 Nov 23 '14 at 09:15
0

You need to make ActorID in your UseCase class as a nullable int. EF is throwing that error because it sees 2 foreign keys that are required in a single class. Having that would create multiple cascade paths--something that SQL Server is, unfortunately, ill-equipped to handle.

In any case, making Actor optional on your UseCase class will mean that the Actor won't be deleted when the UseCase is, which I believe is your intent.

Corey Adler
  • 15,897
  • 18
  • 66
  • 80
  • Hooray for -1 without comments! Because people can really learn well when they don't know what they said wrong! – Corey Adler Nov 11 '14 at 20:03
  • The model can be configured for proper cascading deletes without removing the requirement for `ActorId`. – Moho Nov 11 '14 at 20:17
  • @IronMan84 I tried changing my use case model to this: `public virtual int? ActorID { get; set; }` but I still got the same issue. Going to try the changes suggested by @Moho above, the structure of my DB clearly has issues – gazrolo4 Nov 13 '14 at 18:14