![](/img/trans.png)
[英]How to create relationship between entities & ApplicationUser in MVC 5 EF
[英]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);
如何设置此级联删除方案?
另外,如何向 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.