从EF Core查询中获取SQL代码

Mig*_*ura 48 entity-framework-core

我正在使用Entity Framework 7 Core RC2(重命名为:EF Core),我需要查看正在生成哪个SQL代码.在先前版本的Entity Framework中,我可以使用以下内容:

String sql = ((System.Data.Objects.ObjectQuery)query).ToTraceString();
Run Code Online (Sandbox Code Playgroud)

其中query是IQueryable对象...但是EF Core中没有ToTraceString.

我怎样才能在EF Core中做类似的事情?

Nik*_*tov 55

由于EF 7重命名为Entity Framework Core,因此我将总结EF Core的选项.

记录SQL语句有3种方法IQueryable<>:

  • 使用内置或自定义日志记录.使用您选择的记录器或本教程中提到的.NET Core中的内置Logger记录执行查询.
  • 使用Profiler.使用像MiniProfiler这样的SQL事件探查器来监视正在执行的查询.
  • 使用疯狂反射代码.您可以实现一些类似于旧方法的自定义反射代码来执行相同的基本概念.

这是疯狂的反射代码(扩展方法):

public static class IQueryableExtensions
{
    private static readonly TypeInfo QueryCompilerTypeInfo = typeof(QueryCompiler).GetTypeInfo();

    private static readonly FieldInfo QueryCompilerField = typeof(EntityQueryProvider).GetTypeInfo().DeclaredFields.First(x => x.Name == "_queryCompiler");

    private static readonly FieldInfo QueryModelGeneratorField = QueryCompilerTypeInfo.DeclaredFields.First(x => x.Name == "_queryModelGenerator");

    private static readonly FieldInfo DataBaseField = QueryCompilerTypeInfo.DeclaredFields.Single(x => x.Name == "_database");

    private static readonly PropertyInfo DatabaseDependenciesField = typeof(Database).GetTypeInfo().DeclaredProperties.Single(x => x.Name == "Dependencies");

    public static string ToSql<TEntity>(this IQueryable<TEntity> query) where TEntity : class
    {
        var queryCompiler = (QueryCompiler)QueryCompilerField.GetValue(query.Provider);
        var modelGenerator = (QueryModelGenerator)QueryModelGeneratorField.GetValue(queryCompiler);
        var queryModel = modelGenerator.ParseQuery(query.Expression);
        var database = (IDatabase)DataBaseField.GetValue(queryCompiler);
        var databaseDependencies = (DatabaseDependencies)DatabaseDependenciesField.GetValue(database);
        var queryCompilationContext = databaseDependencies.QueryCompilationContextFactory.Create(false);
        var modelVisitor = (RelationalQueryModelVisitor)queryCompilationContext.CreateQueryModelVisitor();
        modelVisitor.CreateQueryExecutor<TEntity>(queryModel);
        var sql = modelVisitor.Queries.First().ToString();

        return sql;
    }
}
Run Code Online (Sandbox Code Playgroud)

将此扩展方法添加到代码后,您可以使用以下方法:

// Build a query using Entity Framework
var query = _context.Widgets.Where(w => w.IsReal && w.Id == 42);  
// Get the generated SQL
var sql = query.ToSql();  
Run Code Online (Sandbox Code Playgroud)

推荐:http://rion.io/2016/10/19/accessing-entity-framework-core-queries-behind-the-scenes-in-asp-net-core/https://gist.github.com/rionmonster/2c59f449e67edf8cd6164e9fe66c545a

更新:已编辑,以便支持EF Core 2.1

  • .Net Core 3.0 和 EF Core 3.0 现已在 GA 中发布,并且对 ToSql 方法进行了重大更改。知道如何在 3.0 中重新实现它吗?更多信息:https://github.com/aspnet/EntityFrameworkCore/issues/18029 (2认同)

Tho*_*ter 22

对于EF Core 2.1.2,您可以使用它.

    public static class QueryableExtensions
    {
        private static readonly TypeInfo QueryCompilerTypeInfo = typeof(QueryCompiler).GetTypeInfo();

        private static readonly FieldInfo QueryCompilerField = typeof(EntityQueryProvider).GetTypeInfo().DeclaredFields.First(x => x.Name == "_queryCompiler");
        private static readonly FieldInfo QueryModelGeneratorField = typeof(QueryCompiler).GetTypeInfo().DeclaredFields.First(x => x.Name == "_queryModelGenerator");
        private static readonly FieldInfo DataBaseField = QueryCompilerTypeInfo.DeclaredFields.Single(x => x.Name == "_database");
        private static readonly PropertyInfo DatabaseDependenciesField = typeof(Database).GetTypeInfo().DeclaredProperties.Single(x => x.Name == "Dependencies");

        public static string ToSql<TEntity>(this IQueryable<TEntity> query)
        {
            var queryCompiler = (QueryCompiler) QueryCompilerField.GetValue(query.Provider);
            var queryModelGenerator = (QueryModelGenerator)QueryModelGeneratorField.GetValue(queryCompiler);
            var queryModel = queryModelGenerator.ParseQuery(query.Expression);
            var database = DataBaseField.GetValue(queryCompiler);
            var databaseDependencies = (DatabaseDependencies) DatabaseDependenciesField.GetValue(database);
            var queryCompilationContext = databaseDependencies.QueryCompilationContextFactory.Create(false);
            var modelVisitor = (RelationalQueryModelVisitor) queryCompilationContext.CreateQueryModelVisitor();
            modelVisitor.CreateQueryExecutor<TEntity>(queryModel);
            var sql = modelVisitor.Queries.First().ToString();

            return sql;
        }
    }
Run Code Online (Sandbox Code Playgroud)

  • 对于“EF Core 2.1”,这些是我需要的唯一“using”(以防止引用不明确的问题)。`使用 System.Linq; 使用系统反射;使用 Microsoft.EntityFrameworkCore.Query;使用 Microsoft.EntityFrameworkCore.Query.Internal;使用 Microsoft.EntityFrameworkCore.Storage;` (2认同)

tom*_*dox 18

对于只想诊断一次性未解决的EF Core查询或类似问题并且不想更改其代码的任何人,有两种选择:

使用SQL Server Management Studio(SSMS)SQL事件探查器

如果安装了SQL Server Management Studio(SSMS),则可以从SSMS的“工具”菜单中启动SQL事件探查器

SQL Server Management Studio(SSMS)的“工具”菜单中的“ SQL Profiler”选项

一旦它打开,然后启动在SQL Profiler中运行的新跟踪。

然后,您将能够看到来自EF的传入SQL请求,它们通常结构良好且易于阅读。

在Visual Studio中检查输出窗口

在我的VS2019副本中,使用EF2.2,我可以更改输出窗口以显示Web服务器的输出(在“输出”窗格顶部的“显示输出来自”组合中选择应用程序和Web服务器的名称)传出的SQL也显示在其中。我已经检查了我的代码,据我所知我还没有做任何事情来启用它,所以我认为默认情况下它必须这样做:

在此处输入图片说明

  • 不是 Azure Sql 的选项 (4认同)

小智 11

对于上述 EF Core 3.1 解决方案,System.InvalidCastException在使用myQueryable.Where(x => ids.Contains(x.Id)). (生成以下形式的 sql 语句SELECT ... FROM ... WHERE ID IN (...)

System.InvalidCastException
  Message=Unable to cast object of type 'Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlParameterExpression' to type 'Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlConstantExpression'.
  Source=Microsoft.EntityFrameworkCore.Relational
  StackTrace:
   at Microsoft.EntityFrameworkCore.Query.QuerySqlGenerator.VisitIn(InExpression inExpression) in /_/src/EFCore.Relational/Query/QuerySqlGenerator.cs:line 577
   at Microsoft.EntityFrameworkCore.Query.SqlExpressionVisitor.VisitExtension(Expression extensionExpression) in /_/src/EFCore.Relational/Query/SqlExpressionVisitor.cs:line 37
   at System.Linq.Expressions.Expression.Accept(ExpressionVisitor visitor) in /_/src/System.Linq.Expressions/src/System/Linq/Expressions/Expression.cs:line 164
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) in /_/src/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs:line 34
Run Code Online (Sandbox Code Playgroud)

为了解决这个问题,请将代码调整为类似的内容:

EF 核心 3.1

System.InvalidCastException
  Message=Unable to cast object of type 'Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlParameterExpression' to type 'Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlConstantExpression'.
  Source=Microsoft.EntityFrameworkCore.Relational
  StackTrace:
   at Microsoft.EntityFrameworkCore.Query.QuerySqlGenerator.VisitIn(InExpression inExpression) in /_/src/EFCore.Relational/Query/QuerySqlGenerator.cs:line 577
   at Microsoft.EntityFrameworkCore.Query.SqlExpressionVisitor.VisitExtension(Expression extensionExpression) in /_/src/EFCore.Relational/Query/SqlExpressionVisitor.cs:line 37
   at System.Linq.Expressions.Expression.Accept(ExpressionVisitor visitor) in /_/src/System.Linq.Expressions/src/System/Linq/Expressions/Expression.cs:line 164
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) in /_/src/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs:line 34
Run Code Online (Sandbox Code Playgroud)


Yep*_*kai 5

我的看法基于@nikolay-kostov 的回答。

不同之处在于,我获取的 SQL 命令带有提取的参数,而不是硬编码的参数,这更符合 EF Core 将命令发送到数据库的方式。另外,如果您想编辑命令并将其发送到数据库,最好使用参数。

    private static class IQueryableUtils 
    {
        private static readonly TypeInfo QueryCompilerTypeInfo = typeof(QueryCompiler).GetTypeInfo();

        private static readonly FieldInfo QueryCompilerField = typeof(EntityQueryProvider).GetTypeInfo().DeclaredFields.First(x => x.Name == "_queryCompiler");

        private static readonly FieldInfo QueryModelGeneratorField = QueryCompilerTypeInfo.DeclaredFields.First(x => x.Name == "_queryModelGenerator");
        private static readonly FieldInfo queryContextFactoryField = QueryCompilerTypeInfo.DeclaredFields.First(x => x.Name == "_queryContextFactory");
        private static readonly FieldInfo loggerField = QueryCompilerTypeInfo.DeclaredFields.First(x => x.Name == "_logger");
        private static readonly FieldInfo DataBaseField = QueryCompilerTypeInfo.DeclaredFields.Single(x => x.Name == "_database");

        private static readonly PropertyInfo DatabaseDependenciesField = typeof(Database).GetTypeInfo().DeclaredProperties.Single(x => x.Name == "Dependencies");

        public static (string sql, IReadOnlyDictionary<string, object> parameters) ToSql<TEntity>(IQueryable<TEntity> query) where TEntity : class
        {
            var queryCompiler = (QueryCompiler)QueryCompilerField.GetValue(query.Provider);
            var queryContextFactory = (IQueryContextFactory)queryContextFactoryField.GetValue(queryCompiler);
            var logger = (Microsoft.EntityFrameworkCore.Diagnostics.IDiagnosticsLogger<DbLoggerCategory.Query>)loggerField.GetValue(queryCompiler);
            var queryContext = queryContextFactory.Create();
            var modelGenerator = (QueryModelGenerator)QueryModelGeneratorField.GetValue(queryCompiler);
            var newQueryExpression = modelGenerator.ExtractParameters(logger, query.Expression, queryContext);
            var queryModel = modelGenerator.ParseQuery(newQueryExpression);
            var database = (IDatabase)DataBaseField.GetValue(queryCompiler);
            var databaseDependencies = (DatabaseDependencies)DatabaseDependenciesField.GetValue(database);
            var queryCompilationContext = databaseDependencies.QueryCompilationContextFactory.Create(false);
            var modelVisitor = (RelationalQueryModelVisitor)queryCompilationContext.CreateQueryModelVisitor();

            modelVisitor.CreateQueryExecutor<TEntity>(queryModel);
            var command = modelVisitor.Queries.First().CreateDefaultQuerySqlGenerator()
                .GenerateSql(queryContext.ParameterValues);

            return (command.CommandText, queryContext.ParameterValues);
        }
    }

Run Code Online (Sandbox Code Playgroud)