Does anyone know what behavior should be expected by attempting to cascade deletes to child entities that are filtered via Map's Requires/HasValue? I'm curious if I can trust that EF(6) will only cascade the delete to children that make it through the filter, or whether the delete would also be pushed do children that are no longer "pending".
Below is my specific mapping configuration for said child entities.
modelBuilder.Entity<PendingReviewRecord>().Map(m => m.Requires("Pending").HasValue(true))
.HasRequired(t => t.Install).WithOptional(t => t.PendingReviewRecord).WillCascadeOnDelete(true);
My situation has some further complication in that I am implementing a soft-delete on the "Install" object.. but that is being accomplished via command tree interception so entity still feels it as a delete/removal and not some hacky field modification.
Related
I have some troubles using One to Many relationships with EntityFramework Core. When I delete a child object in the List property on the parent, this Child object is flagged as Modified by the ChangeTracker (and not Deleted). However, when I call SaveChanges on the context, this entity is well Deleted.
Of course, I could force the flag of the entity to Deleted but, I would prefer another solution because I'm using AutoMapper to update my entities. and I don't want to mix the AutoMapper mapping process and EntityFramework Context.
var parent = new Parent();
var child = new Child();
parent.Childs.Add(child);
await context.SaveChangesAsync();
// removing the first child
parent.Childs.RemoveAt(0);
// fails (Expected Deleted, got Modified)
Assert.Equal(EntityState.Deleted, context.Entry(child).State);
The best explanation of this behavior is contained inside one of the expected breaking changes in EF Core 3.0 - Cascade deletions now happen immediately by default:
Old behavior
Before 3.0, EF Core applied cascading actions (deleting dependent entities when a required principal is deleted or when the relationship to a required principal is severed) did not happen until SaveChanges was called.
New behavior
Starting with 3.0, EF Core applies cascading actions as soon as the triggering condition is detected. For example, calling context.Remove() to delete a principal entity will result in all tracked related required dependents also being set to Deleted immediately.
Why
This change was made to improve the experience for data binding and auditing scenarios where it is important to understand which entities will be deleted before SaveChanges is called.
The first section explains the current behavior and the last section explains why they are changing it - to help usage scenarios like yours.
With that being said, you should either apply the cascade option manually, or wait for EF Core 3.0 if you can afford that.
I'm trying to understand why Entity Framework Core 2's ChangeTracker sets the state of deleted entities to 'modified' if the deletion is done by removing the entity from a list in another entity.
Consider this example:
Class1
List<Class2> MyListOfClass2Objects
Class2
Both classes have their own DbSet in EF's DbContext. If I remove an object from the MyListOfClass2Objects list and then call DbContext.SaveChanges(), the state of the Class2 object in ChangeTracker is set to EntityState.Modified, and not EntityState.Deleted, which I would expect. If I delete the entity by calling Set<T>.Remove() the state in the ChangeTracker is EntityState.Deleted. In both cases however, the entity is removed from the database.
So, why does Entity Framework set different values in ChangeTracker depending on how you delete an entity even though it is really deleted in both cases?
Update: At the end (thanks to #Sebastian finding) it turns out to be a known issue with the current (at the time of writing) EF Core implementation, tracked originally by EF Core marks entities removed From child collections as modified #10093. The short explanation is:
because cascade deletes happens while SaveChanges is running which means in your code will not see this state as it looks at the state before SaveChanges has executed
and
Cascade delete happens as part of SaveChanges.
Fixup of non-deleted entities to no longer reference deleted entities happens after SaveChanges.
The currently associated action item is Allow delete fixup and cascade timing to be configured #10114, unfortunately in backlog, i.e. unknown if and when will be addressed.
Original:
Because when you remove an entity from the corresponding DbSet, you are telling EF that you want to delete it. But when you remove it from the parent entity collection, if the relationship is optinal (as it seems to be in your case), EF treats that as attempt to disassociate the child from the parent (same as setting child.Parent to null), hence it sets the FK and parent navgigation property to null and marks the entity as Modified. If you really want to delete it, you should use the first method.
All this is explained in the Removing relationships section of the documentation:
You can remove a relationship by setting a reference navigation to null, or removing the related entity from a collection navigation.
Removing a relationship can have side effects on the dependent entity, according to the cascade delete behavior configured in the relationship.
By default, for required relationships, a cascade delete behavior is configured and the child/dependent entity will be deleted from the database. For optional relationships, cascade delete is not configured by default, but the foreign key property will be set to null.
It still is not possible to configure a relation with the ON DELETE SET NULL rule using Entity Framework code first. As a workaround you have to load all the related entities in memory and then on deletion of the parent entity EF will issue SQL commands to set their foreign keys to Null.
This, while it is trivial to implement this yourself using something like:
protected override void Seed(Context context)
{
context.Database.ExecuteSqlCommand("ALTER TABLE dbo.Guests DROP CONSTRAINT Guest_PreferredLanguage");
context.Database.ExecuteSqlCommand("ALTER TABLE dbo.Guests ADD CONSTRAINT Guest_PreferredLanguage FOREIGN KEY (LanguageID) REFERENCES dbo.Languages(LanguageID) ON UPDATE NO ACTION ON DELETE SET NULL");
}
(Example take from this post.)
I can see no problems with this approach: Loaded child entities will remain in sync with the database because EF will update (set to null) their foreign keys and Reference properties, and that other records in the database are affected does no harm as they have not been loaded anyway.
So, why is this feature still missing then? Is there some hidden snag?
The feature is probably not implemented because normally changes only affect the objects which are actually in the unit of work. Cascades are not scalable.
And I also think soft deletes are better in most cases. Maybe thats something for you?
You might also want to look into Domain Driven design. That also covers the correct use of units of work (with aggregates).
Btw your solution edits the database in the seed method. It might be better to do that a Up() method of a migration.
This feature is available in Microsoft.EntityFrameworkCore Version=3.1.10.0 onwards.
modelBuilder.Entity<Guests>()
.HasOne<Languages>(g => g.Language)
.WithMany(l => l.Guests)
.HasForeignKey(g => g.LanguageID)
.IsRequired(false)
.OnDelete(DeleteBehavior.SetNull);
Note, DeleteBehavior.SetNull
I'm using EF5 POCO entities (database first not code first, if that makes a difference). Say I have the following (hypothetical) entity types:-
Store
Customer (with a FK of StoreId)
Product (with a FK of StoreId)
Order (with FKs of CustomerId & ProductId)
A Store has many Customers and Products, and an Order is basically a M-M join between Customer and Product.
A typical scenario of mine involves retrieving a Store entity plus all of its related Customers, Products and Orders. With this hierarchy now in memory the user may choose to delete a Customer via the UI. As part of this action, I also want the Customer's related Orders to be deleted.
Is it sufficient to do:-
store.Customers.Remove(customerToDelete);
Or is it also necessary to "break the link" between the customer's Orders and their Products, e.g.:-
foreach (var order in customerToDelete.Orders)
{
order.Product.Orders.Remove(order);
}
Note that the changes won't necessarily be persisted back to the DB straight away. The user may continue to work with this in-memory Store hierarchy, and only persist the changes upon clicking a "Save" button later. Therefore, if they continue to "browse" this hierarchy of entities via the UI, and drill down into a particular product, I don't want them to see orders relating to the removed customer. Hence I suspect I have to do the above foreach to wipe all trace of that customer's orders?
Not sure if it's related to my question, but can someone explain what the "End1 OnDelete" EDMX properties do? I understand how cascade deletes work in SQL, but not sure where this EDMX cascade option fits in?
The context will not automatically remove the orders owned by the Customer entity. Generally you will have a read only context for search results shown in your UI:
DbSet.Where(x => x == y).AsNoTracking()
So there will be no automatic refresh when you delete a customer from your data source as you will be using a fresh context for updates. This pattern is generally accepted now as you won't want to deal with the inevitable exceptions caused by updating your data source from a context containing stale entities. It's best to create a fresh context for a unit of work/scoped set of operations.
You can however set up a cascade delete using the Fluent API
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>()
.HasRequired(t => t.Department)
.WithMany(t => t.Courses)
.HasForeignKey(d => d.DepartmentID)
.WillCascadeOnDelete(false);
}
This will do the job of removing the orders from the data source, but your orders visible through the UI will need to be refreshed/removed somehow. If your orders data is quite large then consider lazy loading, as you can defer loading your related entities until they are actually accessed which is great so long as you know what you are doing and don't accidently round trip when iterating through collections for example. Your approach of removing the entities manually after a delete is also fine, however, don't forget to delete the relationship/link separately, as this will hang around and possibly cause an issue.
I am using EclipseLink 2.3.3. with a data model with about 100 entities. I have a Java class mapped to each database table using annotations.
I have two use cases to implement. One is that a new record enters the system that hits about 60-75 of the tables. For this case, I want merge and persist to cascade, so that I can just merge the top level object and have that cascade to all related entities.
Another use case is that I need to insert a collection of individual objects, often one from each of a bunch of different tables. In this case I don't want the cascading merge, because I need to have control over the insertions. If I have cascade enabled, merging the first object might or might not merge the other objects, depending on if or how they are related, so I'd rather explicitly merge each of them.
So essentially, I want cascading merge and persist in one situation, but not another. So if I include the cascade annotations in the mapped classes, I need to selectively disable the cascading for certain operations; or, if I turn off cascading in the mapped classes, I would like to enable cascading for certain operations.
So far I am not finding any way to selectively turn on or off cascading for a particular operation. There is a CascadePolicy class but that seems to only be used with queries. There are dynamic entities, and I was thinking perhaps I could use that to do something like create a dynamic entity from an existing entity and turn off the cascading behavior on that entity's relationships and somehow use that for the merge, but I have not been able to find the right API for that.
So I am wondering if there is a better answer somewhere that I'm overlooking? Thanks for any information.
I'm not certain about what level of control you are after, especially in the case that you mention you want to insert individual objects. From the sounds of it, cascade merge is exactly what you want for your Entity object tree in the first case for use with the EntityManager.merge. Merge called on an entity will check if it is new or not, and update or insert as appropriate. Marking relationships as cascade merge will allow finding new objects and having them inserted.
The second case though where you want to handle individual insertions, why not exclude the cascade persist option on mappings and just call EntityManager.persist on the objects you want to insert? Persist then will not cascade, so only the entity you call em.persist on will get inserted. Relationships will be used just to set the foreignkey values - though you might want to leave them nulled out and set them later as part of larger merge calls. Both sides of bidirectional relationships need to be maintained, and if the other side is exists and doesn't get merged, its relationship changes are not stored.
If that isn't what you want, EclipseLink has native API on the UnitOfWork (the EntityManager essentially wraps a UnitOfWork for transactional work) that allows you to specify the merge policy. See mergeClone, deepMergeClone and shallowMergeClone on UnitOfWork, which essentially use CASCADE_ALL_PARTS, CASCADE_PRIVATE_PARTS and NO_CASCADE respectively as the merge policies, while the JPA merges use CASCADE_BY_MAPPING.