I have an entry removal problem with the EntityFramework and a many-to-many relationship for the same entity. Consider this simple example:
Entity:
public class UserEntity {
// ...
public virtual Collection<UserEntity> Friends { get; set; }
}
Fluent API Configuration:
modelBuilder.Entity<UserEntity>()
.HasMany(u => u.Friends)
.WithMany()
.Map(m =>
{
m.MapLeftKey("UserId");
m.MapRightKey("FriendId");
m.ToTable("FriendshipRelation");
});
- Am I correct, that it is not possible to define the
Cascade Delete
in Fluent API? What is the best way to delete a
UserEntity
, for instanceFoo
?It looks for me now, I have to
Clear
theFoo
'sFriends
Collection, then I have to load all otherUserEntities
, which containFoo
inFriends
, and then removeFoo
from each list, before I removeFoo
fromUsers
. But it sounds too complicateda.Is it possible to access the relational table directly, so that I can remove entries like this
// Dummy code var query = dbCtx.Set("FriendshipRelation").Where(x => x.UserId == Foo.Id || x.FriendId == Foo.Id); dbCtx.Set("FriendshipRelation").RemoveRange(query);
Thank you!
Update01:
My best solution for this problem for know is just to execute the raw sql statement before I call
SaveChanges
:dbCtx.Database.ExecuteSqlCommand( "delete from dbo.FriendshipRelation where UserId = @id or FriendId = @id", new SqlParameter("id", Foo.Id));
But the disadvantage of this, is that, if
SaveChanges
failes for some reason, theFriendshipRelation
are already removed and could not be rolled back. Or am I wrong?
Problem 1
The answer is quite simple:
Entity Framework cannot define cascade delete when it doesn't know which properties belong to the relationship.
In addition, in a many:many relationship there is a third table, that is responsible for managing the relationship. This table must have at least 2 FKs. You should configure the cascade delete for each FK, not for the "entire table".
The solution is create the
FriendshipRelation
entity. Like this:Now, you have to change the
UserEntity
. Instead of a collection ofUserEntity
, it has a collection ofUserFriendship
. Like this:Let's see the mapping:
Generated Migration:
To retrieve all user's friends:
All of this works fine. However, there is a problem in that mapping (which also happens in your current mapping). Suppose that "I" am a
UserEntity
:When I retrieve my
Friends
property, it returns "John", "Ann", but not "Richard". Why? because Richard is the "maker" of the relationship not me. TheFriends
property is bound to only one side of the relationship.Ok. How can I solve this? Easy! Change your
UserEntity
class:Update the Mapping:
There are no migrations necessary.
To retrieve all user's friends:
Problem 2
Yes, you have to iterate the collection and remove all children objects. See my answer in this thread Cleanly updating a hierarchy in Entity Framework
Following my answer, just create a
UserFriendship
dbset:Now you can retrieve all friends of a specific user id, just delete all of them in one shot, and then remove the user.
Problem 3
Yes, it is possible. You have a
UserFriendship
dbset now.Hope it helps!
1) I don't see any straightforward way to control the cascade on the many-to-many relationships using FluentApi.
2) The only available way I can think of to control that is by using the
ManyToManyCascadeDeleteConvention
, which I guess is enabled by default, at least it is for me. I just checked one of my migrations including a many-to-many relationship and indeed thecascadeDelete: true
is there for both keys.EDIT: Sorry, I just found that the
ManyToManyCascadeDeleteConvention
does not cover the self-referencing case. This related question's answer says thatSo you end up having to have a custom delete code (like the sql command that you already have) and execute it in a transaction scope.
3) You should not be able to access that table from the context. Usually the table created by a many-to-many relationship is a by-product of the implementation in a relational DBMS and is considered a weak table respective to the related tables, which means that its rows should be cascade-deleted if one of the related entities is removed.
My advice is that, first, check if your migration is setting your table foreign keys to cascade delete. Then, if for some reason you need to restrict the deletion of a record which has related records in the many-to-many relationship, then you just check for it in your transactions.
4) In order to do that, if you really want to (FluentApi enables by default
ManyToManyCascadeDeleteConvention
), is to enclose the sql command and your SaveChanges in a transaction scope.