Entity Framework .Remove() vs. .DeleteObject() - c#

You can remove an item from a database using EF by using the following two methods.
EntityCollection.Remove Method
ObjectContext.DeleteObject Method
The first is on the EntityCollection and the second on the ObjectContext.
When should each be used?
Is one prefered over the other?
Remove() returns a bool and DeleteObject() returns void.

It's not generally correct that you can "remove an item from a database" with both methods. To be precise it is like so:
ObjectContext.DeleteObject(entity) marks the entity as Deleted in the context. (It's EntityState is Deleted after that.) If you call SaveChanges afterwards EF sends a SQL DELETE statement to the database. If no referential constraints in the database are violated the entity will be deleted, otherwise an exception is thrown.
EntityCollection.Remove(childEntity) marks the relationship between parent and childEntity as Deleted. If the childEntity itself is deleted from the database and what exactly happens when you call SaveChanges depends on the kind of relationship between the two:
If the relationship is optional, i.e. the foreign key that refers from the child to the parent in the database allows NULL values, this foreign will be set to null and if you call SaveChanges this NULL value for the childEntity will be written to the database (i.e. the relationship between the two is removed). This happens with a SQL UPDATE statement. No DELETE statement occurs.
If the relationship is required (the FK doesn't allow NULL values) and the relationship is not identifying (which means that the foreign key is not part of the child's (composite) primary key) you have to either add the child to another parent or you have to explicitly delete the child (with DeleteObject then). If you don't do any of these a referential constraint is violated and EF will throw an exception when you call SaveChanges - the infamous "The relationship could not be changed because one or more of the foreign-key properties is non-nullable" exception or similar.
If the relationship is identifying (it's necessarily required then because any part of the primary key cannot be NULL) EF will mark the childEntity as Deleted as well. If you call SaveChanges a SQL DELETE statement will be sent to the database. If no other referential constraints in the database are violated the entity will be deleted, otherwise an exception is thrown.
I am actually a bit confused about the Remarks section on the MSDN page you have linked because it says: "If the relationship has a referential integrity constraint, calling the Remove method on a dependent object marks both the relationship and the dependent object for deletion.". This seems unprecise or even wrong to me because all three cases above have a "referential integrity constraint" but only in the last case the child is in fact deleted. (Unless they mean with "dependent object" an object that participates in an identifying relationship which would be an unusual terminology though.)

If you really want to use Deleted, you'd have to make your foreign keys nullable, but then you'd end up with orphaned records (which is one of the main reasons you shouldn't be doing that in the first place). So just use Remove()
ObjectContext.DeleteObject(entity) marks the entity as Deleted in the context. (It's EntityState is Deleted after that.) If you call SaveChanges afterwards EF sends a SQL DELETE statement to the database. If no referential constraints in the database are violated the entity will be deleted, otherwise an exception is thrown.
EntityCollection.Remove(childEntity) marks the relationship between parent and childEntity as Deleted. If the childEntity itself is deleted from the database and what exactly happens when you call SaveChanges depends on the kind of relationship between the two:
A thing worth noting is that setting .State = EntityState.Deleted does not trigger automatically detected change. (archive)

Related

Why does EF Core delete an entity when a required relationship is nulled out?

I'm using EF Core 3.1 and I have a model with entities Contract and ContractType. The Contract has a required (non-nullable) ContractTypeId field and a ContractType navigation property. If I select a Contract object from the DB, make sure its ContractType is loaded in memory (I use lazy loading) and then set the ContractType property to null, the Contract object is marked in the ChangeTracker as deleted (and it's actually deleted from the DB if I call SaveChanges).
I understand that that's in line with the documentation for the Cascade delete behavior:
https://learn.microsoft.com/en-us/ef/core/saving/cascade-delete
I'm having trouble understanding the logic behind it. It seems dangerous that if you just mess up a required relationship (the navigation property was being set to null because of a bug in my code), EF decides to delete your main entity (in this case Contract). Am I missing something? Maybe the real question here is why is Cascade Delete the default behavior?
According to the EF Team this is by-design but can be changed by setting DeleteOrphansTiming to Never:
https://github.com/dotnet/efcore/issues/21840
The delete behavior can also be set to something other than cascade delete.

Prevent nulling of optional foreign key when deleting parent

I have an optional relationship between Table A and Table B where A is the parent and B is the optional child. If I have only A loaded in my DbContext SQL Server will throw me an error that I cannot delete A because of a relationship with B, this is what I want.
However if I have A and the relation to B loaded in the DbContext Entity Framework will set the relationship on B to null which prevents the error from SQL Server. How can I prevent Entity Framework from setting my relationship to null when deleting the parent A?
I want the error to happen as I want to prevent deletion of A when a relationship exists, I don't want Entity Framework to 'fix' the issue for me.

Entity Framework DbContext .Remove(obj) vs .Entry(obj).State = EntityState.Deleted

I'm using EF Code first. a simple model:
item { public int Id {set; get;},... ,ICollection<ItemImages> {set; get;} }
itemImages {
public int Id {set; get; },
public int ItemId {set; get; }
, ... ,
public Item Item {set; get; }
}
ItemConfig:EntityTypeConfiguration<Item>
{
//some config statement;
//...
// mark child delete when parent delete: waterfall delete.
HasRequired(rs => rs.ItemCat).WithMany(rs => rs.Items).HasForeignKey(rs => rs.ItemCatId).WillCascadeOnDelete(true);
}
when delete entity by Remove(), it delete item and related child (item images records) well.
_db.Item.Remove(DeleteThisObj);
_db.SaveChanges();
but when mark it to delete:
_db.Entry(DeleteThisObj).State = EntityState.Deleted;
_db.SaveChanges();
get error:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
If you really want to use Deleted, you'd have to make your foreign keys nullable, but then you'd end up with orphaned records (which is one of the main reasons you shouldn't be doing that in the first place). So just use Remove()
ObjectContext.DeleteObject(entity) marks the entity as Deleted in the context. (It's EntityState is Deleted after that.) If you call SaveChanges afterwards EF sends a SQL DELETE statement to the database. If no referential constraints in the database are violated the entity will be deleted, otherwise an exception is thrown.
EntityCollection.Remove(childEntity) marks the relationship between parent and childEntity as Deleted. If the childEntity itself is deleted from the database and what exactly happens when you call SaveChanges depends on the kind of relationship between the two:
If the relationship is optional, i.e. the foreign key that refers from the child to the parent in the database allows NULL values, this foreign will be set to null and if you call SaveChanges this NULL value for the childEntity will be written to the database (i.e. the relationship between the two is removed). This happens with a SQL UPDATE statement. No DELETE statement occurs.
If the relationship is required (the FK doesn't allow NULL values) and the relationship is not identifying (which means that the foreign key is not part of the child's (composite) primary key) you have to either add the child to another parent or you have to explicitly delete the child (with DeleteObject then). If you don't do any of these a referential constraint is violated and EF will throw an exception when you call SaveChanges - the infamous "The relationship could not be changed because one or more of the foreign-key properties is non-nullable" exception or similar.
If the relationship is identifying (it's necessarily required then because any part of the primary key cannot be NULL) EF will mark the childEntity as Deleted as well. If you call SaveChanges a SQL DELETE statement will be sent to the database. If no other referential constraints in the database are violated the entity will be deleted, otherwise an exception is thrown.
A thing worth noting is that setting .State = EntityState.Deleted does not trigger automatically detected change.

Multiplicity constraint violations with optional-required EF Code First + Fluent relationship?

For some reason I had my made my mind a while back on an EF 6 project that I would try to avoid naming foreign keys. I defined much of the model without testing it incrementally and so I have been running into multiplicity and incomplete Fluent API definition issues:
A relationship from the 'User_InternalAuth' AssociationSet is in the
'Deleted' state. Given multiplicity constraints, a corresponding
'User_InternalAuth_Target' must also in the 'Deleted' state.
In one case, here is the code:
nModelBuilder.Entity<User>()
.HasOptional<InternalAuth>(u => u.InternalAuth)
.WithRequired(a => a.User)
.WillCascadeOnDelete(true);
My understanding is that it is saying:
The entity User
Has an optional property InternalAuth of type InternalAuth
On the other end, InternalAuth has a required property User, so that all InternalAuths have Users but Users may or may not have an `InternalAuth.
If the User gets deleted, so does his InternalAuth if he has one (does this override an optional behavior of treating optionals like nullables?)
However when I try to delete a User I receive an exception about the multiplicity of some association between InternalAuth and User.
Is it true that if EF understands the multiplicity of a relationship there is a way for it to provide it a unique column name for it so there is a canonical naming convention?
If so, do you ever really need to define foreign keys explicitly by annotating the model or through Fluent API?
If not, is it a worthwhile or advisable thing that I should keep trying to avoid it? (I'm thinking along the lines of migrating the data model, database administration, any EF quirks)
Why does attempting to delete the relationship above violate a multiplicity constraint? What else does it need to know?
assuming that
You can configure cascade delete on a relationship by using the WillCascadeOnDelete method. If a foreign key on the dependent entity is not nullable, then Code First sets cascade delete on the relationship. 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.
My guess is the following : the FK is nullable so the fact to set it to null with the required constraint causes the rise of the exception.
One solution is to put the FK in the PK, that is add, in InternalAuth, the FK to User in the PK. Doing this will mark the entity as deleted when setting a part of his PK to null.

Still deleting referenced objects even with foreign key constraint (no action)

I'm using entity framework 5 model first.
I have some entities in my model and most of them have one-to-many relationships with "no action" foreign key constraint on delete and update.
But I'm still able to delete father and child objects with no errors (on EF4 I used to get an exception warning that I cannot delete an object because there's another referencing it)
Part of the code generated by EF5 model first:
...
... Create all tables...
...
... Create all foreign key constraints ...
...
-- Creating foreign key on [TEstTela_ID] in table 'TEstPermissao'
ALTER TABLE [dbo].[TEstPermissao]
ADD CONSTRAINT [FK_TEstTelaTEstPermissao]
FOREIGN KEY ([TEstTela_ID])
REFERENCES [dbo].[TEstTela]
([ID])
ON DELETE NO ACTION ON UPDATE NO ACTION;
....
Delete Object Code:
...
EstContextDB CurrentContext = new EstContextDB(); // inherits from DbContext
CurrentContext.Set<TEstTela>().Remove(currentTEstTelaEntity);
CurrentContext.SaveChanges(); /* Exception should be thrown here
because at least one TEstPermissao object references this
currentTEstTelaEntity but it still delete the object without
errors or exceptions, and plus the TEstPermissao object
that references this currentTEstTelaEntity gets its reference as 'null' */
The problem doesn't have to do with cascading delete. You try to delete the parent TEstTelaEntity and EF sets the foreign key from the child TEstPermissao to this parent TEstTelaEntity to null (apparently the relationship is optional) and then sends an UPDATE statement for the child and a DELETE statement for the parent to the database. If cascading delete would kick in the child would be deleted as well, not only the parent. The result is consistent and valid: You just have a TEstPermissao entity in the database now without any reference to a TEstTelaEntity.
The foreign key is set to null only in the case that the child is loaded and attached to the context when you delete the parent. Otherwise you would indeed get the exception about a constraint violation you are expecting. (I believe this difference between attached vs. detached children is the same in EF 4 and EF 5.)
If you really don't want to delete a parent as long as it has any children, check with appropriate code if the parent has children or not in order to decide if Remove should be called.

Categories