草庐IT

c# - Entity Framework 代码优先 : CASCADE DELETE for same table many-to-many relationship

coder 2024-06-02 原文

我在 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");
    });
  1. 我是否正确,无法在 Fluent API 中定义 Cascade Delete
  2. 删除 UserEntity(例如 Foo)的最佳方法是什么?

    它现在寻找我,我必须清除 FooFriends 集合,然后我必须加载所有其他UserEntities,其中包含 Friends 中的 Foo,然后从每个列表中删除 Foo,然后再删除 Foo 来自用户。但这听起来太复杂了。

  3. 是否可以直接访问关系表,以便我可以删除这样的条目

    // Dummy code
    var query = dbCtx.Set("FriendshipRelation").Where(x => x.UserId == Foo.Id || x.FriendId == Foo.Id);
    dbCtx.Set("FriendshipRelation").RemoveRange(query);
    

谢谢!

Update01:

  1. 我知道这个问题的最佳解决方案是在调用 SaveChanges 之前执行原始 sql 语句:

    dbCtx.Database.ExecuteSqlCommand(
        "delete from dbo.FriendshipRelation where UserId = @id or FriendId = @id",
        new SqlParameter("id", Foo.Id));
    

    但这样做的缺点是,如果 SaveChanges 由于某种原因失败,FriendshipRelation 已经被删除并且无法回滚。还是我错了?

最佳答案

问题1

答案很简单:

Entity Framework 在不知道哪些属性属于关系时无法定义级联删除。

此外,在多对多关系中还有第三个表,负责管理关系。此表必须至少有 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);

检索所有用户的 friend :

var someUser = ctx.UserEntity
    .Include(i => i.Friends.Select(x=> x.Friend))
    .SingleOrDefault(i => i.UserEntityId == 1);

一切正常。但是,该映射存在问题(这也发生在您当前的映射中)。假设“我”是一个 UserEntity:

  • 我向 John 提出了好友请求 -- John 接受了
  • 我向 Ann 发出了好友请求 -- Ann 接受了
  • Richard 向我提出了好友请求 -- 我接受了

当我检索我的 Friends 属性时,它返回“John”、“Ann”,但没有返回“Richard”。为什么?因为理查德是这段关系的“缔造者”,而不是我。 Friends 属性仅绑定(bind)到关系的一侧。

好的。我该如何解决这个问题?简单!更改您的 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);

无需迁移。

检索所有用户的 friend :

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

是的,您必须迭代集合并删除所有子对象。在这个线程中查看我的答案 Cleanly updating a hierarchy in Entity Framework

按照我的回答,只需创建一个 UserFriendship 数据库集:

public DbSet<UserFriendship> UserFriendships { get; set; }

现在您可以检索特定用户 id 的所有好友,只需一次性删除所有好友,然后删除该用户。

问题3

是的,这是可能的。您现在有一个 UserFriendship 数据库集。

希望对您有所帮助!

关于c# - Entity Framework 代码优先 : CASCADE DELETE for same table many-to-many relationship,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32655959/

有关c# - Entity Framework 代码优先 : CASCADE DELETE for same table many-to-many relationship的更多相关文章

随机推荐