I have an optional relationship from a Child to a Parent class. I would like to get an exception on SubmitChanges when the parent object is marked for deletion if there are still children around that reference it.
The configuration I've tried is this (there is no navigation property from the parent to the children):
modelBuilder.Entity<Child>()
.HasOptional<Parent>(child => child.Parent)
.WithMany()
.HasForeignKey(child => child.ParentId)
.WillCascadeOnDelete(false);
Like this EF sets the children's ParentId property to null when deleting the parent, which is not what I want.
It works if the relationship is configured as required:
modelBuilder.Entity<Child>()
.HasRequired<Parent>(child => child.Parent)
.WithMany()
.HasForeignKey(child => child.ParentId)
.WillCascadeOnDelete(false);
This throws an exception, which would be the desired behaviour. But the relationship has to be optional. Is this possible with EF 4.3.1 using Code First?
No. That is the difference between optional and required. Required = must have a principal record and if you delete the principal record without cascading you will get an exception. Optional = doesn't need a principal record and if you delete the principal record without cascading the FK is set to null.
If you need anything else you must handle it yourselves.
Related
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.
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 this for an NHibernate mapping:
public AnswerSet_AnswerMap() {
Table("DB.AnswerSet_Answer");
Id(x => x.AnswerSet_AnswerId);
References(x => x.Answer, "BaseAnswerID").LazyLoad(Laziness.NoProxy);
References(x => x.AnswerSet, "AnswerSetID").Fetch.Join();
Map(x => x.Format);
}
It's for a junction table between an "AnswerSet" and an "Answer". The .LazyLoad(Laziness.NoProxy) on the answer reference is necessary in our application, but it prevents an AnswerSet_Answer object from being soft deleted in the database (it remains in the database unchanged). Does anyone know why this is happening?
Did you try adding a cascade? Cascade.DeleteAllOrphan ?
NHibernate Cascades:
Entities has associations to other objects, this may be an association to a single item (many-to-one) or an association to a collection (one-to-many, many-to-any).
At any rate, you are able to tell NHibernate to automatically traverse an entity's associations, and act according to the cascade option. For instance, adding an unsaved entity to a collection with save-update cascade will cause it to be saved along with its parent object, without any need for explicit instructions on our side.
Here is what each cascade option means:
none - do not do any cascades, let the users handles them by themselves.
save-update - when the object is saved/updated, check the associations and save/update any object that require it (including save/update the associations in many-to-many scenario).
delete - when the object is deleted, delete all the objects in the association.
delete-orphan - when the object is deleted, delete all the objects in the association. In addition to that, when an object is removed from the association and not associated with another object (orphaned), also delete it.
all - when an object is save/update/delete, check the associations and save/update/delete all the objects found.
all-delete-orphan - when an object is save/update/delete, check the associations and save/update/delete all the objects found. In additional to that, when an object is removed from the association and not associated with another object (orphaned), also delete it.
But cascade will delete your orpahnd record,not "soft-delete" it.
for soft deletion look into this link : Soft Deletes In NHibernate
How do I override the default convention for the foreign key column in EF4 to specify a different column name?
For example, I have one entity with a property called Parent that references to other one of the same type.
EF4 tries to resolve the relation by looking for the foreign key named EntityId, but in my DB schema it is Entity_Id. How do I tell EF that the FK column name is not EntityId?
I've tried the following:
modelBuilder.Entity<SomeEntity>()
.HasOptional(m => m.Parent)
.WithMany()
.IsIndependent()
.Map(m => m.MapKey(k => k.Id, "Entity_Id")));
But I get an exception saying: Sequence contains more than one matching element.
Any help on this?
Thanks!
First: Upgrade to EF 4.1 RTW. CTP 5 is outdated and contains potentially many bugs which are fixed now.
If you have done the upgrade the following should work:
modelBuilder.Entity<SomeEntity>()
.HasOptional(m => m.Parent)
.WithMany()
.Map(c => c.MapKey("Entity_Id"));