EntityFramework CodeFirst:CASCADE DELETE用于同一个表的多对多关系

时间:2015-09-18 15:38:10

标签: c# entity-framework ef-code-first entity-framework-6 code-first

我有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 Foo的{​​{1}}集合,然后我必须加载所有其他Friends,其中包含{{1}在UserEntities中移除Foo之前,在Friends中删除Foo,然后从Foo移除Users。但这听起来太复杂了。

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

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

    Update01:

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

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

      但缺点是,如果由于某种原因导致SaveChanges失效,FriendshipRelation已被删除且无法回滚。或者我错了?

2 个答案:

答案 0 :(得分:12)

问题1

答案很简单:

当实体框架不知道哪些属性属于该关系时,无法定义级联删除。

此外,在很多:很多关系中,有第三个表,负责管理关系。此表必须至少有2个FK。您应该为每个FK配置级联删除,而不是为&#34;整个表&#34;。

解决方案是创建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);

所有这一切都很好。但是,映射存在问题(在当前映射中也会发生)。假设&#34;我&#34;我是UserEntity

  • 我向约翰提出了一个朋友请求 - 约翰接受了
  • 我向Ann - Ann加入了
  • 的朋友请求
  • 理查德向我发了一个朋友请求 - 我接受了

当我检索我的Friends财产时,它会返回&#34; John&#34;,&#34; Ann&#34;,但不会&#34; Richard&#34;。为什么?因为理查德是&#34;制造商&#34;关系不是我。 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);

问题2

是的,您必须迭代集合并删除所有子对象。请参阅此帖子Cleanly updating a hierarchy in Entity Framework

中的答案

根据我的回答,只需创建一个UserFriendship dbset:

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

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

问题3

是的,有可能。你现在有一个UserFriendship dbset。

希望它有所帮助!

答案 1 :(得分:1)

1)我没有看到使用FluentApi控制多对多关系级联的任何直接方法。

2)我能想到控制的唯一可行方法是使用ManyToManyCascadeDeleteConvention,我猜这是默认启用的,至少对我来说是这样。我刚检查了一次迁移,包括多对多关系,实际上两个密钥都有cascadeDelete: true

编辑:对不起,我刚发现ManyToManyCascadeDeleteConvention没有涵盖自我引用案例。这个related question的答案说明了

  

您收到此错误消息,因为在SQL Server中,表不能在由DELETE或UPDATE语句启动的所有级联引用操作的列表中出现多次。例如,级联引用操作树必须只有一条到级联引用操作树上特定表的路径。

所以你最终必须有一个自定义删除代码(比如你已经拥有的sql命令)并在transaction scope中执行它。

3)您应该无法从上下文访问该表。通常,由多对多关系创建的表是关系DBMS中实现的副产品,并且被认为是相应表的表,这意味着它的行应该是如果删除了其中一个相关实体,则级联删除。

我的建议是,首先,检查您的迁移是否将表外键设置为级联删除。然后,如果由于某种原因您需要限制删除具有多对多关系中相关记录的记录,那么您只需在事务中检查它。

4)为了做到这一点,如果你真的想(默认情况下FluentApi启用ManyToManyCascadeDeleteConvention),就是将sql命令和SaveChanges包含在事务范围内。