EntityFramework CodeFirst:对同一个表的多对多关系进行CASCADE DELETE
本文关键字:关系 DELETE CASCADE CodeFirst 同一个 EntityFramework | 更新日期: 2023-09-27 18:13:36
我对同一个实体的EntityFramework和多对多关系有一个条目删除问题。考虑这个简单的例子:
实体:public class UserEntity {
// ...
public virtual Collection<UserEntity> Friends { get; set; }
}
Fluent API配置:
modelBuilder.Entity<UserEntity>()
.HasMany(u => u.Friends)
.WithMany()
.Map(m =>
{
m.MapLeftKey("UserId");
m.MapRightKey("FriendId");
m.ToTable("FriendshipRelation");
});
- 我是正确的,它是不可能在Fluent API定义
Cascade Delete
? 删除
UserEntity
(例如Foo
)的最佳方法是什么?它现在寻找我,我必须
Clear
Foo
的Friends
集合,然后我必须加载所有其他UserEntities
,其中包含Friends
中的Foo
,然后从每个列表中删除Foo
,然后从Users
中删除Foo
。但是听起来太复杂了。是否可以直接访问关系表,以便我可以删除这样的条目
// Dummy code var query = dbCtx.Set("FriendshipRelation").Where(x => x.UserId == Foo.Id || x.FriendId == Foo.Id); dbCtx.Set("FriendshipRelation").RemoveRange(query);
谢谢!
Update01:
对于这个问题,我最好的解决方案是在我调用
SaveChanges
之前执行原始sql语句:dbCtx.Database.ExecuteSqlCommand( "delete from dbo.FriendshipRelation where UserId = @id or FriendId = @id", new SqlParameter("id", Foo.Id));
但是这样做的缺点是,如果
SaveChanges
由于某种原因失败,FriendshipRelation
已经被删除并且无法回滚。还是我错了?
问题1
答案很简单:
实体框架不能定义级联删除当它不知道哪些属性属于关系。
此外,在many:many关系中还有第三个表,负责管理该关系。此表必须至少有2个fk。您应该为每个FK配置级联删除,而不是为"整个表"配置级联删除。
解决方法是创建FriendshipRelation
实体。这样的:
public class UserFriendship
{
public int UserEntityId { get; set; } // the "maker" of the friendship
public int FriendEntityId { get; set; }´ // the "target" of the friendship
public UserEntity User { get; set; } // the "maker" of the friendship
public UserEntity Friend { get; set; } // the "target" of the friendship
}
现在,您必须更改UserEntity
。它不是UserEntity
的集合,而是UserFriendship
的集合。这样的:
public class UserEntity
{
...
public virtual ICollection<UserFriendship> Friends { get; set; }
}
让我们看看映射:
modelBuilder.Entity<UserFriendship>()
.HasKey(i => new { i.UserEntityId, i.FriendEntityId });
modelBuilder.Entity<UserFriendship>()
.HasRequired(i => i.User)
.WithMany(i => i.Friends)
.HasForeignKey(i => i.UserEntityId)
.WillCascadeOnDelete(true); //the one
modelBuilder.Entity<UserFriendship>()
.HasRequired(i => i.Friend)
.WithMany()
.HasForeignKey(i => i.FriendEntityId)
.WillCascadeOnDelete(true); //the one
生成迁移:CreateTable(
"dbo.UserFriendships",
c => new
{
UserEntityId = c.Int(nullable: false),
FriendEntityId = c.Int(nullable: false),
})
.PrimaryKey(t => new { t.UserEntityId, t.FriendEntityId })
.ForeignKey("dbo.UserEntities", t => t.FriendEntityId, true)
.ForeignKey("dbo.UserEntities", t => t.UserEntityId, true)
.Index(t => t.UserEntityId)
.Index(t => t.FriendEntityId);
检索所有用户的好友:
var someUser = ctx.UserEntity
.Include(i => i.Friends.Select(x=> x.Friend))
.SingleOrDefault(i => i.UserEntityId == 1);
所有这些都可以正常工作。然而,在该映射中存在一个问题(也发生在当前映射中)。假设"I"是UserEntity
:
- 我向John发出好友请求——John接受了
- 我向Ann发出好友请求——Ann接受了
- Richard加我为好友——我接受
当我检索我的Friends
属性时,它返回"John","Ann",但不是"Richard"。为什么?因为理查德是这段关系的"创造者",而不是我。Friends
属性只绑定到关系的一侧。
Ok。我怎么解决这个问题?简单!更改UserEntity
类:
public class UserEntity
{
//...
//friend request that I made
public virtual ICollection<UserFriendship> FriendRequestsMade { get; set; }
//friend request that I accepted
public virtual ICollection<UserFriendship> FriendRequestsAccepted { get; set; }
}
更新映射:
modelBuilder.Entity<UserFriendship>()
.HasRequired(i => i.User)
.WithMany(i => i.FriendRequestsMade)
.HasForeignKey(i => i.UserEntityId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<UserFriendship>()
.HasRequired(i => i.Friend)
.WithMany(i => i.FriendRequestsAccepted)
.HasForeignKey(i => i.FriendEntityId)
.WillCascadeOnDelete(false);
不需要迁移。
检索所有用户的好友:
var someUser = ctx.UserEntity
.Include(i => i.FriendRequestsMade.Select(x=> x.Friend))
.Include(i => i.FriendRequestsAccepted.Select(x => x.User))
.SingleOrDefault(i => i.UserEntityId == 1);
问题2
是的,您必须迭代集合并删除所有子对象。查看我的答案在这个线程干净地更新实体框架中的层次结构
根据我的回答,只需创建一个UserFriendship
dbset:
public DbSet<UserFriendship> UserFriendships { get; set; }
现在您可以检索特定用户id的所有朋友,只需一次性删除所有朋友,然后删除该用户。
3
问题是的,有可能。你现在有一个UserFriendship
dbset。
希望有帮助!
1)我没有看到任何使用FluentApi来控制多对多关系上的级联的直接方法。
2)我能想到的唯一可用的方法来控制是通过使用ManyToManyCascadeDeleteConvention
,我猜这是默认启用的,至少对我来说是这样。我刚刚检查了我的一个迁移,包括一个多对多关系,实际上cascadeDelete: true
是为两个键。
编辑:对不起,我刚刚发现ManyToManyCascadeDeleteConvention
不包括自引用情况。这个相关问题的答案说
你会收到这个错误信息,因为在SQL Server中,一个表不能在所有由DELETE或UPDATE语句启动的级联引用操作的列表中出现多次。例如,级联引用操作树必须只有一条路径到级联引用操作树上的特定表。
所以你最终不得不有一个自定义的删除代码(就像你已经拥有的sql命令),并在事务范围内执行它。
3)你应该不能从上下文中访问那个表。通常,由多对多关系创建的表是关系DBMS中实现的副产品,相对于相关表而言被认为是弱表,这意味着如果删除一个相关实体,则应该级联删除其行。
我的建议是,首先,检查您的迁移是否将表外键设置为级联删除。然后,如果出于某种原因,您需要限制删除在多对多关系中具有相关记录的记录,那么您只需在事务中检查它。
4)为了做到这一点,如果你真的想(FluentApi默认启用ManyToManyCascadeDeleteConvention
),是把sql命令和你的SaveChanges在一个事务范围