抛出EntitySqlException时是否可以查看正在进行的SQL,或者可以重写EntityCommandCompilationException?

时间:2019-06-11 01:36:49

标签: entity-framework entity-framework-6

我尝试在Entity Framework中执行窗口功能时遇到了麻烦。 (有问题的项目是基于EF构建的,但是通过在SQL Server上计算PERCENTILE_DISC会获得很多好处。)

我正在创建一个新的Convention,并将其添加到对象模型中,以将对某个方法的调用转换为执行PERCENT_DISC窗口函数的SQL。

创建的EdmFunction的CommandText为:

PERCENTILE_DISC (0.8) WITHIN GROUP (ORDER BY o.ExpectedPayment) OVER (PARTITION BY o.OrderType)

但是当我执行此功能时:

var medians = context.Set<UserLocation>().Select(x => CustomFunction.Percentile()).ToList();

这会引发EntityCommandCompilationException并显示以下消息:

System.Data.Entity.Core.EntityCommandCompilationException:
'An error occurred while preparing definition of the function 'ConsoleApp5.Percentile'. See the inner exception for details.'

Inner Exception:
EntitySqlException: The query syntax is not valid. Near identifier 'WITHIN', line 1, column 35.

但是此直接查询获得了预期的结果:

var p80s = context.Database.SqlQuery<decimal>("SELECT DISTINCT PERCENTILE_DISC (0.8) WITHIN GROUP (ORDER BY o.ExpectedPayment) OVER (PARTITION BY o.OrderType) from Orders o").ToList();

我怀疑这是由于未构建EF解析器来处理窗口功能。因此,我希望能够重写EntityCommandCompilationException并使EF尝试以任何方式执行查询。失败了,我至少希望看到到目前为止生成的SQL,看看是否存在导致真正无效的SQL的其他问题。我该怎么做?

1 个答案:

答案 0 :(得分:0)

我在一个使用一个拦截器注册慢查询的项目中工作。 下面,我放置了拦截器的代码:

class Player:

    def __init__(self, name, tank_name):
        self.name = name
        self.tank_name = tank_name

def new_player():
    name = input('What is your name >>> ')
    tank_name = input('What is your tanks name >>> ')
    p1 = Player(name, tank_name)

new_player()

要使用此拦截器,您可以编写如下代码:

using System;
using System.Data.Common;
using System.Data.Entity.Infrastructure.Interception;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Nash.Data.Interceptors.SlowQuery
{
public sealed class SqlQueryCommandInterceptor : IDbCommandInterceptor
{
    private readonly ISlowQueryLogger _slowQueryLogger;

    public SqlQueryCommandInterceptor(ISlowQueryLogger slowQueryLogger)
    {
        _slowQueryLogger = slowQueryLogger;
    }

    public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        => interceptionContext.UserState = Stopwatch.StartNew();

    public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        => LogSlowQuery(interceptionContext, command);

    public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        => interceptionContext.UserState = Stopwatch.StartNew();

    public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        => LogSlowQuery(interceptionContext, command);

    public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        => interceptionContext.UserState = Stopwatch.StartNew();

    public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        => LogSlowQuery(interceptionContext, command);

    private void LogSlowQuery<T>(DbCommandInterceptionContext<T> interceptionContext, DbCommand dbCommand)
    {
        var debugText = GetDbCommandDebugText(dbCommand);
        var userState = (Stopwatch)interceptionContext.UserState;
        userState.Stop();
        var elapsed = userState.Elapsed;
        if (elapsed > TimeSpan.FromSeconds(2.6))
            _slowQueryLogger.LogSlowQuery(debugText, elapsed);
    }

    private static string GetDbCommandDebugText(DbCommand dbCommand)
    {
        var debugText = dbCommand.CommandText;
        if (dbCommand is SqlCommand && debugText.Contains("@"))
        {
            var matches = Regex.Matches(debugText, @"(\@[\w\.]+)").Cast<Match>().ToArray();
            var paramDict = dbCommand.Parameters.Cast<SqlParameter>()
                .Select(x => new
                {
                    ParameterName = x.ParameterName.StartsWith("@") ? x.ParameterName : "@" + x.ParameterName,
                    Value = x.Value,
                })
                .ToDictionary(x => x.ParameterName, x => x.Value);
            var buffer = new StringBuilder();
            var i = 0;
            foreach (var m in matches)
            {
                if (m.Index > i)
                {
                    buffer.Append(debugText.Substring(i, m.Index - i));
                    i = m.Index;
                }
                var paramName = m.Groups[1].Value;
                if (paramDict.TryGetValue(paramName, out var paramVal))
                    if (paramVal == null || DBNull.Value.Equals(paramVal))
                        buffer.Append($"NULL");
                    else
                        buffer.Append($"'{paramVal}'");
                else
                    buffer.Append(paramName);
                i += m.Length;
            }
            if (i < debugText.Length)
                buffer.Append(debugText.Substring(i, debugText.Length - i));
            debugText = buffer.ToString();
        }
        return debugText;
    }
}
}

就我而言,我将此代码放入Web应用程序启动程序:Global.asax.cs。我使用Ninject来获取拦截器的一个实例。

我的代码可以做比您需要的更多的工作...因此您可能需要阅读以更好地理解并使其适应您的需求。

我希望它能为您提供帮助。