entity-framework - 当抛出 EntitySqlException 时,我可以看到正在进行的 SQL,或者我可以覆盖 EntityCommandCompilationException 吗?

标签 entity-framework entity-framework-6 custom-function

我在尝试在 Entity Framework 中执行窗口函数时遇到了一些麻烦。 (有问题的项目建立在 EF 上,但会从在 SQL Server 上计算 PERCENTILE_DISC 获得很多 yield 。)

我正在创建一个新的约定并将其添加到对象模型中,以便将对某个方法的调用转换为执行 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 的不同问题。我怎样才能完成这些?

最佳答案

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

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;
    }
}
}
要使用此拦截器,您可以编写如下代码:
using System.Data.Entity.Infrastructure.Interception;
DbInterception.Add(NinjectWebCommon.Kernel.Get<SqlQueryCommandInterceptor>());
就我而言,我将此代码放入我的网络应用程序启动:Global.asax.cs。我使用 Ninject 来获取拦截器的一个实例。
我的代码可以做的比你需要的更多......所以你可能需要阅读以更好地理解并适应你的需求。
我希望它能帮助你。

关于entity-framework - 当抛出 EntitySqlException 时,我可以看到正在进行的 SQL,或者我可以覆盖 EntityCommandCompilationException 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56535365/

相关文章:

c# - 我可以避免对简单的 Linq to Entities 投影进行嵌套 SQL 查询吗?

google-apps-script - 无法识别自定义函数

google-apps-script - 将单元格引用传递给电子表格函数

json - 如何让 Google 表格每 60 秒刷新一次?

asp.net-mvc - Azure + EF 6 - 让代码优先迁移发挥作用

c# - 使用表达式树动态构建 EF4 查询,NotSupportedException

c# - Linq To Entities 方法错误包含 NotSupportedException

entity-framework - 有没有人有一个非常完整的 EF 6.1 示例通用存储库?

C# Linq 快速计算列表中项目的有效方法

entity-framework - Npgsql 的 EntityFramework6 创建无效的更新语句