Hom*_*sey 3 c# entity-framework-core
我正在尝试使用 EF7 实现“软删除”。我的表有一个名为type 的Item字段。我在 SO 和其他地方看到的所有例子都使用这样的东西:IsDeletedbit
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Item>().Map(m => m.Requires("IsDeleted").HasValue(false));
}
Run Code Online (Sandbox Code Playgroud)
但Map()不再是一种方法ModelBuilder。
编辑:让我澄清一下。我现在主要只对读取数据感兴趣。我希望 EF 自动过滤掉我的Item表中 where IsDeleted == 1(或 true)的所有记录。我不想&& x.IsDeleted == false在每个查询的末尾都需要一个。
现在是 2021 年,我想到添加一个与当前版本的 EF Core 相关的更现代、标准、内置的解决方案。
使用全局查询过滤器,您可以确保某些过滤器始终应用于某些实体。您可以通过接口定义软删除属性,这有助于以编程方式将过滤器添加到所有相关实体。看:
...
public interface ISoftDeletable
{
public string DeletedBy { get; }
public DateTime? DeletedAt { get; }
}
...
// Call it from DbContext.OnModelCreating()
private static void ConfigureSoftDeleteFilter(ModelBuilder builder)
{
foreach (var softDeletableTypeBuilder in builder.Model.GetEntityTypes()
.Where(x => typeof(ISoftDeletable).IsAssignableFrom(x.ClrType)))
{
var parameter = Expression.Parameter(softDeletableTypeBuilder.ClrType, "p");
softDeletableTypeBuilder.SetQueryFilter(
Expression.Lambda(
Expression.Equal(
Expression.Property(parameter, nameof(ISoftDeletable.DeletedAt)),
Expression.Constant(null)),
parameter)
);
}
}
Run Code Online (Sandbox Code Playgroud)
然后,为了确保在删除期间使用此标志而不是硬删除(替代例如存储库设置标志而不是删除实体):
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
foreach (var entry in ChangeTracker.Entries<ISoftDeletable>())
{
switch (entry.State)
{
case EntityState.Deleted:
// Override removal. Unchanged is better than Modified, because the latter flags ALL properties for update.
// With Unchanged, the change tracker will pick up on the freshly changed properties and save them.
entry.State = EntityState.Unchanged;
entry.Property(nameof(ISoftDeletable.DeletedBy)).CurrentValue = _currentUser.UserId;
entry.Property(nameof(ISoftDeletable.DeletedAt)).CurrentValue = _dateTime.Now;
break;
}
}
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
Run Code Online (Sandbox Code Playgroud)
一个关键的方面是考虑相关实体的级联删除,要么禁用级联删除,要么了解和控制 EF Core 的级联删除时序行为。该CascadeDeleteTiming设置的默认值为CascadeTiming.Immediate,这会导致 EF Core 立即将“已删除”实体的所有导航属性标记为EntityState.Deleted,并且EntityState.Deleted仅在根实体上恢复状态不会在导航属性上恢复状态。因此,如果您的导航属性不使用软删除,并且您希望避免它们被删除,则您也必须处理它们的更改跟踪器状态(而不仅仅是处理实体等ISoftDeletable),或者更改CascadeDeleteTiming设置,如下所示。
对于软删除实体上使用的自有类型也是如此。使用默认删除级联计时,EF Core 还将这些拥有的类型标记为“已删除”,如果它们设置为必需/不可为空,则在尝试保存软删除实体时将遇到 SQL 更新失败。
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges;
}
Run Code Online (Sandbox Code Playgroud)
如果您以这种方式定义全局查询过滤器,EF Core 将尽力隐藏引用软删除实体的所有其他实体。
例如,如果您已软删除一个Partner实体,并且您的Order实体中的每个实体都通过(必需的)导航属性引用合作伙伴,那么当您检索订单列表并包含合作伙伴时,引用的所有订单Partner列表中将缺少软删除。
此行为在文档页面的底部进行了讨论。
遗憾的是,从 EF Core 5 开始,全局查询过滤器不提供将其限制为根实体或仅禁用其中一个过滤器的选项。唯一可用的选项是使用IgnoreQueryFilters()禁用所有过滤器的方法。由于该IgnoreQueryFilters()方法接受IQueryable并返回一个IQueryable,因此您不能使用此方法透明地禁用 DbContext 类中公开的过滤器DbSet。
不过,一个重要的细节是,只有Include()在查询时提供给定的导航属性时才会发生这种情况。还有一个有趣的解决方案,用于获取结果集,该结果集已将查询过滤器应用于某些实体,但没有将它们应用于其他实体,这依赖于 EF 的一个鲜为人知的功能,即关系修复。EntityA基本上,您加载具有导航属性的列表EntityB(不包括EntityB)。EntityB然后使用单独加载 的列表IgnoreQueryFilters()。发生的情况是 EF 自动将EntityB导航属性设置EntityA到加载的EntityB实例上。这样,查询过滤器将应用于EntityA自身,但不会应用于EntityB导航属性,因此EntityA即使使用软删除,您也可以看到 s EntityB。请参阅另一个问题的这个答案。(当然这会对性能产生影响,并且您仍然无法将其封装在 DbContext 中。)