我如何使用entity framework自动过滤软删除的实体?
我首先使用实体框架代码。 我重写DbContext
SaveChanges
以允许我执行“软删除”:
if (item.State == EntityState.Deleted && typeof(ISoftDelete).IsAssignableFrom(type)) { item.State = EntityState.Modified; item.Entity.GetType().GetMethod("Delete") .Invoke(item.Entity, null); continue; }
这很好,所以对象知道如何标记自己是一个软删除(在这种情况下,它只是将IsDeleted
设置为true
)。
我的问题是我怎么能这样做,当我检索对象忽略IsDeleted
任何? 所以,如果我说_db.Users.FirstOrDefault(UserId == id)
如果该用户有IsDeleted == true
它将忽略它。 本质上我想过滤?
注意:我不想只把&& IsDeleted == true
这就是为什么我用接口标记类,所以删除知道如何“只是工作”,我想以某种方式修改检索知道如何“只是工作“也基于那个接口存在。
我有软删除工作的所有我的实体和软删除项目不会通过上下文使用此答案build议的技术检索。 这包括通过导航属性访问实体。
将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并find所有要删除的条目:
编辑 重写删除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而不是派生types的情况。 我怀疑我应该检查整个inheritance层次….但是有计划,以改善元数据API ,如果我必须看看EF代码之间的types和表第一映射
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 ); }
我以前曾使用代码如下所示在迁移中为自然键创build索引:
public override void Up() { CreateIndex("dbo.Organisations", "Name", unique: true, name: "IX_NaturalKey"); }
但是这意味着您不能创build一个与删除的组织名称相同的新组织。 为了让这个我改变了代码来创build索引:
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#和过滤包括在这里;
使用EntityFramework.DynamicFilters 。 它允许您创build全局filter,在执行查询时将自动应用(包括针对导航属性)。
项目页面上有一个例子“IsDeleted”filter,如下所示:
modelBuilder.Filter("IsDeleted", (ISoftDelete d) => d.IsDeleted, false);
该筛选器将自动在针对ISoftDelete的实体的任何查询中注入where子句。 filter在DbContext.OnModelCreating()中定义。
免责声明:我是作者。
一种select是将!IsDeleted
封装成扩展方法。 像下面的东西只是一个例子。 要小心它只是给你一个扩展方法的想法,下面不会编译。
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); } }
用法:
_db.Users.FirstOrDefaultExcludingDeletes(UserId == id)
伟大的问题。
您需要拦截SQL查询,然后以某种方式执行,然后添加其他where子句以从select中删除“已删除”项目。 不幸的是,实体没有可用于更改查询的GetCommand。
可能会修改位于正确位置的EF提供程序包装以允许查询更改。
或者,你可以利用QueryInterceptor,但每个查询将不得不使用InterceptWith(visitor)
来改变expression式…
所以,我会专注于这种方法,因为有AFAIK没有其他select,然后拦截查询和修复它(如果你想保持查询不变的代码)。
无论如何,如果你找出有用的东西,让我们知道。