簡體   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