Entity Framework Cascading Delete - entity-framework

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

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

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.

Related

Multiple relations to same table with one required and others optional in EF Core

I have a table named Provider with three relations to another table State. Of these relations one is required and the other two are optional. See the relationship in the diagram below:
Here are the entities along with the fluent configurations for each.
Provider
public class Provider
{
public int Id { get; set; }
[Required]
public int PrimaryStateId { get; set; }
public virtual State PrimaryState { get; set; }
public int? BillingStateId { get; set; }
public virtual State BillingState { get; set; }
public int? ShippingStateId { get; set; }
public virtual State ShippingState { get; set; }
}
class ProviderConfig : IEntityTypeConfiguration<Provider>
{
public void Configure(EntityTypeBuilder<Provider> entity)
{
entity.HasOne(x => x.PrimaryState)
.WithMany(x => x.ProvidersPrimary)
.IsRequired(true)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(x => x.BillingState)
.WithMany(x => x.ProvidersBilling)
.IsRequired(false)
.OnDelete(DeleteBehavior.SetNull);
entity.HasOne(x => x.ShippingState)
.WithMany(x => x.ProvidersShipping)
.IsRequired(false)
.OnDelete(DeleteBehavior.SetNull);
}
}
State
public class State
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Provider> ProvidersPrimary { get; set; } = new List<Provider>();
public virtual ICollection<Provider> ProvidersBilling { get; set; } = new List<Provider>();
public virtual ICollection<Provider> ProvidersShipping { get; set; } = new List<Provider>();
}
class StateConfig : IEntityTypeConfiguration<State>
{
public void Configure(EntityTypeBuilder<State> entity)
{
entity.Property(x => x.Name).IsRequired();
entity.HasIndex(x => x.Name).IsUnique();
}
}
As you can see, I want to set DeleteBehavior.Restrict for the PrimaryState, and DeleteBehavior.SetNull for the other two relations. However, this throws error on update-database with the following message:
Introducing FOREIGN KEY constraint 'FK_Provider_State_ShippingStateId' on table 'Provider' 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 or index. See previous errors.
However, if I get rid of one of the optional relations, it works fine. That is, it works when I have one required relation and one optional relation, but not when I have one required and two optional relations. Also, it works fine if I get rid of OnDelete(DeleteBehavior.SetNull) from the optional relations, but then in the database the delete rule for foreign key BillingStateId ends up being Set Null, but that for ShippingStateId is No Action. No idea why it is different for two similarly configured optional relations.
Questions:
Why does update-database fail with that error? I don't understand how cycles or multiple cascade paths may be caused.
Why getting rid of OnDelete(DeleteBehavior.SetNull) creates a foreign key with Set Null delete rule for one optional relation but No Action for the other?
What is the correct way to configure this relationship? That is, one required relation and two optional relations, and the foreign keys for the optional relations should have a delete rule of Set Null.
VS Solution link: click

Masstransit sagas and Entity Framework repository model changes

I've been playing around with the masstransit sample from here https://github.com/MassTransit/Sample-ShoppingWeb
Allthough i have updated to the latest version(3.3.5) of masstransit and everything works fine.
I want to add ShoppingCartItems to my ShoppingCart so i added it to the model and the mapping like this.
public class ShoppingCartMap :
SagaClassMapping<ShoppingCart>
{
public ShoppingCartMap()
{
Property(x => x.CurrentState)
.HasMaxLength(64);
Property(x => x.Created);
Property(x => x.Updated);
Property(x => x.UserName)
.HasMaxLength(256);
Property(x => x.ExpirationId);
Property(x => x.OrderId);
HasMany(c => c.ShoppingCartItems);
}
}
public class ShoppingCart :
SagaStateMachineInstance
{
public string CurrentState { get; set; }
public string UserName { get; set; }
public DateTime Created { get; set; }
public DateTime Updated { get; set; }
/// <summary>
/// The expiration tag for the shopping cart, which is scheduled whenever
/// the cart is updated
/// </summary>
public Guid? ExpirationId { get; set; }
public Guid? OrderId { get; set; }
public Guid CorrelationId { get; set; }
public virtual List<ShoppingCartItem> ShoppingCartItems { get; set; } = new List<ShoppingCartItem>();
}
public class ShoppingCartItem
{
public Guid? Id { get; set; }
public string Name { get; set; }
public Guid? OrderId { get; set; }
}
This is run at startup:
SagaDbContextFactory sagaDbContextFactory =
() => new SagaDbContext<ShoppingCart, ShoppingCartMap>(SagaDbContextFactoryProvider.ConnectionString);
_repository = new Lazy<ISagaRepository<ShoppingCart>>(
() => new EntityFrameworkSagaRepository<ShoppingCart>(sagaDbContextFactory));
The problem i get is an error message saying the model has changed. If i drop the database and run the solution from scratch it works but i dont want to drop my entire DB every time i need to make a change in my saga class.
My plan is to build my ShoppingCart through the saga and when i reach my finished state i will use the saga context(ShoppingCart) to create and persist real orders. Maybe i am going by this all wrong and have missunderstood the whole concept of sagas? If so how would one go about sagas that have complex object graphs?
Saga persistence just saves your saga instance objects to some tables, according to your mapping. You can use your persistence layer's own schema update tools to fix this. I do not think this has anything to do with MassTransit. For EF you can use EF code-first migrations. For NH you can use the built-in schema update. For document databases like MondoDb or RavenDb you just do nothing.
In any case, think about this as a normal database schema change task. You have to put some effort in it like in any other database schema change. For example, you need to consider migrations required to fix your existing saga when you update the schema. As for any other schema change you would need to have some scripts or code to fix this. The same applies for document databases as well although you do not need to have schema update scripts or code for every change, but at least for those that require to change existing saga documents.

EF CF - How do I build optional:optional relationship?

I want Foo to have an optional Bar and Bar to have an optional Foo.
I seemed to manage to get it working but I had an extra column being created on only one of the tables, e.g. it made InvitationId and then also Invitation_Id in SQL on only one of the tables, even though both entities are setup the same way, but in reverse.
So I wanted to make a smaller repro so I could ask the question on SO, but in the process, and although I have just copied the original entities, removed some properties, I now have a different error, which is worryingly non-deterministic.
Ok, code.
[Table("Foo")]
public partial class Foo
{
[Key]
public Guid Id { get; set; }
[Required]
[StringLength(128)]
public string Name { get; set; }
// Referential
[ForeignKey("Bar")]
public Guid? BarId { get; set; }
public virtual Bar Bar { get; set; }
}
[Table("Bar")]
public partial class Bar
{
[Key]
public Guid Id { get; set; }
[Required]
[StringLength(128)]
public string Name { get; set; }
// Referential
[ForeignKey("Foo")]
public Guid? FooId { get; set; }
public virtual Foo Foo { get; set; }
}
And in OnModelCreating
modelBuilder.Entity<Foo>()
.HasOptional<Bar>(foo => foo.Bar)
.WithOptionalPrincipal(bar => bar.Foo);
The error is:
The navigation property 'Foo' declared on type 'Product.Data.Entities.Bar' has been configured with conflicting foreign keys.
The original entities still exist in the project and are setup in exactly the same way except they have more properties, but they get created without error, except one has the extraneous FK column.
So there's a number of issues:
Why did I get the extra Invitation_Id column when it already has InvitationId?
Why can I not reproduce it?
Why does the error now appear? And if I solve that, is it going to help me with my original entities if they don't have the same issue.
What's the proper way of achieving my objective in my opening sentence above?
Meanwhile, I'm going to begin building Foo and Bar back into Invitation and Expectation bit by bit until it goes funny.
Update
So I ended up with EXACT copies of the original entities in all but name. These copies caused the FK conflict error above, but the originals do not!!
I then removed the originals and renamed the copies to their original names, changing none of the properties or attributes, and the error went away and I was back to the original issue of the extraneous FK column!
Bonkers.
Luke
The first thing is in an one to one relationship one end must be the principal and the another one is the dependent, so you can't specify a FK property in both entities. The second thing is if you are going to use a FK property, EF requires the PK of the dependent entity should be FK too:
public class Principal
{
public int Id{get;set;}
public virtual Dependent Dependent{get;set;}
}
public class Dependent
{
[Key, ForeignKey("Principal")]
public int PrincipalId{get;set;}
public virtual Principal Principal{get;set;}
}
The third thing is EF lets you configure a one-to-one relationship with optional in both sides using Fluent API, but you can specify the FK, because as I said before, it should be configured as PK too, so EF will handle that FK for you in DB, that's way you have an extra Invitation_Id column.
To resolve your issue your model should be this way(remove the FK properties):
[Table("Foo")]
public partial class Foo
{
[Key]
public Guid Id { get; set; }
[Required]
[StringLength(128)]
public string Name { get; set; }
// Referential
public virtual Bar Bar { get; set; }
}
[Table("Bar")]
public partial class Bar
{
[Key]
public Guid Id { get; set; }
[Required]
[StringLength(128)]
public string Name { get; set; }
// Referential
public virtual Foo Foo { get; set; }
}
And use the same Fluent Api configuration:
modelBuilder.Entity<Foo>()
.HasOptional(foo => foo.Bar)
.WithOptionalPrincipal(bar => bar.Foo);
About why the exception is not happened in your real code, I think the same as #user2697817, you should be creating two different relationships, but I can fully ensure that because I'm not seeing your real model.
A second option could be the solution that is showed by #user2697817, but in that case you are going to have two different relationships.
As I mentioned in my comment, because there is two relationships and it's possible to have a navigation property for each side of the relationship I think EF is having trouble distinguishing which navigation prop is part of which relationship.
I would suggest defining both relationships explicitly in your OnModelCreating
modelBuilder.Entity<Foo>().HasOptional(f => f.Bar)
.WithMany()
.HasForeignKey(f => f.BarId);
modelBuilder.Entity<Bar>().HasOptional(b => b.Foo)
.WithMany()
.HasForeignKey(b => b.FooId);

Entity Framework Generated Column Names

I have a Job Entity which has 2 collections of the same type
public virtual ICollection<Device> ExistingDevices { get; set; }
public virtual ICollection<Device> NewDevices { get; set; }
On the Device Entity, it refers back to the Job
public int JobId { get; set; }
public virtual Job Job { get; set; }
On the surface, this works just fine, however on the database, if you look at a device you see this
//Devices Table in Db
|JobId | Job_Id | Job_Id1 |
My setup includes Entity configuration objects for using fluent API, however I have not worked out how to fix this. The first JobId is fine, it is a perfect description of the data. The second two refer to the ExistingDevices and NewDevices lists they belong to. The headers are not at all descriptive.
Is it possible to rename these columns to something more appropriate?
//Edit
It has the FK JobId but also if the device is in the ExistingDevices list, the JobId also gets put into Job_Id and Job_Id1 is null. If the device belongs to the NewDevices list, the Job_Id is null and the Job_Id1 has the JobId in it.
Naming the Job_id to ExistingDevices and Job_Id1 to NewDevices would make it much clearer in Db.
Update
Having slept on it, I decided it was a design fault.
I changed the Device Model to have
public bool NewDevice { get; set; }
and changed the Job Model by removing the 2 existing ICollections and adding
public virtual ICollection<Device> Devices { get; set; }
Rather than having two device collections, I now have one, with a properly descriptive FK. In the database a 1 or 0 will indicate new or existing device.
You have two one to many relationship, the database will have two foreign key columns on the dependent entity (Device). JobId might represent ExistingDevices and JobId1 might represent NewDevices.
To be clear you should define two navigation properties as follow.
public int? ExistingJobId { get; set; }
public virtual Job ExistingJob { get; set; }
public int? NewJobId { get; set; }
public virtual Job NewJob { get; set; }
Then can configure the relationship using Fluent Api.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Device>()
.HasOptional(x => x.ExistingJob)
.WithMany(x => x.ExistingDevices)
.HasForeignKey(x => x.ExistingJobId);
.WillCascadeOnDelete(true);
modelBuilder.Entity<Device>()
.HasOptional(x => x.NewJob)
.WithMany(x => x.NewDevices)
.HasForeignKey(x => x.NewJobId)
.WillCascadeOnDelete(false);
}
Note, one foreign key is defined without cascading delete (false) because multiple cascading delete is not allowed.
update: the required existing job and new job have been changed into optional.

EF 4.1 Code First ModelBuilder HasForeignKey for One to One Relationships

Very simply I am using Entity Framework 4.1 code first and I would like to replace my [ForeignKey(..)] attributes with fluent calls on modelBuilder instead. Something similar to WithRequired(..) and HasForeignKey(..) below which tie an explicit foreign key property (CreatedBySessionId) together with the associated navigation property (CreatedBySession). But I would like to do this for a one to one relationsip instead of a one to many:
modelBuilder.Entity<..>().HasMany(..).WithRequired(x => x.CreatedBySession).HasForeignKey(x => x.CreatedBySessionId)
A more concrete example is below. This works quite happily with the [ForeignKey(..)] attribute but I'd like to do away with it and configure it purely on modelbuilder.
public class VendorApplication
{
public int VendorApplicationId { get; set; }
public int CreatedBySessionId { get; set; }
public virtual Session CreatedBySession { get; set; }
}
public class Session
{
public int SessionId { get; set; }
[ForeignKey("CurrentApplication")]
public int? CurrentApplicationId { get; set; }
public virtual VendorApplication CurrentApplication { get; set; }
public virtual ICollection<VendorApplication> Applications { get; set; }
}
public class MyDataContext: DbContext
{
public IDbSet<VendorApplication> Applications { get; set; }
public IDbSet<Session> Sessions { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Session>().HasMany(x => x.Applications).WithRequired(x => x.CreatedBySession).HasForeignKey(x => x.CreatedBySessionId).WillCascadeOnDelete(false);
// Note: We have to turn off Cascade delete on Session <-> VendorApplication relationship so that SQL doesn't complain about cyclic cascading deletes
}
}
Here a Session can be responsible for creating many VendorApplications (Session.Applications), but a Session is working on at most one VendorApplication at a time (Session.CurrentApplication). I would like to tie the CurrentApplicationId property with the CurrentApplication navigation property in modelBuilder instead of via the [ForeignKey(..)] attribute.
Things I've Tried
When you remove the [ForeignKey(..)] attribute the CurrentApplication property generates a CurrentApplication_VendorApplicationId column in the database which is not tied to the CurrentApplicationId column.
I've tried explicitly mapping the relationship using the CurrentApplicationId column name as below, but obviously this generates an error because the database column name "CurrentApplicationId" is already being used by the property Session.CurrentApplicationId:
modelBuilder.Entity<Session>().HasOptional(x => x.CurrentApplication).WithOptionalDependent().Map(config => config.MapKey("CurrentApplicationId"));
It feels like I'm missing something very obvious here since all I want to do is perform the same operation that [ForeignKey(..)] does but within the model builder. Or is it a case that this is bad practise and was explicitly left out?
You need to map the relationship as one-to-many and omit the collection property in the relationship.
modelBuilder.Entity<Session>()
.HasOptional(x => x.CurrentApplication)
.WithMany()
.HasForeignKey(x => x.CurrentApplicationId)