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

    它现在寻找我,我必须Clear FooFriends集合,然后我必须加载所有其他UserEntities,其中包含Friends中的Foo,然后从每个列表中删除Foo,然后从Users中删除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:

  • 对于这个问题,我最好的解决方案是在我调用SaveChanges之前执行原始sql语句:

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

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

  • EntityFramework CodeFirst:对同一个表的多对多关系进行CASCADE DELETE

    问题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在一个事务范围