从 Entity Framework Core IQueryable<T> 获取 SQL 代码

Posted

技术标签:

【中文标题】从 Entity Framework Core IQueryable<T> 获取 SQL 代码【英文标题】:Get SQL code from an Entity Framework Core IQueryable<T> 【发布时间】:2016-09-28 09:48:21 【问题描述】:

我正在使用 Entity Framework Core,我需要查看正在生成哪些 SQL 代码。在以前版本的实体框架中,我可以使用以下内容:

string sql = ((System.Data.Objects.ObjectQuery)query).ToTraceString();

其中 query 是一个 IQueryable 对象...但 ToTraceString 在 EF Core 中不可用。

如何在 EF Core 中做类似的事情?

【问题讨论】:

How to log queries using Entity Framework 7?的可能重复 你可以试试这个:rion.io/2016/10/19/…. 【参考方案1】:

EF 核心 5/6 / 网络 5/6

query.ToQueryString()

见Documentation ToQueryString()和What's New in EF Core 5.0

var query = _context.Widgets.Where(w => w.IsReal && w.Id == 42);  
var sql = query.ToQueryString();

对于较旧的网络核心框架,可以使用扩展。

核心 2.1.2


using System.Linq;
using System.Reflection;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.Sql;
using static Microsoft.EntityFrameworkCore.DbLoggerCategory;

    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;
        
    

EF Core 3.0

        public static string ToSql<TEntity>(this IQueryable<TEntity> query)
        
            using var enumerator = query.Provider.Execute<IEnumerable<TEntity>>(query.Expression).GetEnumerator();
            var enumeratorType = enumerator.GetType();
            var selectFieldInfo = enumeratorType.GetField("_selectExpression", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException($"cannot find field _selectExpression on type enumeratorType.Name");
            var sqlGeneratorFieldInfo = enumeratorType.GetField("_querySqlGeneratorFactory", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException($"cannot find field _querySqlGeneratorFactory on type enumeratorType.Name");
            var selectExpression = selectFieldInfo.GetValue(enumerator) as SelectExpression ?? throw new InvalidOperationException($"could not get SelectExpression");
            var factory = sqlGeneratorFieldInfo.GetValue(enumerator) as IQuerySqlGeneratorFactory ?? throw new InvalidOperationException($"could not get IQuerySqlGeneratorFactory");
            var sqlGenerator = factory.Create();
            var command = sqlGenerator.GetCommand(selectExpression);
            var sql = command.CommandText;
            return sql;
        

见RosiOli的要点

EF Core 3.1

using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Query;

public static string ToSql<TEntity>(this IQueryable<TEntity> query) where TEntity : class

    using var enumerator = query.Provider.Execute<IEnumerable<TEntity>>(query.Expression).GetEnumerator();
    var relationalCommandCache = enumerator.Private("_relationalCommandCache");
    var selectExpression = relationalCommandCache.Private<SelectExpression>("_selectExpression");
    var factory = relationalCommandCache.Private<IQuerySqlGeneratorFactory>("_querySqlGeneratorFactory");

    var sqlGenerator = factory.Create();
    var command = sqlGenerator.GetCommand(selectExpression);

    string sql = command.CommandText;
    return sql;


private static object Private(this object obj, string privateField) => obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);
private static T Private<T>(this object obj, string privateField) => (T)obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);

EF net core team 也跟踪该问题,并计划在下一个版本中发布。

【讨论】:

您能否举例说明如何编写此代码以使用IQueryable 而不是IQueryable&lt;T&gt; 我想你总是有一个IQueryable&lt;T&gt;。请参阅上面的 widget 示例。你有一个只有 IQueryable 的例子吗? var relationalCommandCache = enumerator.Private("_relationalCommandCache");返回空 对于EF Core 2.1,这些是我唯一需要的usings(以防止引用不明确的问题)。 using System.Linq; using System.Reflection; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Storage; 请注意,如果 Query 包含 Func&lt;T&gt;.ToQueryString() 方法确实会引发异常。【参考方案2】:

此答案适用于 EF Core 2.1。

对于 EF Core 3.0 和 3.1,请参阅 @Thom Kiesewetter 的回答

对于 EF Core 5,将在 IQueryable&lt;&gt; 上使用内置方法 ToQueryString()

由于 EF 7 已重命名为 Entity Framework Core,我将总结一下 EF Core 的选项。

IQueryable&lt;&gt;记录SQL语句有3种方法:

使用内置或自定义日志记录。使用您选择的记录器或 .NET Core 中的内置记录器记录执行查询,如 this tutorial 中所述。 使用 Profiler。使用像 MiniProfiler 这样的 SQL Profiler 来监控正在执行的查询。 使用疯狂反射代码。您可以实现一些类似于旧方法的自定义反射代码来执行相同的基本概念。

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

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;
    

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

// 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();  

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

【讨论】:

感谢 cmets。我更新了代码,现在它应该可以与 2.1 一起使用。 @SteffenMangold 用于调试目的 :) 它的目的不是为了快速。 @RicardoPeres:不,他们引用了rion.io/2016/10/19/…,这归功于您的帖子。 @Alexei 我开始使用optionsBuilder.UseLoggerFactory(LoggerFactory); public static readonly LoggerFactory LoggerFactory = new LoggerFactory(new[] new ConsoleLoggerProvider((_, __) =&gt; true, true) ); 是因为它会生成更漂亮的sql,但不幸的是也有很多垃圾邮件。 .Net Core 3.0 和 EF Core 3.0 现已在 GA 中发布,它对方法进行了重大更改:ToSql。知道如何为 3.0 重新实现它吗?更多信息:github.com/aspnet/EntityFrameworkCore/issues/18029【参考方案3】:

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

使用 SQL Server Management Studio (SSMS) SQL Profiler

如果您已安装 SQL Server Management Studio (SSMS),您只需从 SSMS 的“工具”菜单中fire up the SQL Profiler:

然后在 SQL Profiler 打开后启动一个新的跟踪。

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

在 Visual Studio 中检查输出窗口

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

如果您想在查询中查看发送到 SQL 服务器的参数,您可以在使用 EnableSensitiveDataLogging 方法设置 DBContext 时将其打开,例如

services.AddDbContext<FusionContext>(options => options
    .UseSqlServer(connectionString))
    //.EnableDetailedErrors()
    .EnableSensitiveDataLogging()

@Tich -- Lil3p 在 cmets 中提到,他们还需要使用项目属性页面的 a switch to turn on SQL Debugging in the Debug tab(在 LaunchSettings.json 中设置 "sqlDebugging": true)。我检查过,我的任何项目都没有启用该功能,但如果上述方法不适合您,那可能也值得尝试。

【讨论】:

不是 Azure Sql 的选项 @batmaci 我添加了另一种可能适用于 Azure 的方法 我确实从 EF Core 获得了输出,但它没有显示它用于 @__p_0 等的变量。 @DaleyKD 如果记忆对我有用,这是一个安全问题 - 我认为 MVC 默认隐藏参数,因为它们可能包含敏感数据。我认为 MVC 的调试选项之一会导致显示参数,但我不记得是哪一个。查看我的代码,我在 Startup.Configure 中有 app.UseDeveloperExceptionPage(),在 Startup.ConfigureServices 中有 services.AddServerSideBlazor() .AddCircuitOptions(options =&gt; options.DetailedErrors = true; );。其中之一可能会显示参数。 这个链接帮助了我 -> thecodebuzz.com/adding-logging-in-entity-framework-core.【参考方案4】:

我的看法基于@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);
        
    

【讨论】:

【参考方案5】:

实体框架核心 3.x

你可以通过日志获得它。

创建工厂:

var loggerFactory = LoggerFactory.Create(builder =>

    builder
    .AddConsole((options) =>  )
    .AddFilter((category, level) =>
        category == DbLoggerCategory.Database.Command.Name
        && level == LogLevel.Information);
);

告诉DbContext使用哪个工厂:

optionsBuilder.UseLoggerFactory(_loggerFactory);

来自this post

如果您想实现 ILogger,可以获取更多信息:

public class EntityFrameworkSqlLogger : ILogger

    #region Fields
    Action<EntityFrameworkSqlLogMessage> _logMessage;
    #endregion
    #region Constructor
    public EntityFrameworkSqlLogger(Action<EntityFrameworkSqlLogMessage> logMessage)
    
        _logMessage = logMessage;
    
    #endregion
    #region Implementation
    public IDisposable BeginScope<TState>(TState state)
    
        return default;
    
    public bool IsEnabled(LogLevel logLevel)
    
        return true;
    
    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    
        if (eventId.Id != 20101)
        
            //Filter messages that aren't relevant.
            //There may be other types of messages that are relevant for other database platforms...
            return;
        
        if (state is IReadOnlyList<KeyValuePair<string, object>> keyValuePairList)
        
            var entityFrameworkSqlLogMessage = new EntityFrameworkSqlLogMessage
            (
                eventId,
                (string)keyValuePairList.FirstOrDefault(k => k.Key == "commandText").Value,
                (string)keyValuePairList.FirstOrDefault(k => k.Key == "parameters").Value,
                (CommandType)keyValuePairList.FirstOrDefault(k => k.Key == "commandType").Value,
                (int)keyValuePairList.FirstOrDefault(k => k.Key == "commandTimeout").Value,
                (string)keyValuePairList.FirstOrDefault(k => k.Key == "elapsed").Value
            );
            _logMessage(entityFrameworkSqlLogMessage);
        
    
    #endregion

【讨论】:

【参考方案6】:

添加此答案是因为此处的所有建议都与新的 EF Core 版本不同(即,此处的所有答案在 EF Core 2.2 上都已失效)。这是第一次尝试对我有用的代码,似乎与 .NET Core 版本无关(到目前为止):https://blogs.msdn.microsoft.com/dbrowne/2017/09/22/simple-logging-for-ef-core/

【讨论】:

【参考方案7】:

对于带有变量的 EF Core 3.1,我在上面来自 @Thom Kiesewetter 等人的评论中链接了以下内容(基于一些 GitHub comments from halllo)。

/// <summary>
/// SQL Extension methods to get the SQL and check correctness
/// Class can be removed with EF Core 5 (https://github.com/dotnet/efcore/issues/6482#issuecomment-587605366) (although maybe variable substitution might still be necessary if we want them inline)
/// </summary>
public static class SqlExtensions

    private static object Private(this object obj, string privateField) => obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);
    private static T Private<T>(this object obj, string privateField) => (T)obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);

    /// <summary>
    /// Gets a SQL statement from an IQueryable
    /// </summary>
    /// <param name="query">The query to get the SQL statement for</param>
    /// <returns>Formatted SQL statement as a string</returns>
    public static string ToQueryString<TEntity>(this IQueryable<TEntity> query) where TEntity : class
    
        using var enumerator = query.Provider.Execute<IEnumerable<TEntity>>(query.Expression).GetEnumerator();
        var relationalCommandCache = enumerator.Private("_relationalCommandCache");
        var selectExpression = relationalCommandCache.Private<SelectExpression>("_selectExpression");
        var factory = relationalCommandCache.Private<IQuerySqlGeneratorFactory>("_querySqlGeneratorFactory");
        var relationalQueryContext = enumerator.Private<RelationalQueryContext>("_relationalQueryContext");

        var sqlGenerator = factory.Create();
        var command = sqlGenerator.GetCommand(selectExpression);
        var parametersDict = relationalQueryContext.ParameterValues;

        return SubstituteVariables(command.CommandText, parametersDict);
    

    private static string SubstituteVariables(string commandText, IReadOnlyDictionary<string, object> parametersDictionary)
    
        var sql = commandText;
        foreach (var (key, value) in parametersDictionary)
        
            var placeHolder = "@" + key;
            var actualValue = GetActualValue(value);
            sql = sql.Replace(placeHolder, actualValue);
        

        return sql;
    

    private static string GetActualValue(object value)
    
        var type = value.GetType();

        if (type.IsNumeric())
            return value.ToString();

        if (type == typeof(DateTime) || type == typeof(DateTimeOffset))
        
            switch (type.Name)
            
                case nameof(DateTime):
                    return $"'(DateTime)value:u'";

                case nameof(DateTimeOffset):
                    return $"'(DateTimeOffset)value:u'";
            
        

        return $"'value'";
    

    private static bool IsNullable(this Type type)
    
        return
            type != null &&
            type.IsGenericType &&
            type.GetGenericTypeDefinition() == typeof(Nullable<>);
    

    private static bool IsNumeric(this Type type)
    
        if (IsNullable(type))
            type = Nullable.GetUnderlyingType(type);

        if (type == null || type.IsEnum)
            return false;

        return Type.GetTypeCode(type) switch
        
            TypeCode.Byte => true,
            TypeCode.Decimal => true,
            TypeCode.Double => true,
            TypeCode.Int16 => true,
            TypeCode.Int32 => true,
            TypeCode.Int64 => true,
            TypeCode.SByte => true,
            TypeCode.Single => true,
            TypeCode.UInt16 => true,
            TypeCode.UInt32 => true,
            TypeCode.UInt64 => true,
            _ => false
        ;
    

这可能并不能替代所有类型,但大多数类型都已涵盖。随意扩展。

【讨论】:

【参考方案8】:

对于EF Core 3.1 solution above,在使用myQueryable.Where(x =&gt; ids.Contains(x.Id)) 之类的查询时,您可能会遇到System.InvalidCastException(有关详细信息,请参见下文)。 (产生SELECT ... FROM ... WHERE ID IN (...)形式的sql语句)

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

为了解决这个问题,请将代码调整为:

EF Core 3.1

using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Query;

public static string ToSql<TEntity>(this IQueryable<TEntity> query) where TEntity : class

    using var enumerator = query.Provider.Execute<IEnumerable<TEntity>>(query.Expression).GetEnumerator();
    var relationalCommandCache = enumerator.Private("_relationalCommandCache");
    var selectExpression = relationalCommandCache.Private<SelectExpression>("_selectExpression");
    var factory = relationalCommandCache.Private<IQuerySqlGeneratorFactory>("_querySqlGeneratorFactory");
    var relationalQueryContext = enumerator.Private<RelationalQueryContext>("_relationalQueryContext");
    var parameterValueBasedSelectExpressionOptimizer = relationalCommandCache.Private<ParameterValueBasedSelectExpressionOptimizer>("_parameterValueBasedSelectExpressionOptimizer");

    (selectExpression, _) = parameterValueBasedSelectExpressionOptimizer.Optimize(selectExpression, relationalQueryContext.ParameterValues);

    var sqlGenerator = factory.Create();
    var command = sqlGenerator.GetCommand(selectExpression);

    string sql = command.CommandText;
    return sql;


private static object Private(this object obj, string privateField) => obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);
private static T Private<T>(this object obj, string privateField) => (T)obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);

【讨论】:

【参考方案9】:

对于 EF Core 3 及更高版本,EFCore.BulkExtensions 有一个 ToParametrizedSql 方法。我唯一的抱怨是它以 Microsoft.Data.SqlClient 的形式返回参数,所以如果那是我的连接类型,有时我必须将它们转换为 System.Data.SqlClient。

https://github.com/borisdj/EFCore.BulkExtensions

EFCore.BulkExtensions.IQueryableExtensions.ToParametrizedSql

【讨论】:

【参考方案10】:

作为公共服务:

    var someQuery = (
        from projects in _context.projects
        join issues in _context.issues on projects.Id equals issues.ProjectId into tmpMapp
        from issues in tmpMapp.DefaultIfEmpty()
        select issues
    ) //.ToList()
    ;

    // string sql = someQuery.ToString();
    // string sql = Microsoft.EntityFrameworkCore.IQueryableExtensions.ToSql(someQuery);
    // string sql = Microsoft.EntityFrameworkCore.IQueryableExtensions1.ToSql(someQuery);
    // using Microsoft.EntityFrameworkCore;
    string sql = someQuery.ToSql();
    System.Console.WriteLine(sql);

然后是这些扩展方法(.NET Core 1.0 的 IQueryableExtensions1,.NET Core 2.0 的 IQueryableExtensions):

    using System;
    using System.Linq;
    using System.Reflection;
    using Microsoft.EntityFrameworkCore.Internal;
    using Microsoft.EntityFrameworkCore.Query;
    using Microsoft.EntityFrameworkCore.Query.Internal;
    using Microsoft.EntityFrameworkCore.Storage;
    using Remotion.Linq.Parsing.Structure;


    namespace Microsoft.EntityFrameworkCore
    

        // https://***.com/questions/1412863/how-do-i-view-the-sql-generated-by-the-entity-framework
        // http://rion.io/2016/10/19/accessing-entity-framework-core-queries-behind-the-scenes-in-asp-net-core/

        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 PropertyInfo NodeTypeProviderField =
                QueryCompilerTypeInfo.DeclaredProperties.Single(x => x.Name == "NodeTypeProvider");

            private static readonly MethodInfo CreateQueryParserMethod =
                QueryCompilerTypeInfo.DeclaredMethods.First(x => x.Name == "CreateQueryParser");

            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
            
                if (!(query is EntityQueryable<TEntity>) && !(query is InternalDbSet<TEntity>))
                
                    throw new ArgumentException("Invalid query");
                

                var queryCompiler = (QueryCompiler) QueryCompilerField.GetValue(query.Provider);
                var nodeTypeProvider = (INodeTypeProvider) NodeTypeProviderField.GetValue(queryCompiler);
                var parser = (IQueryParser) CreateQueryParserMethod.Invoke(queryCompiler, new object[] nodeTypeProvider);
                var queryModel = parser.GetParsedQuery(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;
            
        



        public class IQueryableExtensions1
        
            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 PropertyInfo NodeTypeProviderField =
                QueryCompilerTypeInfo.DeclaredProperties.Single(x => x.Name == "NodeTypeProvider");

            private static readonly MethodInfo CreateQueryParserMethod =
                QueryCompilerTypeInfo.DeclaredMethods.First(x => x.Name == "CreateQueryParser");

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

            private static readonly FieldInfo QueryCompilationContextFactoryField = typeof(Database).GetTypeInfo()
                .DeclaredFields.Single(x => x.Name == "_queryCompilationContextFactory");


            public static string ToSql<TEntity>(IQueryable<TEntity> query) where TEntity : class
            
                if (!(query is EntityQueryable<TEntity>) && !(query is InternalDbSet<TEntity>))
                
                    throw new ArgumentException("Invalid query");
                

                var queryCompiler = (IQueryCompiler) QueryCompilerField.GetValue(query.Provider);

                var nodeTypeProvider = (INodeTypeProvider) NodeTypeProviderField.GetValue(queryCompiler);
                var parser =
                    (IQueryParser) CreateQueryParserMethod.Invoke(queryCompiler, new object[] nodeTypeProvider);
                var queryModel = parser.GetParsedQuery(query.Expression);
                var database = DataBaseField.GetValue(queryCompiler);
                var queryCompilationContextFactory =
                    (IQueryCompilationContextFactory) QueryCompilationContextFactoryField.GetValue(database);
                var queryCompilationContext = queryCompilationContextFactory.Create(false);
                var modelVisitor = (RelationalQueryModelVisitor) queryCompilationContext.CreateQueryModelVisitor();
                modelVisitor.CreateQueryExecutor<TEntity>(queryModel);
                var sql = modelVisitor.Queries.First().ToString();

                return sql;
            


        


    

【讨论】:

使用最新的 EF Core 2.1.1 ,这不再起作用。私有静态只读 PropertyInfo NodeTypeProviderField = QueryCompilerTypeInfo.DeclaredProperties.Single(x => x.Name == "NodeTypeProvider"); 时出错 @Stef Heyenrath:我认为我的回答清楚地说明了 .NET Core 1.0 和 2.0,而不是 2.1 或 2.2。其他人已经给出了 2.2、3.0 和 3.1 的代码。在我写这个答案的时候,.NET Core 2.1 还没有发布。它对 .NET Core 2.0 和 1.0 完全有效

以上是关于从 Entity Framework Core IQueryable<T> 获取 SQL 代码的主要内容,如果未能解决你的问题,请参考以下文章

从 Entity Framework Core IQueryable<T> 获取 SQL 代码

Entity Framework Core 删除包含已读取实体的实体

如何使用 FromSqlRaw Entity Framework Core 3.1 从存储过程中返回多个 SELECT 集

如何使用 Entity Framework Core 将 SQL 数据从一个表传输到另一个表

Entity Framework Core 中的动态查询执行

Entity Framework Core 忽略 .Include(..) 而没有 .ToList(..) 间接