繁体   English   中英

如何正确设置 ApplicationUser 和 FriendRequest 实体以允许级联删除

[英]How to properly setup ApplicationUser and FriendRequest entities to allow for cascade deleting

我有传统的ApplicationUser (IdentityUser),该用户可以向另一个ApplicationUser发送好友请求。 我目前有以下通用实体类:

public class ApplicationUser : IdentityUser
{
    public virtual List<DeviceToken> DeviceTokens { get; set; } = new List<DeviceToken>();
    public string DisplayName { get; set; }
}

public class FriendRequest
{
    public int Id { get; set; }
    public DateTime DateRequested { get; set; }
    public ApplicationUser Requester { get; set; }
    public ApplicationUser Receiver { get; set; }
}

我已经运行了数据库更新等,这工作正常。 但是,当我 go 进入我的 SQLServer 以尝试删除 ApplicationUser 时,它告诉我The DELETE statement conflicted with the REFERENCE constraint "FK_FriendRequest_AspNetUsers_RequesterId"

所以我决定实现从 ApplicationUser 到他们所属的朋友请求的级联删除流程。

我已经尝试过微软在此处配置级联删除的资源,但我无法弄清楚如何将其应用于我的案例:

builder.Entity<ApplicationUser>()
    .HasMany(e => e.FriendRequests)//No such property, no idea how to address
    .OnDelete(DeleteBehavior.ClientCascade);
  1. 如何设置此级联删除方案?

  2. 另外,如何向 ApplicationUser 添加一个属性,该属性引用它们所属的所有 FriendRequest,并确保 EFCore 知道我指的是现有的 FriendRequest 实体/表?


更新

遵循向 ApplicationUser 添加虚拟属性的建议方法,这将是前进的方向:

public class ApplicationUser : IdentityUser
{
    public virtual List<DeviceToken> DeviceTokens { get; set; } = new List<DeviceToken>();
    public string DisplayName { get; set; }
    public ICollection<FriendRequest> FriendRequests { get; }
}

builder.Entity<ApplicationUser>()
    .HasMany(u => u.FriendRequests)
    .WithOne(u => u.Requester)
    .OnDelete(DeleteBehavior.ClientCascade); //not sure about this

builder.Entity<ApplicationUser>()
    .HasMany(u => u.FriendRequests)
    .WithOne(u => u.Requester)
    .OnDelete(DeleteBehavior.ClientCascade); //not sure about this

您的 ApplicationUser 需要 2 个虚拟 ICollections。

public class ApplicationUser 
{
    public int Id { get; set; }
    public string DisplayName { get; set; }
    public virtual ICollection<FriendRequest> FriendRequestsAsRequestor { get; set; }
    public virtual ICollection<FriendRequest> FriendRequestsAsReceiver { get; set; }
}

public class FriendRequest
{
    public int Id { get; set; }
    public DateTime DateRequested { get; set; }
    public int RequestorId { get; set; }
    public ApplicationUser Requestor { get; set; }
    public int ReceiverId { get; set; }
    public ApplicationUser Receiver { get; set; }
}

public class ApplicationUserConfig : IEntityTypeConfiguration<ApplicationUser>
{
    public void Configure(EntityTypeBuilder<ApplicationUser> builder)
    {
        builder.HasMany(au => au.FriendRequestsAsRequestor)
            .WithOne(fr => fr.Requestor)
            .HasForeignKey(fr => fr.RequestorId)
            .OnDelete(DeleteBehavior.Cascade);

        builder.HasMany(au => au.FriendRequestsAsReceiver)
            .WithOne(fr => fr.Receiver)
            .HasForeignKey(fr => fr.ReceiverId)
            .OnDelete(DeleteBehavior.Cascade);
    }
}

利用:

void AddFriendRequest(int requestorId, int receiverId)
{
    var ctxt = new DbContext();
    FriendRequest fr = new FriendRequest
    {
        RequestorId = requestorId;
        ReceiverId = receiverId;
        DateRequested = DateTime.Now;
    }

    ctxt.FriendRequests.Add(fr);
    ctxt.SaveChanges();
}

List<FriendRequest> GetFriendRequests()
{
    var ctxt = new DbContext();
    return ctxt.FriendRequests
        .Include(fr => fr.Requestor)
        .Include(fr => fr.Receiver)
        .ToList(); 
}

ApplicationUser GetUserWithFriendRequests(int id)
{
    var ctxt = new DbContext();
    return ctxt.ApplicationUser
        .Include(au => au.FriendRequestsAsRequestor)
        .Include(au => au.FriendRequestsAsReceiver)
        .SingleOrDefault(au => au.Id == id);
}

我已经尝试过微软在此处配置级联删除的资源,但我无法弄清楚如何将其应用于我的案例:

builder.Entity<ApplicationUser>()
    .HasMany(e => e.FriendRequests)//No such property, no idea how to address
    .OnDelete(DeleteBehavior.ClientCascade);

DeleteBehavior的文档中:

ClientCascade:对于被 DbContext 跟踪的实体,在删除相关主体时将删除依赖实体。 如果数据库是使用实体框架迁移或 EnsureCreated() 方法从 model 创建的,则如果违反外键约束,则数据库中的行为将生成错误。

在这种情况下,确保级联删除的是客户端(.NET 应用程序)而不是数据库。 如果客户端未能进行级联删除(相关实体未跟踪),则数据库将生成您看到的错误。

也许DeleteBehavior.Cascade更适合您的代码优先方案:

级联:对于被 DbContext 跟踪的实体,在删除相关主体时将删除依赖实体。 如果已使用 Entity Framework Migrations 或 EnsureCreated() 方法从 model 创建数据库,则数据库中的行为与上述跟踪实体的行为相同。 请记住,某些数据库无法轻松支持此行为,尤其是在关系中存在循环的情况下,在这种情况下,最好使用 ClientCascade,这将允许 EF 对加载的实体执行级联删除,即使数据库不支持这一点。 这是所需关系的默认值。 也就是说,对于具有不可空外键的关系。

如果你试试这个,你 go 和这个 SQL 脚本迁移(我假设 SGBDR 是 SQL 服务器):

CREATE TABLE [ApplicationUser] (
    [Id] int NOT NULL IDENTITY,
    [DisplayName] nvarchar(max) NULL,
    CONSTRAINT [PK_ApplicationUser] PRIMARY KEY ([Id])
);
GO

CREATE TABLE [FriendRequests] (
    [Id] int NOT NULL IDENTITY,
    [DateRequested] datetime2 NOT NULL,
    [RequesterId] int NULL,
    [ReceiverId] int NULL,
    CONSTRAINT [PK_FriendRequests] PRIMARY KEY ([Id]),
    CONSTRAINT [FK_FriendRequests_ApplicationUser_ReceiverId] FOREIGN KEY ([ReceiverId]) REFERENCES [ApplicationUser] ([Id]) ON DELETE CASCADE,
    CONSTRAINT [FK_FriendRequests_ApplicationUser_RequesterId] FOREIGN KEY ([RequesterId]) REFERENCES [ApplicationUser] ([Id]) ON DELETE CASCADE
);
GO

当它应用时,这会产生这个错误:

Introducing FOREIGN KEY constraint 'FK_FriendRequests_ApplicationUser_RequesterId' on table 'FriendRequests' may cause cycles or multiple cascade paths.
Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.

我第一次看到这个错误时,我会用@onedaywhen 的回答来参考这个问题

SQL 服务器对级联路径进行简单计数,而不是尝试确定是否确实存在任何循环,而是假设最坏的情况并拒绝创建引用操作 (CASCADE)...

一个不完美的解决方案是使用DeleteBehavior.Cascade并确保在删除之前跟踪所有相关实体:

public class ApplicationUser
{
    public int Id { get; set; }
    public string DisplayName { get; set; }
    public ICollection<FriendRequest> RequestedRequests { get; set; }
    public ICollection<FriendRequest> RecevedRequests { get; set; }
}

public class FriendRequest
{
    public int Id { get; set; }
    public DateTime DateRequested { get; set; }
    public ApplicationUser Requester { get; set; }
    public ApplicationUser Receiver { get; set; }
}

public class MyDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
        optionsBuilder.UseSqlServer("***");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<FriendRequest>()
            .HasOne(r => r.Requester)
            .WithMany(u => u.RequestedRequests)
            .OnDelete(DeleteBehavior.ClientCascade);
        modelBuilder.Entity<FriendRequest>()
            .HasOne(r => r.Receiver)
            .WithMany(u => u.RecevedRequests)
            .OnDelete(DeleteBehavior.ClientCascade);
    }

    public DbSet<ApplicationUser> Users { get; set; }
    public DbSet<FriendRequest> FriendRequests { get; set; }

    public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
    {
        PrepareUserToDeleting();
        return base.SaveChangesAsync();
    }

    public override int SaveChanges(bool acceptAllChangesOnSuccess)
    {
        PrepareUserToDeleting();
        return base.SaveChanges();
    }

    private void PrepareUserToDeleting()
    {
        // For each deleted user entity
        foreach(var entry in ChangeTracker.Entries<ApplicationUser>().Where(e => e.State == EntityState.Deleted))
        {
            var user = entry.Entity;
            // If RecevedRequests isn't loaded
            if (user.RecevedRequests == null)
            {
                //Then load RecevedRequests
                entry.Collection(u => u.RecevedRequests).Load();
            }
            // Idem with RequestedRequests
            if (user.RequestedRequests == null)
            {
                entry.Collection(u => u.RequestedRequests).Load();
            }
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM