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

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.

Related

DbContext Update vs EntityState Modified

What is the difference between _context.Entry(entity).State = EntityState.Modified and _context.Entity.Update(entity) in ASP.NET EF Core? For example:
[HttpPut]
public async Task<ActionResult<Student>> PutStudent(Student student)
{
**_context.Entry(student).State = EntityState.Modified;**
await _context.SaveChangesAsync();
return student;
}
[HttpPut]
public async Task<ActionResult<Student>> PutStudent(Student student)
{
**_context.Student.Update(student);**
await _context.SaveChangesAsync();
return student;
}
Setting an entity's state toModified will mark all of the entity's scalar properties as modified, meaning that SaveChanges will generate an update statement updating all mapped table fields except the key field(s).
Not asked, but a single property can also be marked as Modified:
_context.Entry(student).Property(s => s.Name).IsModified = true;
This will also set the entity's state as modified.
The Update method is quite different, see the docs:
Begins tracking the given entity (...)
For entity types with generated keys if an entity has its primary key value set then it will be tracked in the Modified state. If the primary key value is not set then it will be tracked in the Added state. This helps ensure new entities will be inserted, while existing entities will be updated. An entity is considered to have its primary key value set if the primary key property is set to anything other than the CLR default for the property type.
This can be very convenient in disconnected scenarios in which new and updated entities are attached to a context. EF will figure out which entities are Added and which are Modified.
Another difference is that the Update method also traverses nested entities. For example, if Exams is a collection in the Student class, updating a Student will also mark its Exams as Modified, or Added where their keys aren't set.
Not documented, but worth mentioning, if a Student and its Exams are attached as Unchanged then the Update method will only set the Student's state to Modified, not the Exams.

Foreign key ID must be nullable, although I have CascadeOnDelete

I have two tables, which have a 1:n relationship. I used the following EF Code First relationship definition:
modelBuilder.Entity<MyPrimary>()
.HasMany(x => x.MyOthers)
.WithRequired()
.HasForeignKey(x => x.primary_id)
.WillCascadeOnDelete();
Note that primary_id is a non-nullable column, that's also why I set the relationship to .WithRequired() - a MyOther needs to have a MyPrimary and cannot exist on its own.
Now I have the following code:
myPrimary.MyOthers.Clear();
ctx.SaveChanges();
And I receive the following exception:
System.InvalidOperationException : 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.
Why? Shouldn't all the MyOther instances in myPrimary.MyOthers be cascade-deleted and therefore the non-nullable FK should not be a problem?
When you call .Clear() EF trying to unassign MyOther from MyPrimary, It is not smart enough to figure out that you want to delete MyOther records. Maybe after clear you will decide to add these MyOther records to another MyPrimary record. So you should to mark these records as deleted explicitly. You can write method similar to this
public void MarkForDeleteItems<T>(ICollection<T> collection) where T : class
{
foreach (var collectionItem in collection.ToList())
{
ctx.Entry(collectionItem).State = EntityState.Deleted;
}
}
And then use it
MarkForDeleteItems(myPrimary.MyOthers);
ctx.SaveChanges();
Also WillCascadeOnDelete meaning that when you delete MyPrimary record than database will delete all MyOthers records which related to MyPrimary.

Code first refuses to cascade delete an entity

I have a Parent entity with a 0-to-Many relationship to a Child entity. When I delete a parent I want it to automatically cascade delete all the attached children. Attempting to do so gives the following exception...
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.
I do not understand the message talking about setting a null reference. Because of the cascade delete the children will be removed so there is no need to set any children to have null references.
My two simple entities are defined as...
public class Parent
{
[Key]
public int Id { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
[Key]
public int Id { get; set; }
public int ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
With the following mapping...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Child>()
.HasRequired(x => x.Parent)
.WithMany(x => x.Children)
.HasForeignKey(x => x.ParentId)
.WillCascadeOnDelete(true);
}
Looking at the generated database, it does indeed mark the foreign key relationship as cascade on delete. So the database schema looks fine. The actual code that throws the error...
Parent p = context.Parents.Find(id);
context.Entry<Parent>(p).State = System.Data.Entity.EntityState.Deleted;
context.SaveChanges();
Any ideas?
Your error is being generated by Entity Framework, not by your database.
The problem is that you're using context.Entry<Parent>(p).State = EntityState.Deleted instead of context.Parents.Remove(p). The main difference is that calling Remove on the parent handles setting the entity state to deleted for any children with a required relationship that are loaded into the context. State = EntityState.Deleted does not.
In your case, you probably have some related Child entities loaded into the context and EF is complaining about the orphaned children. If you didn't have any children loaded, the DELETE statement would be sent to the database, and the database would handle the cascade delete normally.
Using DbSet.Remove would be preferable.
See this for more details:
Delete parent with children in one to many relationship
You code matches this well so I think you configured it correctly: https://msdn.microsoft.com/en-us/data/jj591620.aspx#CascadeDelete
I don't know why this doesn't work, cascade has always worked for me so far. I suspect some configuration. I always disable lazy loading, proxy generation and validation so that could be some this to look at.
Here are some things to try:
1) Can you delete the item manually from the database and does the cascade work then?
2) Is this a sql error or an "app" error? I suspect it's an app error an that EF validation is somehow triggering. If it's from the db the exception should and SqlException. I usually check with Sql Profiler if the command get sent too.
3) If it's an app error try disabling EF's validation and see what happens.
ctx.Configuration.ValidateOnSaveEnabled = false;
4) When you use find does that load the children (might do if it's found in the local context) or does it have lazy loading enabled. Setting the state is recursive if I remember correctly and that could possibly mess up some things. You can check in the changetracker how many entities that are marked for deletion.

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.

Entity Framework .Remove() vs. .DeleteObject()

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)

Categories