3

我实现了 Rowan Miller 在TechEd 会议期间演示的软删除模式但是我遇到了一个直接的问题,因为我在我的 Code First 模型中使用了继承。第一个错误是在查询期间,因为我将 IsDeleted 属性放在我的超类型(基类)上,但是当我截获子类型的查询并尝试添加过滤器时,EF 抱怨该类型上没有这样的属性。很公平,我将属性移到了子类型中,并且效果还不错。但是在删除时,命令树拦截器将子类型的删除更改为“更新集 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;
            }
        }
    }
}
4

3 回答 3

4

我对此的解决方案有点老套,但它确实有效。我首先尝试通过从 DbCommandTree 继承来创建 NullDbCommandTree;不幸的是,后一个类中的大多数方法和构造函数都被标记为内部的,所以没有用。

因为我必须返回某种命令树,所以我用 DbFunctionCommandTree 替换了删除命令树。我在数据库中创建了一个什么都不做的存储过程,它只是被调用而不是删除。它现在工作正常。

我必须对 QueryVisitor 和命令树进行的另一项修改是检查实体是否实际上具有 IsDeleted 属性,因为在类层次结构中只有一个具有它。对于拥有它的,我们将删除替换为更新,对于没有的,我们调用 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)));
于 2014-07-23T14:36:42.577 回答
2

阅读软删除模式,该模式将实体设置为已删除项目的分离。

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

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=

于 2014-11-17T10:36:27.380 回答
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 服务器负载平衡器插件并看到了这个问题。我刚刚在 5 分钟前找到了解决方案 :) 希望它在 2 年后对您有所帮助。

于 2016-08-07T09:59:22.240 回答