简体   繁体   English

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

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

I have the traditional ApplicationUser (IdentityUser), and that user can send a friend request to another ApplicationUser .我有传统的ApplicationUser (IdentityUser),该用户可以向另一个ApplicationUser发送好友请求。 I currently have the following general entity classes:我目前有以下通用实体类:

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; }
}

I have ran database-update etc and this is working fine.我已经运行了数据库更新等,这工作正常。 However when I go into my SQLServer to try to delete an ApplicationUser, it tells me that The DELETE statement conflicted with the REFERENCE constraint "FK_FriendRequest_AspNetUsers_RequesterId" .但是,当我 go 进入我的 SQLServer 以尝试删除 ApplicationUser 时,它告诉我The DELETE statement conflicted with the REFERENCE constraint "FK_FriendRequest_AspNetUsers_RequesterId"

So I have decided to implement a cascade delete flow from the ApplicationUser to the friend requests that they are part of.所以我决定实现从 ApplicationUser 到他们所属的朋友请求的级联删除流程。

I have tried the resource on here by Microsoft on configuring cascade delete but I cannot figure out how to apply it to my case:我已经尝试过微软在此处配置级联删除的资源,但我无法弄清楚如何将其应用于我的案例:

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

  2. Also how do I add a property to ApplicationUser that refers to all the FriendRequests they are part of, and make sure EFCore knows I am referring to that existing FriendRequest entity/table?另外,如何向 ApplicationUser 添加一个属性,该属性引用它们所属的所有 FriendRequest,并确保 EFCore 知道我指的是现有的 FriendRequest 实体/表?


Update更新

Following the suggested approach of adding a virtual property to ApplicationUser, would this be way forward:遵循向 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

Your ApplicationUser needs 2 virtual ICollections.您的 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);
    }
}

Use:利用:

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);
}

I have tried the resource on here by Microsoft on configuring cascade delete but I cannot figure out how to apply it to my case:我已经尝试过微软在此处配置级联删除的资源,但我无法弄清楚如何将其应用于我的案例:

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

From the doc of DeleteBehavior :DeleteBehavior的文档中:

ClientCascade: For entities being tracked by the DbContext, dependent entities will be deleted when the related principal is deleted. ClientCascade:对于被 DbContext 跟踪的实体,在删除相关主体时将删除依赖实体。 If the database has been created from the model using Entity Framework Migrations or the EnsureCreated() method, then the behavior in the database is to generate an error if a foreign key constraint is violated.如果数据库是使用实体框架迁移或 EnsureCreated() 方法从 model 创建的,则如果违反外键约束,则数据库中的行为将生成错误。

In this case, it's the client (the .NET app) and not the DB that ensure the cascade delete.在这种情况下,确保级联删除的是客户端(.NET 应用程序)而不是数据库。 If the client fail to do the cascade delete (related entity not tracked), the db will generate the error you see.如果客户端未能进行级联删除(相关实体未跟踪),则数据库将生成您看到的错误。

Maybe the DeleteBehavior.Cascade is more appropriate to your code first scenario:也许DeleteBehavior.Cascade更适合您的代码优先方案:

Cascade: For entities being tracked by the DbContext, dependent entities will be deleted when the related principal is deleted.级联:对于被 DbContext 跟踪的实体,在删除相关主体时将删除依赖实体。 If the database has been created from the model using Entity Framework Migrations or the EnsureCreated() method, then the behavior in the database is the same as is described above for tracked entities.如果已使用 Entity Framework Migrations 或 EnsureCreated() 方法从 model 创建数据库,则数据库中的行为与上述跟踪实体的行为相同。 Keep in mind that some databases cannot easily support this behavior, especially if there are cycles in relationships, in which case it may be better to use ClientCascade which will allow EF to perform cascade deletes on loaded entities even if the database does not support this.请记住,某些数据库无法轻松支持此行为,尤其是在关系中存在循环的情况下,在这种情况下,最好使用 ClientCascade,这将允许 EF 对加载的实体执行级联删除,即使数据库不支持这一点。 This is the default for required relationships.这是所需关系的默认值。 That is, for relationships that have non-nullable foreign keys.也就是说,对于具有不可空外键的关系。

If you try this, you go with this SQL script migration (I assume the SGBDR is SQL Server):如果你试试这个,你 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

And when it's apply, this produce this error:当它应用时,这会产生这个错误:

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.

First time I see this error, then I will refer to this question with @onedaywhen's answer :我第一次看到这个错误时,我会用@onedaywhen 的回答来参考这个问题

SQL Server does simple counting of cascade paths and, rather than trying to work out whether any cycles actually exist, it assumes the worst and refuses to create the referential actions (CASCADE)... SQL 服务器对级联路径进行简单计数,而不是尝试确定是否确实存在任何循环,而是假设最坏的情况并拒绝创建引用操作 (CASCADE)...

A no perfect solution is to use DeleteBehavior.Cascade and ensure all related entities are tracked before the delete:一个不完美的解决方案是使用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