我的代码中有三个主要类:
public class AppRole : ITenantEntity<Guid>
{
[Required]
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public Guid TenantId { get; set; }
public virtual ICollection<AppUserRole> AppUserRoles { get; } = new HashSet<AppUserRole>();
}
public class AppUser
{
[Required]
public string UserId { get; set; }
[Required]
public Guid TenantId { get; set; }
public virtual User User { get; set; }
}
public class AppUserRole
{
[Required]
public string UserId { get; set; }
[Required]
public Guid RoleId { get; set; }
public virtual User User { get; set; }
public virtual AppRole Role { get; set; }
}
以及这些的映射:
builder
.Entity<AppRole>()
.HasMany(s => s.AppUserRoles)
.WithOne(s => s.Role)
.HasForeignKey(s => s.RoleId)
.OnDelete(DeleteBehavior.Cascade);
builder
.Entity<AppUser>().HasKey(x => new { x.UserId, x.TenantId });
builder
.Entity<AppUserRole>().HasKey(x => new { x.UserId, x.RoleId });
我的应用程序中的用例如下所示:
所以在数据库中我得到了这样的数据:
AppRole - ID:1,名称:“测试”,... AppRole - ID:2,名称:“test2”,...
AppUser - 用户 ID:“myuser”,...
AppUserRole - 用户 ID:“myuser”,角色 ID:1 AppUserRole - 用户 ID:“myuser”,角色 ID:2
当我尝试将新的 AppRole 添加到表中时,实体框架的第一步是删除 AppRoles 表中的每一行,执行查询:
DELETE FROM [AppRoles]
WHERE [Id] = @p0 AND [TenantId] = @p1;
SELECT @@ROWCOUNT;
下一步是执行插入:
INSERT INTO [AppRoles] ([Id], [TenantId], [Name])
VALUES (@p22, @p23, @p24),
(@p25, @p26, @p27),
...
(@p55, @p56, @p57);
此机制会影响 AppUserRole 表中的所有行都被删除。 这才是真正的问题。 AppUserRole 表中消失的行
为了更好地理解问题,让我们看另一个例子:
想象一下班上有20名学生。每个人都有不同科目的成绩。一年中,一名学生加入。这是否会导致清除剩余学生的所有成绩数据? 我不这么认为。 那就是问题所在。当新项目添加到 AppRole 时,不应删除 AppUserRoles 中的数据。
有人看到这里有什么错误吗? 为什么 ef 先删除再添加?
我必须在这里问这个问题,因为我自己尝试解决这个问题并不能解决问题。
我忘了 - 这是执行更新的代码:
public virtual async Task<T> Update(T item)
{
var originalItem = await this.DbSet.SingleAsync(...).ConfigureAwait(false);
this.Mapper.Map(item, originalItem);
await this.Context.SaveChangesAsync().ConfigureAwait(false);
return this.Mapper.Map<T>(originalItem);
}
使用集合时,您需要注意您的代码或映射器不会最终替换集合实例。此行也可能会在读取数据时导致问题:
public virtual ICollection<AppUserRole> AppUserRoles { get; } = new HashSet<AppUserRole>();
将此更新为:
public virtual ICollection<AppUserRole> AppUserRoles { get; protected set;} = new HashSet<AppUserRole>();
然后,当更新您想要对子集合进行更改的父集合时,请使用
Include()
: 立即加载它
var originalItem = await this.DbSet
.Include(x => x.AppUserRoles)
.SingleAsync(...)
.ConfigureAwait(false);
this.Mapper.Map(item, originalItem);
如果您希望添加 AppUserRole,您可能希望让映射器忽略任何集合或引用并手动处理它们。您不希望映射器尝试用新集合覆盖集合(
protected
setter 应该有助于避免这种情况),而且对于单一引用,您通常不希望更改现有实体值,而是加载新实体(例如) 来引用并替换父级中的引用。