[英]EntityFramework CodeFirst: CASCADE DELETE for same table many-to-many relationship
我有EntityFramework的條目刪除問題和同一實體的多對多關系。 考慮這個簡單的例子:
實體:
public class UserEntity {
// ...
public virtual Collection<UserEntity> Friends { get; set; }
}
流暢的API配置:
modelBuilder.Entity<UserEntity>()
.HasMany(u => u.Friends)
.WithMany()
.Map(m =>
{
m.MapLeftKey("UserId");
m.MapRightKey("FriendId");
m.ToTable("FriendshipRelation");
});
Cascade Delete
? 刪除UserEntity
的最佳方法是UserEntity
,例如Foo
?
現在找我,我一定要Clear
了Foo
的Friends
集合,然后我必須加載所有其他UserEntities
,其中含有Foo
的Friends
,然后取出Foo
從每個列表,之前我刪除Foo
從Users
。 但這聽起來太復雜了。
是否可以直接訪問關系表,以便我可以刪除這樣的條目
// 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
已被刪除且無法回滾。 還是我錯了?
答案很簡單:
當實體框架不知道哪些屬性屬於該關系時,它無法定義級聯刪除。
此外,在許多:很多關系中,有第三個表,負責管理關系。 此表必須至少有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
。 它有一個UserFriendship
集合,而不是UserEntity
的集合。 像這樣:
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);
所有這一切都很好。 但是,映射存在問題(在當前映射中也會發生)。 假設“我”是UserEntity
:
當我檢索我的Friends
屬性時,它返回“John”,“Ann”,但不返回“Richard”。 為什么? 因為理查德是關系的“制造者”而不是我。 Friends
屬性僅限於關系的一側。
好。 我怎么解決這個問題? 簡單! 更改您的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);
是的,您必須迭代集合並刪除所有子對象。 請參閱此線程中的答案干凈地更新實體框架中的層次結構
根據我的回答,只需創建一個UserFriendship
dbset:
public DbSet<UserFriendship> UserFriendships { get; set; }
現在,您可以檢索特定用戶ID的所有朋友,只需一次刪除所有朋友,然后刪除該用戶。
對的,這是可能的。 您現在擁有UserFriendship
數據庫集。
希望能幫助到你!
1)我沒有看到使用FluentApi控制多對多關系級聯的任何直接方法。
2)我能想到的唯一可以控制的方法是使用ManyToManyCascadeDeleteConvention
,我猜這是默認啟用的,至少對我來說是這樣。 我剛檢查了一次遷移,包括多對多關系,實際上是cascadeDelete: true
兩個密鑰都是cascadeDelete: true
。
編輯:對不起,我剛發現ManyToManyCascadeDeleteConvention
不包括自引用案例。 這個相關問題的答案說明了這一點
您收到此錯誤消息,因為在SQL Server中,表不能在DELETE或UPDATE語句啟動的所有級聯引用操作的列表中出現多次。 例如,級聯引用操作樹必須只有一條到級聯引用操作樹上的特定表的路徑。
因此,您最終必須擁有自定義刪除代碼(如您已有的sql命令)並在事務范圍內執行它。
3)您應該無法從上下文訪問該表。 通常,由多對多關系創建的表是關系DBMS中的實現的副產品,並且被認為是與相關表相對應的弱表,這意味着如果其中一個表,其行應該被級聯刪除。相關實體已被刪除。
我的建議是,首先,檢查您的遷移是否將表外鍵設置為級聯刪除。 然后,如果由於某種原因您需要限制刪除具有多對多關系中相關記錄的記錄,那么您只需在事務中檢查它。
4)為了做到這一點,如果你真的想(默認情況下FluentApi啟用ManyToManyCascadeDeleteConvention
),則將sql命令和SaveChanges包含在事務范圍內。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.