C# 如何使用实体框架自动筛选出软删除的实体?

C# 如何使用实体框架自动筛选出软删除的实体?,c#,.net,entity-framework,ef-code-first,C#,.net,Entity Framework,Ef Code First,我首先使用实体框架代码。我覆盖DbContext中的SaveChanges,以允许我执行“软删除”: 这很好,因此对象知道如何将自己标记为软删除(在本例中,它只是将IsDeleted设置为true) 我的问题是,如何使它在检索对象时忽略任何已删除的对象?因此,如果我说\u db.Users.FirstOrDefault(UserId==id)如果该用户有IsDeleted==true它将忽略它。本质上我想过滤 注意:我不想只把&&IsDeleted==true 这就是为什么我要用一个接口来标记类

我首先使用实体框架代码。我覆盖
DbContext
中的
SaveChanges
,以允许我执行“软删除”:

这很好,因此对象知道如何将自己标记为软删除(在本例中,它只是将
IsDeleted
设置为
true

我的问题是,如何使它在检索对象时忽略任何已删除的
对象?因此,如果我说
\u db.Users.FirstOrDefault(UserId==id)
如果该用户有
IsDeleted==true
它将忽略它。本质上我想过滤

注意:我不想只把
&&IsDeleted==true

这就是为什么我要用一个接口来标记类,这样移除就知道如何“正常工作”,我想以某种方式修改检索,以知道如何“正常工作”,也基于存在的接口。

一个选项是封装
!已将
删除到扩展方法中。下面的内容只是一个例子。注意,这只是给你一个扩展方法的概念,下面的内容不会编译

public static class EnumerableExtensions
{
    public static T FirstOrDefaultExcludingDeletes<T>(this IEnumerable<T> source, Func<T, bool> predicate)
    {
        return source.Where(args => args != IsDeleted).FirstOrDefault(predicate);
    }
}
好问题

您需要在SQL查询以某种方式执行之前拦截它,然后添加额外的where子句以从选择中删除“已删除”项。不幸的是,实体没有可用于更改查询的GetCommand

也许可以修改位于正确位置的EF提供者包装器,以允许查询更改

或者,您可以使用QueryInterceptor,但每个查询都必须使用
InterceptWith(visitor)
来更改表达式

因此,我将集中讨论这种方法,因为除了拦截查询并修复它(如果您希望保持查询代码不变)之外,没有其他选择


无论如何,如果你发现了一些有用的东西,请告诉我们。

我的所有实体都使用了软删除,软删除的项目不会使用作者建议的技术通过上下文检索。这包括通过导航属性访问实体的时间

向每个可以软删除的实体添加IsDeleted鉴别器。不幸的是,我还没有研究出如何基于从抽象类或接口()派生的实体来实现这一点:

SoftDelete方法直接在数据库上运行sql,因为实体中不能包含鉴别器列:

private void SoftDelete(DbEntityEntry entry)
{
    var e = entry.Entity as ModelBase;
    string tableName = GetTableName(e.GetType());
    Database.ExecuteSqlCommand(
             String.Format("UPDATE {0} SET IsDeleted = 1 WHERE ID = @id", tableName)
             , new SqlParameter("id", e.ID));

    //Marking it Unchanged prevents the hard delete
    //entry.State = EntityState.Unchanged;
    //So does setting it to Detached:
    //And that is what EF does when it deletes an item
    //http://msdn.microsoft.com/en-us/data/jj592676.aspx
    entry.State = EntityState.Detached;
}
GetTableName返回要为实体更新的表。它处理表链接到基类型而不是派生类型的情况。我想我应该检查整个继承层次结构。。。。 但是有计划,如果有必要的话,我会调查

但这意味着您不能创建与已删除组织同名的新组织。为了实现这一点,我更改了代码以创建以下索引:

public override void Up()
{
    Sql(String.Format("CREATE UNIQUE INDEX {0} ON dbo.Organisations(Name) WHERE IsDeleted = 0", "IX_NaturalKey"));
}
这将从索引中排除已删除的项目

注意 如果相关项目被软删除,则不会填充导航属性,但外键会被删除。 例如:

if(foo.BarID != null)  //trying to avoid a database call
   string name = foo.Bar.Name; //will fail because BarID is not null but Bar is

//but this works
if(foo.Bar != null) //a database call because there is a foreign key
   string name = foo.Bar.Name;
p.S.在此处投票支持全局筛选,筛选包括使用。它允许您创建在执行查询时自动应用(包括针对导航属性)的全局筛选器

项目页面上有一个示例“IsDeleted”过滤器,如下所示:

public override void Up()
{
    CreateIndex("dbo.Organisations", "Name", unique: true, name: "IX_NaturalKey");
}
modelBuilder.Filter("IsDeleted", (ISoftDelete d) => d.IsDeleted, false);
该过滤器将自动在针对ISoftDelete实体的任何查询中插入where子句。过滤器是在DbContext.OnModelCreating()中定义的

免责声明:我是作者。

您可以在Entity Framework Core 2.0上使用

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>().Property<string>("TenantId").HasField("_tenantId");

    // Configure entity filters
    modelBuilder.Entity<Blog>().HasQueryFilter(b => EF.Property<string>(b, "TenantId") == _tenantId);
    modelBuilder.Entity<Post>().HasQueryFilter(p => !p.IsDeleted);
}
模型创建时受保护的覆盖无效(ModelBuilder ModelBuilder)
{
modelBuilder.Entity().Property(“TenantId”).HasField(“_TenantId”);
//配置实体筛选器
modelBuilder.Entity();
modelBuilder.Entity().HasQueryFilter(p=>!p.IsDeleted);
}

除非我误解了您的意思,否则您只需在Linq查询中添加另一个子句即可。也就是说,你可以使用
FirstOrDefault(UserId==id&&!IsDeleted)
或者使用一个已经被过滤的
IQueryable
,就像在
\u repository.ActiveUsers.FirstOrDefault(UserId==id)
@Arran是的,我希望能够避免这样做,这样我就不必知道在我的代码中哪些类被软删除了。我让soft delete类使用一个接口ISoftDelete,所以当一个删除操作完成,然后保存更改时,它会看到它实现了该接口并处理soft delete。没有类似的方法来处理检索吗?您可以尝试实现类似的方法。或者,您可以在Visual Studio中简单地执行查找和替换操作。:)@RobertHarvey但是如果我使用
IQueryable
我会失去像Add()这样的东西,我想要
DbSet
的所有好处,但是能够过滤它们:)我不想为我的用户使用
DbSet
,为活跃用户使用
IQueryable
之类的东西(如果你是这么建议的话),这是一个很好的答案,所以+1,它可以解决我的一些问题,但不能解决所有问题。我想这需要深入到我们的关系中去。因此,如果我拉一个为该组加载用户的组,它将不会加载已删除的用户。我想您可以创建另一个扩展方法ExcludeDeletes,并执行类似于_db.Groups.ExcludeSoftDeletes(groupId==id).users.ExcludeSoftDeletes(UserId==id)的操作,谢谢,但我真的不想再次为此编写新的API,重写一些控制检索什么数据的内部方法,也许将内部检索挂接到视图或其他东西是理想的,因为这不会迫使我重写EFAPI。假设我想要一个,或者任何一个,我需要为所有东西编写扩展。好主意!谢斯菲尔角;所有EF API都以任何方式将(Func谓词)作为参数,因此您只需
public override void Up()
{
    CreateIndex("dbo.Organisations", "Name", unique: true, name: "IX_NaturalKey");
}
public override void Up()
{
    Sql(String.Format("CREATE UNIQUE INDEX {0} ON dbo.Organisations(Name) WHERE IsDeleted = 0", "IX_NaturalKey"));
}
if(foo.BarID != null)  //trying to avoid a database call
   string name = foo.Bar.Name; //will fail because BarID is not null but Bar is

//but this works
if(foo.Bar != null) //a database call because there is a foreign key
   string name = foo.Bar.Name;
modelBuilder.Filter("IsDeleted", (ISoftDelete d) => d.IsDeleted, false);
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>().Property<string>("TenantId").HasField("_tenantId");

    // Configure entity filters
    modelBuilder.Entity<Blog>().HasQueryFilter(b => EF.Property<string>(b, "TenantId") == _tenantId);
    modelBuilder.Entity<Post>().HasQueryFilter(p => !p.IsDeleted);
}