36

我首先使用实体​​框架代码。我重写SaveChangesDbContext允许我执行“软删除”:

if (item.State == EntityState.Deleted && typeof(ISoftDelete).IsAssignableFrom(type))
{
    item.State = EntityState.Modified;
    item.Entity.GetType().GetMethod("Delete")
        .Invoke(item.Entity, null);

    continue;
}

这很好,所以对象知道如何将自己标记为软删除(在这种情况下,它只是设置IsDeletedtrue)。

我的问题是我怎样才能使它在我检索对象时忽略任何 with IsDeleted?所以如果我说_db.Users.FirstOrDefault(UserId == id)如果那个用户有IsDeleted == true它会忽略它。本质上我想过滤?

注意:我不想只是把这&& IsDeleted == true 就是为什么我用接口标记类所以删除知道如何“工作”,我想以某种方式修改检索以知道如何“工作”也基于该界面存在。

4

5 回答 5

40

我对我的所有实体都进行了软删除,并且使用此答案建议的技术不会通过上下文检索软删除的项目。这包括当您通过导航属性访问实体时。

为每个可以软删除的实体添加 IsDeleted 鉴别器。不幸的是,我还没有弄清楚如何根据派生自抽象类或接口的实体来执行此操作(EF 映射当前不支持将接口作为实体):

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
   modelBuilder.Entity<Foo>().Map(m => m.Requires("IsDeleted").HasValue(false));
   modelBuilder.Entity<Bar>().Map(m => m.Requires("IsDeleted").HasValue(false));

   //It's more complicated if you have derived entities. 
   //Here 'Block' derives from 'Property'
   modelBuilder.Entity<Property>()
            .Map<Property>(m =>
            {
                m.Requires("Discriminator").HasValue("Property");
                m.Requires("IsDeleted").HasValue(false);
            })
            .Map<Block>(m =>
            {
                m.Requires("Discriminator").HasValue("Block");
                m.Requires("IsDeleted").HasValue(false);
            });
}

覆盖 SaveChanges 并找到所有要删除的条目:

编辑 另一种覆盖删除sql的方法是更改​​EF6生成的存储过程

public override int SaveChanges()
{
   foreach (var entry in ChangeTracker.Entries()
             .Where(p => p.State == EntityState.Deleted 
             && p.Entity is ModelBase))//I do have a base class for entities with a single 
                                       //"ID" property - all my entities derive from this, 
                                       //but you could use ISoftDelete here
    SoftDelete(entry);

    return base.SaveChanges();
}

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 返回要为实体更新的表。它处理表链接到 BaseType 而不是派生类型的情况。我怀疑我应该检查整个继承层次结构......但是有计划改进元数据 API,如果我必须研究类型和表之间的 EF 代码优先映射

private readonly static Dictionary<Type, EntitySetBase> _mappingCache 
       = new Dictionary<Type, EntitySetBase>();

private ObjectContext _ObjectContext
{
    get { return (this as IObjectContextAdapter).ObjectContext; }
}

private EntitySetBase GetEntitySet(Type type)
{
    type = GetObjectType(type);

    if (_mappingCache.ContainsKey(type))
        return _mappingCache[type];

    string baseTypeName = type.BaseType.Name;
    string typeName = type.Name;

    ObjectContext octx = _ObjectContext;
    var es = octx.MetadataWorkspace
                    .GetItemCollection(DataSpace.SSpace)
                    .GetItems<EntityContainer>()
                    .SelectMany(c => c.BaseEntitySets
                                    .Where(e => e.Name == typeName 
                                    || e.Name == baseTypeName))
                    .FirstOrDefault();

    if (es == null)
        throw new ArgumentException("Entity type not found in GetEntitySet", typeName);

    _mappingCache.Add(type, es);

    return es;
}

internal String GetTableName(Type type)
{
    EntitySetBase es = GetEntitySet(type);

    //if you are using EF6
    return String.Format("[{0}].[{1}]", es.Schema, es.Table);

    //if you have a version prior to EF6
    //return string.Format( "[{0}].[{1}]", 
    //        es.MetadataProperties["Schema"].Value, 
    //        es.MetadataProperties["Table"].Value );
}

我之前在迁移中使用如下代码在自然键上创建了索引:

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;

PS在此处为全局过滤投票https://entityframework.codeplex.com/workitem/945?FocusElement=CommentTextBox#在此处过滤

于 2013-09-24T15:28:44.027 回答
40

使用EntityFramework.DynamicFilters。它允许您创建将在执行查询时自动应用的全局过滤器(包括针对导航属性)。

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

modelBuilder.Filter("IsDeleted", (ISoftDelete d) => d.IsDeleted, false);

该过滤器将自动在针对 ISoftDelete 实体的任何查询中注入 where 子句。过滤器在您的 DbContext.OnModelCreating() 中定义。

免责声明:我是作者。

于 2015-07-22T03:12:16.850 回答
9

One option would be to encapsulate the !IsDeleted into an extension method. Something like below is just an example. Beware its just to give you an idea of an extension method, the below won't compile.

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);
    }
}

Usage:

_db.Users.FirstOrDefaultExcludingDeletes(UserId == id)
于 2012-10-02T23:08:23.643 回答
6

您可以在 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);
}
于 2018-10-26T10:53:07.043 回答
-5

好问题。

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

也许可以修改位于正确位置的 EF Provider Wrapper 以允许更改查询。

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

因此,我将专注于这种方法,因为 AFAIK 没有其他选择,然后拦截查询并修复它(如果您想保持查询代码不变)。

无论如何,如果您发现有用的东西,请告诉我们。

于 2012-10-04T14:15:41.247 回答