Difference between Remove<OneToManyCascadeDeleteConvention>() and WillCascadeOnDelete(false) in EF - c#

According to Enabling Cascade Delete on Microsoft's web site:
You can remove these cascade delete conventions by using:
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>()
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>()
The following code configures the relationship to be required and then disables cascade delete.
C#
modelBuilder.Entity<Course>()
.HasRequired(t => t.Department)
.WithMany(t => t.Courses)
.HasForeignKey(d => d.DepartmentID)
.WillCascadeOnDelete(false);
So, as far as I understood, Remove<OneToManyCascadeDeleteConvention>() removes cascade delete for all entities in this context, while WillCascadeOnDelete(false) only removes only the related entity (Course entity in the example above). Is that true?

Related

Delete parent if no children in EF Core 7

Using EF Core 7 and .NET 7 (but also in previous versions), it is possible to delete all children of a one-to-many relationship in a SQL server database by configuring the delete behavior of the parent entity in the OnModelCreating-method in the class deriving from the DbContext-class, like this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Department>()
.HasMany(d => d.Employees)
.WithOne(e => e.Department)
.OnDelete(DeleteBehavior.Cascade)
}
}
But what if I want to delete the parent if all child entities are deleted?
I've tried mapping a reversed delete pattern from the one above (see below), but to no success.
modelBuilder.Entity<Employee>()
.HasOne(e => e.Department)
.WithMany(d => d.Employees)
.OnDelete(DeleteBehavior.Cascade);
ORM engines are inspired from the relational database management systems. Removing a parent when last child is removed is not a standard operation on a relation change in the DB engines. So EFCore does not support it to. At the database level you can use triggers to achieve what you want.

Does isRequired() conflict with onDelete(DeleteBehavior.setNull)?

I am creating code first Entity framework and when I using fluent API to manage the mapping I have the following code
modelBuilder.Entity<Room>()
.HasOne(r => r.Creator)
.WithMany(u => u.Rooms)
.HasForeignKey(p => p.CreatorId)
.IsRequired()
.OnDelete(DeleteBehavior.SetNull);
so my question is there is any conflict in this chaining method as IsRequired() is used to make the foreign key not allowed to be null and the ".OnDelete(DeleteBehavior.SetNull);" set the foreign key to null when u (referred to the user) entity is deleted, so what would be the behavior of this chaining method when the user associated with this room is deleted, is the operation will be done and the foreign will be set to null or the operation will be restricted?
Yes, they will conflict. If you use DeleteBehavior.SetNull, your CreatorId will be set to nullable during the migration. However, if you set CreatorId to required, they will conflict and you will not be able to successfully migrate and successfully created the relationship.
If you need the CreatorId attribute to be required, then it is recommended that you useļ¼š
modelBuilder.Entity<Room>()
.HasOne(r => r.Creator)
.WithMany(u => u.Rooms)
.HasForeignKey(p => p.CreatorId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
else,you can use:
modelBuilder.Entity<Room>()
.HasOne(r => r.Creator)
.WithMany(u => u.Rooms)
.HasForeignKey(p => p.CreatorId)
.OnDelete(DeleteBehavior.SetNull);
For more details,you can see this article: The Fluent API OnDelete Method.

Entity Framework 6 fluent api delete cascade on n:n relationship [duplicate]

I want to disable cascade deletes for a link table with entity framework code-first. For example, if many users have many roles, and I try to delete a role, I want that delete to be blocked unless there are no users currently associated with that role. I already remove the cascade delete convention in my OnModelCreating:
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
...
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
And then I set up the user-role link table:
modelBuilder.Entity<User>()
.HasMany(usr => usr.Roles)
.WithMany(role => role.Users)
.Map(m => {
m.ToTable("UsersRoles");
m.MapLeftKey("UserId");
m.MapRightKey("RoleId");
});
Yet when EF creates the database, it creates a delete cascade for the foreign key relationships, eg.
ALTER TABLE [dbo].[UsersRoles] WITH CHECK ADD CONSTRAINT [FK_dbo.UsersRoles_dbo.User_UserId] FOREIGN KEY([UserId])
REFERENCES [dbo].[User] ([UserId])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[UsersRoles] WITH CHECK ADD CONSTRAINT [FK_dbo.UsersRoles_dbo.Role_RoleId] FOREIGN KEY([RoleId])
REFERENCES [dbo].[Role] ([RoleId])
ON DELETE CASCADE
GO
How can I stop EF generating this delete cascade?
I got the answer. :-) Those cascade deletes were being created because of ManyToManyCascadeDeleteConvention. You need to remove this convention to prevent it from creating cascade deletes for link tables:
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
I believe that turning off ManyToManyCascadeDeleteConvention globally is not a wise option. Instead, it's better to turn it off only for the concerned table.
This can be achieved through editing the generated migration file, for property cascadeDelete. For example:
AddForeignKey("dbo.UsersRoles", "UserId", "dbo.User", "UserId", cascadeDelete: false);
I agree with Ebram Khalil that turning it off for a single table is a good option. I like to stick as close to the automatically built migrations as I can, however, so I would set it up in OnModelCreating:
modelBuilder.Entity<User>()
.HasMany(usr => usr.Roles)
.WithMany(role => role.Users)
.Map(m => {
m.ToTable("UsersRoles");
m.MapLeftKey("UserId");
m.MapRightKey("RoleId");
})
.WillCascadeOnDelete(false);
I believe this preserves the delete going the other direction, so if both needed to be blocked (makes sense in this example) a similar call would need to be made starting with Entity<User>(Role)
Of course, this comes ages after the question was asked. So it may not have been valid in 2012.
This works for me in EFCore 6.0.1 and MySql, according to ms docs.
Note: Don't forget to regenerate your migration files after this.
// In your dbContext class
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<User>()
.HasMany(usr => usr.Roles)
.WithMany(role => role.Users)
.OnDelete(DeleteBehavior.Restrict);
}

EF Core: Possible FK cycles or multiple cascade paths [duplicate]

I have a Booking class that has a booking contact (a Person) and a set of navigation properties (People) that links through a join table to another set of navigation properties (Bookings) in Person. How do I generate the Booking table with cascading deletes enabled for the booking contact relationship? When I leave it out of the fluent API code (default setting of cascade delete enabled) I get the following error message from migration:
Introducing FOREIGN KEY constraint
'FK_dbo.BookingPeople_dbo.People_PersonID' on table 'BookingPeople'
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.
modelBuilder.Entity<Person>()
.HasMany<Booking>(s => s.aBookings)
.WithRequired(s => s.Contact)
.HasForeignKey(s => s.ContactId);
modelBuilder.Entity<Booking>()
.HasMany(t => t.People)
.WithMany(t => t.Bookings)
.Map(m => {
m.ToTable("BookingPeople");
m.MapLeftKey("BookingID");
m.MapRightKey("PersonID");
});
The problem is you have multiple paths of cascade deletes that could end trying to delete the same row in the BookingPeople table in DB.
You can avoid such ambiguous delete paths by either disabling cascading delete in the one-to-many relationship using Fluent API:
modelBuilder.Entity<Booking>()
.HasRequired(s => s.Contact)
.WithMany(s => s.aBookings)
.HasForeignKey(s => s.ContactId)
.WillCascadeOnDelete(false);
Or by defining the relationship as optional (with a nullable foreign key, but you can not configure the relationship with cascade delete using Fluent Api).
modelBuilder.Entity<Booking>()
.HasOptional(s => s.Contact)
.WithMany(s => s.aBookings)
.HasForeignKey(s => s.ContactId);// ContactId is a nullable FK property
Also, you can remove the cascade delete convention by using:
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Or in the case of the many-to-many relationship:
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
If you need to delete all the Bookings asociated with a Person when you delete it, my advice is configure the one-to-many relationship as optional, and override the SaveChanges method:
public override int SaveChanges()
{
Bookings.Local
.Where(r => r.ContactId == null)
.ToList()
.ForEach(r => Bookings.Remove(r));
return base.SaveChanges();
}
If a foreign key on the dependent entity is nullable, Code First does not set cascade delete on the relationship, and when the principal is deleted the foreign key will be set to null. This way, you can find the orphans in the SaveChanges method and delete them

Entity Framework Code first: cycles or multiple cascade paths

I have a Booking class that has a booking contact (a Person) and a set of navigation properties (People) that links through a join table to another set of navigation properties (Bookings) in Person. How do I generate the Booking table with cascading deletes enabled for the booking contact relationship? When I leave it out of the fluent API code (default setting of cascade delete enabled) I get the following error message from migration:
Introducing FOREIGN KEY constraint
'FK_dbo.BookingPeople_dbo.People_PersonID' on table 'BookingPeople'
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.
modelBuilder.Entity<Person>()
.HasMany<Booking>(s => s.aBookings)
.WithRequired(s => s.Contact)
.HasForeignKey(s => s.ContactId);
modelBuilder.Entity<Booking>()
.HasMany(t => t.People)
.WithMany(t => t.Bookings)
.Map(m => {
m.ToTable("BookingPeople");
m.MapLeftKey("BookingID");
m.MapRightKey("PersonID");
});
The problem is you have multiple paths of cascade deletes that could end trying to delete the same row in the BookingPeople table in DB.
You can avoid such ambiguous delete paths by either disabling cascading delete in the one-to-many relationship using Fluent API:
modelBuilder.Entity<Booking>()
.HasRequired(s => s.Contact)
.WithMany(s => s.aBookings)
.HasForeignKey(s => s.ContactId)
.WillCascadeOnDelete(false);
Or by defining the relationship as optional (with a nullable foreign key, but you can not configure the relationship with cascade delete using Fluent Api).
modelBuilder.Entity<Booking>()
.HasOptional(s => s.Contact)
.WithMany(s => s.aBookings)
.HasForeignKey(s => s.ContactId);// ContactId is a nullable FK property
Also, you can remove the cascade delete convention by using:
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Or in the case of the many-to-many relationship:
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
If you need to delete all the Bookings asociated with a Person when you delete it, my advice is configure the one-to-many relationship as optional, and override the SaveChanges method:
public override int SaveChanges()
{
Bookings.Local
.Where(r => r.ContactId == null)
.ToList()
.ForEach(r => Bookings.Remove(r));
return base.SaveChanges();
}
If a foreign key on the dependent entity is nullable, Code First does not set cascade delete on the relationship, and when the principal is deleted the foreign key will be set to null. This way, you can find the orphans in the SaveChanges method and delete them

Categories

Resources