Reputation: 161
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
Upvotes: 1
Views: 1146
Reputation: 16553
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 UseCase
s
Upvotes: 2
Reputation: 16149
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.
Upvotes: 0