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
Related
I need to create a relationship between two tables that share a common column of type string - how can I do that?
This is what I did. Does this work?
You need to specify the foreign key (preferably always) and the principal key (if it is different to the primary key).
In order to use a principal key other than Primary Key, you need to create a UNIQUE Constraint. In EF you can do this by specifying HasIndex and IsUnique, otherwise you will get an error because the selected principal key is not a unique column.
See this example:
//HaweyTajer is the principal table
modelbuilder.Entity<HaweyTajer>(entity =>
{
//Mark the primary key
entity.HasKey(x => x.Id);
//Build the relation (Consider adding OnDelete())
entity.HasMany(x => x.Certivicates)
.WithOne(x => x.HaweyTaker)
.HasPrincipalKey(x => x.AZbaraNum)
.HasForeignKey(x => x.AZbaraNum);
//Build the index on the column inside the principal table
entity.HasIndex(x => x.AZbaraNum)
.IsUnique();
});
You might as well change the column type to not nvarchar(250) not null or add a filter to the index with HasFilter().
Reference: https://learn.microsoft.com/en-us/ef/ef6/modeling/code-first/fluent/relationships
Hope this helps.
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?
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);
}
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
I have a Code First Model and I'm trying to delete all rows which was referenced to a certain ID, the problem is, it was working before I use Migrations. this is my Code :
var query = context.user.Include(p => p.area).
Include(p => p.city).Include(p => p.city.state).
Include(p => p.city.state.country).
Include(p => p.favorites).Include(p => p.ads).FirstOrDefault(a => a.id_user == result);
context.user.Remove(query);
context.Entry(query).State = EntityState.Deleted;
context.SaveChanges();
It just delete the user table, not all!
Either turn cascade delete on for user so when it is deleted, all its children will be deleted as well. Like this:
modelBuilder.Entity<user>()
.WillCascadeOnDelete(true);
If you do not want that, you can use RemoveRange like this:
context.user.RemoveRange(query);
Or you can loop through each child and delete them individually like this:
foreach (var record in context.user.area)
{
user.area.Remove(record);
}
And last thing, you can remove one of your includes since you do not need it.
var query = context.user.Include(p => p.area).
Include(p => p.city.state.country).
Include(p => p.favorites).Include(p => p.ads).FirstOrDefault(a => a.id_user == result);
EDIT 1
I am not sure what kind of relationships you have setup between your entities. WillCascadeOnDelete(true) should delete the children when the parent is deleted. Here is an example:
modelBuilder.Entity<Course>()
.HasRequired(t => t.Department)
.WithMany(t => t.Courses)
.HasForeignKey(d => d.DepartmentID)
.WillCascadeOnDelete(true);
What the above is saying is this: Course must have a Department, and a Department has many courses, and a Course has a foreign-key named DepartmentID. When a Department is deleted, cascade the deletion: meaning delete all the courses (children) of this department. Here the relationship is one department can have many courses: one to many.
If a child object has a non-nullable foreign-key then EF will delete the child when the parent is deleted: cascade on delete will be set by default. If the child can have a nullable foreign key, then the child will not be deleted when the parent is deleted.
So from the above we can tell that the deletion strategy EF uses depends on how you have setup your model.
Your query returns one user. Therefore, if you want to delete all the children, they will be deleted automatically if you have built your model that way.
If you have not, then delete the children one by one. If a user has one child of area then do this:
context.user.area.Remove(query.user.area);
If a user has many area then use the foreach strategy I have shown above.