我在TechEd session期间实现了Rowan Miller演示的软删除模式,但我遇到了一个直接问题,因为我在我的Code First模型中使用了继承。第一个错误是在查询期间,因为我将IsDeleted属性放在我的超类型(基类)上但是当我截获查询子类并尝试添加过滤器时,EF抱怨该类型上没有这样的属性。很公平,我将属性移动到子类型,这一点工作正常。但是当删除时,命令树拦截器将子类型的删除更改为'update set isdeleted = 1',但EF也为超类型(基类)生成了删除。这导致数据库中的外键约束错误。这有点痛苦,但我可以通过抑制超类型的删除命令的执行来修复它。
但是,我在拦截上下文中找不到SuppressExecution方法,如果我将结果设置为null,我会得到一个nullref异常。我想我需要一些方法来用NullDbCommand或类似命令替换命令。有什么想法吗?
public class CommandTreeInterceptor : IDbCommandTreeInterceptor
{
public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
{
if (interceptionContext.OriginalResult.DataSpace != DataSpace.SSpace) return;
// Look for query and add 'IsDeleted = 0' filter.
var queryCommand = interceptionContext.Result as DbQueryCommandTree;
if (queryCommand != null)
{
var newQuery = queryCommand.Query.Accept(new SoftDeleteQueryVisitor());
interceptionContext.Result = new DbQueryCommandTree(queryCommand.MetadataWorkspace,
queryCommand.DataSpace, newQuery);
}
// Look for delete and change it to an update instead.
var deleteCommand = interceptionContext.OriginalResult as DbDeleteCommandTree;
if (deleteCommand != null)
{
// !!! Need to suppress this whole command for supertypes (base class).
var column = SoftDeleteAttribute.GetSoftDeleteColumnName(deleteCommand.Target.Variable.ResultType.EdmType);
if (column != null)
{
var setClause =
DbExpressionBuilder.SetClause(
deleteCommand.Target.VariableType.Variable(deleteCommand.Target.VariableName)
.Property(column), DbExpression.FromBoolean(true));
var update = new DbUpdateCommandTree(deleteCommand.MetadataWorkspace,
deleteCommand.DataSpace,
deleteCommand.Target,
deleteCommand.Predicate,
new List<DbModificationClause>{ setClause }.AsReadOnly(), null);
interceptionContext.Result = update;
}
}
}
}
答案 0 :(得分:3)
我对此的解决方案有点hacky但它确实有效。我首先尝试通过继承DbCommandTree来创建NullDbCommandTree;遗憾的是,后一类中的大多数方法和构造函数都标记为内部,因此没有用。
因为我必须返回某种命令树,所以用DbFunctionCommandTree替换了删除命令树。我在数据库中创建了一个不执行任何操作的存储过程,只是调用而不是删除。它现在可以正常工作。
我必须对QueryVisitor和命令树进行的另一个修改是检查实体是否实际具有IsDeleted属性,因为在类层次结构中只有一个具有它。对于拥有它的那个,我们用更新替换delete,对于那些没有更新的我们调用null函数。所以现在这是我的命令树代码:
// Look for for softdeletes delete.
var deleteCommand = interceptionContext.OriginalResult as DbDeleteCommandTree;
if (deleteCommand != null)
{
var columnName =
SoftDeleteAttribute.GetSoftDeleteColumnName(deleteCommand.Target.Variable.ResultType.EdmType);
if (columnName != null)
{
// If the IsDeleted property is on this class, then change the delete to an update,
// otherwise suppress the whole delete command somehow?
var tt = (EntityType) deleteCommand.Target.Variable.ResultType.EdmType;
if (
tt.DeclaredMembers.Any(
m => m.Name.Equals(columnName, StringComparison.InvariantCultureIgnoreCase)))
{
var setClause =
DbExpressionBuilder.SetClause(
deleteCommand.Target.VariableType.Variable(deleteCommand.Target.VariableName)
.Property(columnName), DbExpression.FromBoolean(true));
var update = new DbUpdateCommandTree(deleteCommand.MetadataWorkspace,
deleteCommand.DataSpace,
deleteCommand.Target,
deleteCommand.Predicate,
new List<DbModificationClause> {setClause}.AsReadOnly(), null);
interceptionContext.Result = update;
}
else
{
interceptionContext.Result = CreateNullFunction(deleteCommand.MetadataWorkspace,
deleteCommand.DataSpace);
}
}
}
}
private DbFunctionCommandTree CreateNullFunction(MetadataWorkspace metadataWorkspace, DataSpace dataSpace)
{
var function = EdmFunction.Create("usp_SoftDeleteNullFunction", "dbo", dataSpace,
new EdmFunctionPayload { CommandText = "usp_SoftDeleteNullFunction" }, null);
return new DbFunctionCommandTree(metadataWorkspace, dataSpace, function,
TypeUsage.CreateStringTypeUsage(PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String), false, true),
null);
}
}
这是查询访问者代码:
var columnName = SoftDeleteAttribute.GetSoftDeleteColumnName((expression.Target.ElementType));
if (columnName == null || !expression.Target.ElementType.Members.Any(m => m.Name.Equals(columnName, StringComparison.InvariantCultureIgnoreCase)))
{
return base.Visit(expression);
}
var binding = expression.Bind();
return binding.Filter(binding.VariableType.Variable(binding.VariableName).Property(columnName).NotEqual(DbExpression.FromBoolean(true)));
答案 1 :(得分:2)
读取soft delete pattern,将实体设置为已删除项目的分离。
以下是上述文章中的代码片段:
public override int SaveChanges()
{
foreach ( var entry in ChangeTracker.Entries()
.Where( p => p.State == EntityState.Deleted ) )
SoftDelete( entry );
return base.SaveChanges();
}
private void SoftDelete( DbEntityEntry entry )
{
Type entryEntityType = entry.Entity.GetType();
string tableName = GetTableName( entryEntityType );
string primaryKeyName = GetPrimaryKeyName( entryEntityType );
string deletequery = string.Format(
"UPDATE {0} SET IsDeleted = 1 WHERE {1} = @id",
tableName, primaryKeyName);
Database.ExecuteSqlCommand(
deletequery,
new SqlParameter("@id", entry.OriginalValues[primaryKeyName] ) );
// 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;
}
7分钟后还要观看视频:http://channel9.msdn.com/Events/TechEd/NorthAmerica/2014/DEV-B417#fbid=
答案 2 :(得分:2)
创建DbCommandInterceptor:
public class DataIntercepter : DbCommandInterceptor
{
public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
base.ScalarExecuting(command, interceptionContext);
}
public override void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
base.ScalarExecuted(command, interceptionContext);
}
public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
base.NonQueryExecuting(command, interceptionContext);
}
public override void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
base.NonQueryExecuted(command, interceptionContext);
}
public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
base.ReaderExecuting(command, interceptionContext);
}
public override void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
base.ReaderExecuted(command, interceptionContext);
}
}
然后在执行前将其添加到代码中的任何位置(global.asax应该没问题):
DbInterception.Add(new DataIntercepter());
然后禁止执行:
public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
interceptionContext.SuppressExecution();
base.NonQueryExecuting(command, interceptionContext);
}
或者,设置你自己的结果:
public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
interceptionContext.Result = -1;
base.NonQueryExecuting(command, interceptionContext);
}
目前正在研究SQL Server loadbalancer插件并看到了这个问题。我刚刚在5分钟前找到了解决方案:)希望2年后对你有任何帮助。