如何抑制Entity Framework 6 IDbCommandTreeInterceptor的执行?

时间:2014-07-22 16:25:09

标签: c# entity-framework entity-framework-6.1 soft-delete

我在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;
            }
        }
    }
}

3 个答案:

答案 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年后对你有任何帮助。