C# 如何抑制实体框架6 IDbCommandTreeInterceptor的执行?

C# 如何抑制实体框架6 IDbCommandTreeInterceptor的执行?,c#,entity-framework,entity-framework-6.1,soft-delete,C#,Entity Framework,Entity Framework 6.1,Soft Delete,我实现了Rowan Miller在一次测试中演示的软删除模式,但我遇到了一个直接的问题,因为我在代码优先模型中使用了继承。第一个错误是在查询期间,因为我将IsDeleted属性放在了我的超类型(基类)上,但当我截获子类型的查询并尝试添加筛选器时,EF抱怨该类型上没有此类属性。公平地说,我将属性移动到了子类型,这一点工作正常。但当涉及到删除时,命令树拦截器将子类型的删除更改为“更新集isdeleted=1”,但EF也生成了超类型(基类)的删除。这导致数据库中出现外键约束错误。这有点痛苦,但我可以通

我实现了Rowan Miller在一次测试中演示的软删除模式,但我遇到了一个直接的问题,因为我在代码优先模型中使用了继承。第一个错误是在查询期间,因为我将IsDeleted属性放在了我的超类型(基类)上,但当我截获子类型的查询并尝试添加筛选器时,EF抱怨该类型上没有此类属性。公平地说,我将属性移动到了子类型,这一点工作正常。但当涉及到删除时,命令树拦截器将子类型的删除更改为“更新集isdeleted=1”,但EF也生成了超类型(基类)的删除。这导致数据库中出现外键约束错误。这有点痛苦,但我可以通过抑制超类型的delete命令的执行来修复它

但是,我在拦截上下文中找不到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;
            }
        }
    }
}
公共类CommandTreeInterceptor:IDBComandTreeInterceptor
{
已创建公共无效树(DbCommandTreeInterceptionContext interceptionContext)
{
if(interceptionContext.OriginalResult.DataSpace!=DataSpace.ssspace)返回;
//查找查询并添加“IsDeleted=0”筛选器。
var queryCommand=interceptionContext.Result为DbQueryCommandTree;
if(queryCommand!=null)
{
var newQuery=queryCommand.Query.Accept(新的SoftDeleteQueryVisitor());
InterceptOnContext.Result=新的DbQueryCommand树(queryCommand.MetadataWorkspace,
queryCommand.DataSpace,newQuery);
}
//查找“删除”并将其改为“更新”。
var deleteCommand=interceptionContext.OriginalResult作为DbDeleteCommandTree;
if(deleteCommand!=null)
{
//!!!需要抑制超类型(基类)的整个命令。
var column=SoftDeleteAttribute.GetSoftDeleteColumnName(deleteCommand.Target.Variable.ResultType.EdmType);
if(列!=null)
{
var集合子句=
DbExpressionBuilder.SetClause(
deleteCommand.Target.VariableType.Variable(deleteCommand.Target.VariableName)
.Property(column),DbExpression.FromBoolean(true));
var update=新的DbUpdateCommandTree(deleteCommand.MetadataWorkspace,
deleteCommand.DataSpace,
deleteCommand.Target,
deleteCommand.Predicate,
新列表{setClause}.AsReadOnly(),null);
Result=update;
}
}
}
}

我的解决方案有点老套,但很有效。我首先尝试通过从DbCommandTree继承来创建一个NullDbCommandTree;不幸的是,后一个类中的大多数方法和构造函数都标记为internal,因此没有任何用处

因为我必须返回某种命令树,所以我用DbFunctionCommandTree替换了delete命令树。我在数据库中创建了一个不执行任何操作的存储过程,它只是被调用而不是被删除。现在还可以

我必须对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);
    }
}
读取已删除的项目,将实体设置为分离

以下是上述文章中的代码片段:

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分钟后观看视频:

创建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);
    }
}
然后抑制执行:

public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
    interceptionContext.SuppressExecution();
    base.NonQueryExecuting(command, interceptionContext);
}
public override void NonQueryExecuting(DbCommand命令,DbCommandInterceptionContext interceptionContext)
{
interceptOnContext.SuppressExecution();
base.NonQueryExecuting(命令,拦截上下文);
}
或者,设置您自己的结果:

public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
    interceptionContext.Result = -1;
    base.NonQueryExecuting(command, interceptionContext);
}
public override void NonQueryExecuting(DbCommand命令,DbCommandInterceptionContext interceptionContext)
{
Result=-1;
base.NonQueryExecuting(命令,拦截上下文);
}

目前正在开发一个SQL server loadbalancer插件,并看到了这个问题。我5分钟前才找到解决方案:)希望两年后能对你有所帮助。

PS我知道这个解决方案是一个真正的黑客,如果你有更好的想法,请加入。就我个人而言,我希望EF支持SuppressExecution或拥有NullDbCommandTree,前者是首选解决方案。实际上,首选解决方案是EF core中的软删除。也找不到解决方案。。。但是我使用了
新的DbQueryCommandTree(deleteCommand.MetadataWorkspace、DataSpace.SSpace、DbExpressionBuilder.True)在我的工作环境中。这很有趣。我们得出的结论是,软删除是一种反模式,如下所述:除了文档之外,我们什么都不用软删除,这样就可以恢复。我不认为我会根据一篇8年前的文章做出放弃软删除的决定。Tumbleweeeeeed。。。有没有感觉到你在另一个星球上?谢谢。我看了这个方法,并拒绝了它,理由很好,我无法100%回忆起。我认为原因与继承有关-IsDeleted字段可能不在被删除的实体上,并且无法发现它。我还需要截取查询,所以需要截取器。而且。。。还有一件我记不清的事。但是上面的方法在没有继承的情况下只能用于设置IsDeleted标志,而不能用于查询。不处理此代码a
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);
}