【问题标题】:DB Intercept causing SQL Nested too deeply数据库拦截导致 SQL 嵌套太深
【发布时间】:2016-07-09 21:36:52
【问题描述】:

我在实体框架 6(代码优先模型)中使用 DB 拦截来实现软删除功能,但是间歇性地我收到 SQL Server 抛出的异常,指出“您的 SQL 语句的某些部分是嵌套的太深了。重写查询或将其分解为更小的查询。"

这是一个正在生成的 sql 示例,您可以看到生成的 SQL 非常离谱:

https://gist.github.com/junderhill/87caceac728809a8ca837b9d8b5189f3

我的 EF Intercept 的代码如下:

 public class SoftDeleteInterceptor : IDbCommandTreeInterceptor
{
    public const string IsDeletedColumnName = "IsDeleted";
    public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
    {
        if (interceptionContext.OriginalResult.DataSpace != DataSpace.SSpace)
        {
            return;
        }

        var queryCommand = interceptionContext.Result as DbQueryCommandTree;
        if (queryCommand != null)
        {
            interceptionContext.Result = HandleQueryCommand(queryCommand);
        }
    }

    private static DbCommandTree HandleQueryCommand(DbQueryCommandTree queryCommand)
    {
        var newQuery = queryCommand.Query.Accept(new SoftDeleteQueryVisitor());
        return new DbQueryCommandTree(
            queryCommand.MetadataWorkspace,
            queryCommand.DataSpace,
            newQuery);
    }

    public class SoftDeleteQueryVisitor : DefaultExpressionVisitor
    {
        public override DbExpression Visit(DbScanExpression expression)
        {
            var table = (EntityType)expression.Target.ElementType;
            if (table.Properties.All(p => p.Name != IsDeletedColumnName))
            {
                return base.Visit(expression);
            }

            var binding = expression.Bind();
            return binding.Filter(
                binding.VariableType
                    .Variable(binding.VariableName)
                    .Property(IsDeletedColumnName)
                    .NotEqual(DbExpression.FromBoolean(true)));
        }
    }

【问题讨论】:

  • 您应该查看 EF 团队项目经理的 this 工作。
  • 不幸的是我仍然有同样的问题。我不明白为什么 EF 会为看起来应该很简单的东西生成如此晦涩的 SQL。
  • 您找到解决方案了吗?

标签: sql-server entity-framework ef-code-first soft-delete


【解决方案1】:

更新:

这确实工作正常。但是,随着时间的推移,某些事情导致我们的服务器显着变慢。每次我们重置 IIS 应用程序池时,它会很快,但似乎 EF 以某种方式缓存或泄漏内存并减慢它的速度。我们暂时撤出了拦截器。


我们在工作中遇到了这个问题,经过很长时间后,这里有一个解决方案(尚未经过广泛测试;如果需要,我会用任何更新进行更新):

要从根目录开始,在SoftDeleteQueryVisitor 中,如果需要添加过滤器,则返回return binding.Filter(...),它会修改查询。这反过来会触发TreeCreated。这会遍历树,再次找到DbScanExpression,然后重新添加过滤器。显而易见的解决方案是,如果已经有过滤器,则不要添加它。

查看树的结构,我们发现filter是scan的父节点:

+       queryCommand    {DbQueryCommandTree
|_Parameters
| |_p__linq__0 : Edm.Guid
|_Query : Collection{Record['IDGUID'=Edm.Guid, 'Name'=Edm.String, 'FooField'=Edm.String, 'BarField'=Edm.Boolean, 'Group'=Edm.String, 'IsDeleted'=Edm.Boolean]}
  |_Project
    |_Input : 'Limit1'
    | |_Limit
    |   |_Filter
    |   | |_Input : 'Extent1'
    |   | | |_Filter
    |   | |   |_Input : 'Var_8'
    |   | |   | |_Scan : CodeFirstDatabase.LeaderboardSuite
    |   | |   |_Predicate
    |   | |     |_
    |   | |       |_Var(Var_8).IsDeleted
    |   | |       |_<>
    |   | |       |_True
    |   | |_Predicate
    |   |   |_
    |   |     |_Var(Extent1).IDGUID
    |   |     |_=
    |   |     |_@p__linq__0
    |   |_2
    |_Projection
      |_NewInstance : Record['IDGUID'=Edm.Guid, 'Name'=Edm.String, 'FooField'=Edm.String, 'BarField'=Edm.Boolean, 'Group'=Edm.String, 'IsDeleted'=Edm.Boolean]
        |_Column : 'IDGUID'
        | |_Var(Limit1).IDGUID
        |_Column : 'Name'
        | |_Var(Limit1).Name
        |_Column : 'FooField'
        | |_Var(Limit1).FooField
        |_Column : 'BarField'
        | |_Var(Limit1).BarField
        |_Column : 'Group'
        | |_Var(Limit1).Group
        |_Column : 'IsDeleted'
          |_Var(Limit1).IsDeleted}  System.Data.Entity.Core.Common.CommandTrees.DbQueryCommandTree

然而,一旦我们意识到我们拥有访问者和访问者的生命周期,我们就会意识到我们可以存储状态:

var newQuery = queryCommand.Query.Accept(new SoftDeleteQueryVisitor());

我们还知道Filter 节点将在Scan 节点之前被命中,因为它是Scan 节点的父节点。此时,我们要考虑两种可能:查询上没有where子句,查询上有where子句。

此时,我只是遍历树,确定查询谓词是否在EqualsNotEquals 子句中使用了IsDeleted 字段,如果是,则不在那里添加节点。我没有费心检查OrXor 子句,因为这些情况极不可能发生。但是,如果您发现需要它们,它们很容易添加。

public class SoftDeleteQueryVisitor : DefaultExpressionVisitor
{
    private readonly List<DbScanExpression> _scanExpressions = new List<DbScanExpression>();

    public override DbExpression Visit(DbScanExpression expression)
    {
        var table = (EntityType)expression.Target.ElementType;
        if (table.Properties.All(p => p.Name != InterceptorConstants.IsDeletedColumnName))
        {
            return base.Visit(expression);
        }

        if (_scanExpressions.Contains(expression))
        {
            return base.Visit(expression);
        }

        var binding = expression.Bind();
        return binding.Filter(
            binding.VariableType
                .Variable(binding.VariableName)
                .Property(InterceptorConstants.IsDeletedColumnName)
                .NotEqual(DbExpression.FromBoolean(true)));
    }

    private bool HasField(DbScanExpression expression)
    {
        var table = (EntityType)expression.Target.ElementType;
        return table.Properties.Any(p => p.Name == InterceptorConstants.IsDeletedColumnName);
    }

    private bool HasExpression(DbExpression predicate)
    {
        if (predicate is DbAndExpression andExpression)
        {
            return HasExpression(andExpression.Left) || HasExpression(andExpression.Right);
        }

        if (predicate is DbComparisonExpression comparisonExpression &&
            (comparisonExpression.ExpressionKind == DbExpressionKind.NotEquals ||
            comparisonExpression.ExpressionKind == DbExpressionKind.Equals))
        {
            if (comparisonExpression.Right is DbPropertyExpression rightPropertyExpression &&
                rightPropertyExpression.Property.Name == InterceptorConstants.IsDeletedColumnName ||
                comparisonExpression.Left is DbPropertyExpression leftPropertyExpression &&
                leftPropertyExpression.Property.Name == InterceptorConstants.IsDeletedColumnName)
            {
                return true;
            }
        }

        return false;
    }

    public override DbExpression Visit(DbFilterExpression expression)
    {
        if (expression.Input.Expression is DbScanExpression scanExpression)
        {
            if (HasField(scanExpression))
            {
                if (HasExpression(expression.Predicate))
                {
                    _scanExpressions.Add(scanExpression);
                }
            }
        }

        return base.Visit(expression);
    }
}

【讨论】:

    猜你喜欢
    • 2013-05-10
    • 1970-01-01
    • 2020-12-28
    • 1970-01-01
    • 2017-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多