Why is EF cascade delete not working on 0 to 1 relationships? - entity-framework

I have the following table defined through code-first:
public class Action
{
public int ActionId { get; set; }
public int? EmailMessageId { get; set; }
public virtual EmailMessage EmailMessage { get; set; }
}
public class EmailMessage
{
public int EmailMessageId { get; set; }
public string Content { get;set;
}
When an action is created with a corresponding EmailMessage, deleting the action doesn't remove the entry in EmailMessage. It seems EF only creates a cascade delete on 1-many relationships. In this case the relationship is 0 or 1 relationship which has no cascade delete set by default.
I then added the fluent configuration:
modelBuilder
.Entity<Action>()
.HasOptional(x =>x.EmailMessage)
.WithMany()
.HasForeignKey(x=>x.EmailMessageId)
.WillCascadeOnDelete(true);
Which seems to correctly set the CASCADE DELETE when viewing the schema in Management Studio. But when I remove the row from Action manually from the db, the row in EmailMessage remains.
What exactly am I doing wrong here?
I thought I might be getting somewhere when I used 'WithOptionalDependent()' in the configuration. However when I looked at the schema, it had introduced "EmailMessage_EmailMessageId", when I already had EmailMessageId in the table?
Can anyone advise what is wrong here?

The cascade delete is designed to remove the child when the parent is deleted, not the other way around. In this case, the Action is the child associated with the foreign-key of the EmailMessage parent. So deleting the Action will not affect the EmailMessage, but deleting the EmailMessage should cascade delete to the Action.

I have just realized when using "contextName.Entry(parentStudySession).State = System.Data.Entity.EntityState.Deleted;" to delete some parent with a few children..I talk for EF 6.3, EF doesnt't delete the children for us, even didn't delete the parent itself even though we have got cascading on delete constraint. Instead, it gives an 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 ... "
Instead, when I use "contextName.StudySession.Remove(parentStudySession);" ,
EF 6.3 successfully deletes the PARENT and the CHILDREN... I have cascade on delete constraint.
T.Adakoğlu

Related

EF Core Cascading Referential Integrity with DeleteBehavior.Restrict does not work well

I have one sql server database created with code first. There are two tables that have a one to many relationship. The database works and is created well.
In sql server if I try to delete one of the classification records, I get an error (referencial integrity restriction). This is how I want it to work. But in ef core, if I delete one classification dbset.Remove(classification), the classification is deleted and the classification in the customer is set to null.
I think this is how it should work for DeleteBehavior.ClientSetNull.
There is a note "Changes in EF Core 2.0" in https://learn.microsoft.com/en-us/ef/core/saving/cascade-delete that explains the DeleteBehavior function.
I have the next records:
Classification:
Id Name
1 General
2 Others
Customers:
Id Name IdClassification
1 Customer A 1
2 Customer B 2
3 Customer C <null>
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
...
public int? IdClassification { get; set; }
public Classification Classification { get; set; }
}
public class Classification
{
public int Id { get; set; }
public string Name { get; set; }
...
public ICollection<Customer> Customers { get; set; }
}
public class Context : DbContext
{
public virtual DbSet<Classification> Classifications { get; set; }
public virtual DbSet<Customer> Customers { get; set; }
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Classification>(
entity =>
{
entity.HasKey(e => e.Id);
});
modelBuilder.Entity<Customer>(
entity =>
{
entity.HasKey(e => e.Id);
entity.HasIndex(e => e.IdClassification);
...
// Claves foráneas
entity.HasOne(c => c.Classification)
.WithMany(x => x.Customers)
.HasForeignKey(x => x.IdClassification)
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("FK_Customer_Classification");
});
}
}
Is there a way to prevent deletion of classification records in ef core? (I don't want to check if there is any customer record that is linked to the classification because I have to use the classification with more tables).
Thanks in advance.
EF Core 3.0 added several new values to the DeleteBehavior enum - ClientCascade, NoAction, ClientNoAction. Unfortunately the documentation is not updated (except for enum values in API reference), and only the ClientNoAction is mentioned in the 3.0 Breaking Changes - DeleteBehavior.Restrict has cleaner semantics:
Old behavior
Before 3.0, DeleteBehavior.Restrict created foreign keys in the database with Restrict semantics, but also changed internal fixup in a non-obvious way.
New behavior
Starting with 3.0, DeleteBehavior.Restrict ensures that foreign keys are created with Restrict semantics--that is, no cascades; throw on constraint violation--without also impacting EF internal fixup.
Why
This change was made to improve the experience for using DeleteBehavior in an intuitive manner, without unexpected side-effects.
Mitigations
The previous behavior can be restored by using DeleteBehavior.ClientNoAction.
More info is contained in the associated tracking issue - 12661: Update DeleteBehavior to be more consistent and understandable
Honestly even after reading all that, I don't find it cleaner, but even more confusing. Restrict seems to be obsoleted and replaced with NoAction, which regardless of what have been said actually does set loaded related entities navigation property/FK to null, thus causing SET NULL database behavior as you already experienced.
After trying all of them, the only option which does what you expect is the aforementioned ClientNoAction:
Note: it is unusual to use this value. Consider using ClientSetNull instead to match the behavior of EF6 with cascading deletes disabled.
For entities being tracked by the DbContext, the values of foreign key properties in dependent entities are not changed when the related principal entity is deleted. This can result in an inconsistent graph of entities where the values of foreign key properties do not match the relationships in the graph.
If the database has been created from the model using Entity Framework Migrations or the EnsureCreated() method, then the behavior in the database is to generate an error if a foreign key constraint is violated.
regardless of their note at the beginning.
With all that being said, simply replace Restrict with ClientNoAction and the issue will be solve. No database migration is needed because this change affects only the client behavior.
Well, the classification entity needs correct initialization, suppose to delete restriction rule.
modelBuilder.Entity<Classification>()
.HasKey(e => e.Id)
.HasMany(e => e.Customers)
.WithOne(e => e.Classification)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(true);
Hope this helps.

Code first Many to Many Constraint error

I have the following fluent mapping:
modelBuilder.Entity<User>()
.HasMany<Device>(user => user.Devices)
.WithMany(sv => sv.Users)
.Map(userDevice =>
{
userDevice.MapLeftKey("UserId");
userDevice.MapRightKey("DeviceId");
userDevice.ToTable("UserDevice"));
});
Inside User:
public virtual ICollection<Device> Devices { get; set; }
Inside Device:
public virtual ICollection<User> Users { get; set; }
Now I receive the following error:
{"Introducing FOREIGN KEY constraint
'FK_dbo.UserDevice_dbo.Device_DeviceId' on table 'UserDevice' may
cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION
or ON UPDATE NO ACTION, or modify other FOREIGN KEY
constraints.\r\nCould not create constraint. See previous errors."}
I would love to set "WillCascadeOnDelete(false)" but this option is not available on "WithMany()"
I also found the following solution but I do not want to disable cascade delete completely:
Entity Framework Many to Many Cascade Delete Issue

Does Entity Framework support when deleted a record and set the foreign key to null?

I have a model like this:
public class Account
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public Account Parent { get; set; }
}
and I add the following configuration:
this.HasOptional(item => item.Parent)
.WithMany()
.HasForeignKey(item => item.ParentId)
.WillCascadeOnDelete(true);
and then I got following error message:
Assembly Initialization method
UnitTest.Biz.Accounting.TestInitializer.Init threw exception.
System.Data.SqlClient.SqlException:
System.Data.SqlClient.SqlException: Introducing FOREIGN KEY constraint
'FK_dbo.acct_Account_dbo.acct_Account_ParentId' on table
'acct_Account' 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.
I had read the documents of the EF, but I don't understand where is wrong in my code...
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
http://msdn.microsoft.com/en-us/data/jj591620#CascadeDelete
Self Referencing Table
SQL server doesn't allow self referencing table to have cascading delete.
As you can see, SQL Server noticed that your cascade operation is
cyclic, and it does not allow this type of cascading. This is true not
only for a self-referencing table, but also for any chain of
relationships between tables in which the cascading operations have a
potential to be cyclical.
Source
That's why in EF you can't set cascading delete on self referencing entity.
.WillCascadeOnDelete(true); // Not allowed.
If a foreign key on the dependent entity is nullable, Code First does
not set cascade delete on the relationship
is about the default convention of EF which you can override by fluent api. If you have following code,
public int? ParentId { get; set; } // <--- nullable
public Account Parent { get; set; }
EF will mark the cascading delete as false by default, even if you don't configure it with WillCascadeOnDelete(false), which actually means that the foreign key cascading delete in the database will be set as ON DELETE NO ACTION.
And currently EF doesn't support ON DELETE SET NULL unless you have custom query to drop and re-add the constraint, check this post.
and when the principal is deleted the foreign key will be set to null.
This has been explained in this post which means only if the children are loaded into the context.

One-To-One relationship with fluent api. A Hacky way?

EF 4.3.1. I have defined User and Box entities. Each box may or may not be assigned to a user.
What I'd like to achieve is to have a OwnBox property in User class, and an Owner property in Box class.
in Database, I have defined OwnerId foreignkey in Boxes (Boxes.OwnerId has relation with Users.UserId).
To define the relationship with fluent api, I have defined the following classes:
public partial class User
{
public int UserId {get; set;}
public virtual Box OwnBox { get; set; }
}
public partial class Box
{
public int? OwnerId { get; set; }
public virtual User User { get; set; }
}
Then in my Mapping class for Box, I have defined the relations as follows:
this.HasOptional(t => t.User).WithOptionalDependent(d => d.OwnBox).
Map(m => m.MapKey("OwnerId")).WillCascadeOnDelete(true);
But by firing up the project, I got the error:
Schema specified is not valid. Errors: (56,6) : error 0019: Each
property name in a type must be unique. Property name 'OwnerId' was
already defined.
So I had to tell EF to forget about the OwnerId column first:
this.Ignore(t => t.OwnerId);
Now the project works fine. But I'm still doubtful if this is a good approach and will everything work fine on CRUD operations with foreign key associations.
First of all, this is not one-to-one relationship. In one-to-one relationship the foreign key must be a primary key.
I believe in your scenario the situation can happen:
User = { UserID = 2 }
Box1 = { UserID = 2 }
Box2 = { UserID = 2 }
Nothing stops you from doing that, but which box should be returned when you do that:
User.OwnBox, Box1 or Box2?
EF can deal with that using Independent Association. It will create foreign key, hidden from your POCO class. You can specify the name of the column using MapKey as you did. However, because you also created a property called OnwerID, just as the column used with MapKey, the EF has a problem as two properties are mapped to the same column.
When you use ignore, the POCO OwnerID property is ignored by EF so that fixes the problem of two properties, however, the OwnderID value never gets saved or read to the database. Because EF just ignores it.
Thanks for your question, I have learnt a lot thanks to this.

Entity Framework - Clear a Child Collection

I have run into an interesting problem with Entity Framework and based on the code I had to use to tackle it I suspect my solution is less than ideal. I have a 1-to-Many relationship between Table A and Table B where entities in TableB have a reference to TableA. I have a scenario where I want to simultaneously delete all children of a row in TableA and I thought this could be achieve by simply clearing the collection:
Entity.Children.Clear()
Unfortunately, when I attempted to save changes this produced as a Foreign Key violation.
A relationship is being added or
deleted from an AssociationSet
'FK_EntityB_EntityA'. With cardinality
constraints, a corresponding 'EntityB'
must also be added or deleted.
The solution I came up with was to manually delete object via the entity context's DeleteObject(), but I just know this logic I am using has got to be wrong.
while (collection.Any())
Entities.DeleteObject(collection.First());
For one, the fact that I had to use a Where() loop seems far less than ideal, but I suppose that's purely a semantic assessment on my part. In any case, is there something wrong with how I am doing this, or is there perhaps a better way to clear a child entity collection of an entity such that Entity Framework properly calls a data store delete on all of the removed objects?
Clear() removes the reference to the entity, not the entity itself.
If you intend this to be always the same operation, you could handle AssociationChanged:
Entity.Children.AssociationChanged +=
new CollectionChangeEventHandler(EntityChildrenChanged);
Entity.Children.Clear();
private void EntityChildrenChanged(object sender,
CollectionChangeEventArgs e)
{
// Check for a related reference being removed.
if (e.Action == CollectionChangeAction.Remove)
{
Context.DeleteObject(e.Element);
}
}
You can build this in to your entity using a partial class.
You can create Identifying relationship between parent and child entities and EF will delete child entity when you delete it from parent's collection.
public class Parent
{
public int ParentId {get;set;}
public ICollection<Child> Children {get;set;}
}
public class Child
{
public int ChildId {get;set;}
public int ParentId {get;set;}
}
Mapping configuration:
modelBuilder.Entity<Child>().HasKey(x => new { x.ChildId, x.ParentId });
modelBuilder.Entity<Parent>().HasMany(x => x.Children).WithRequired().HasForeignKey(x => x.ParentId);
Trick: When setting up the relationship between Parent and Child, you'll HAVE TO create a "composite" key on the child. This way, when you tell the Parent to delete 1 or all of its children, the related records will actually be deleted from the database.
To configure composite key using Fluent API:
modelBuilder.Entity<Child>().HasKey(t => new { t.ParentId, t.ChildId });
Then, to delete the related children:
var parent = _context.Parents.SingleOrDefault(p => p.ParentId == parentId);
var childToRemove = parent.Children.First(); // Change the logic
parent.Children.Remove(childToRemove);
// you can delete all children if you want
// parent.Children.Clear();
_context.SaveChanges();
Done!