简体   繁体   English

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

[英]Get SQL code from an Entity Framework Core IQueryable<T>

I am using Entity Framework Core and I need to see which SQL code is being generated.我正在使用 Entity Framework Core,我需要查看正在生成哪些 SQL 代码。 In previous versions of Entity Framework I could use the following:在以前版本的实体框架中,我可以使用以下内容:

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

Where query is an IQueryable object ... But ToTraceString is not available in EF Core.其中 query 是一个 IQueryable 对象......但 ToTraceString 在 EF Core 中不可用。

How can I do something similar in EF Core?如何在 EF Core 中做类似的事情?

EF core 5 / Net 5 EF 核心 5 / 网 5

query.ToQueryString()

See Documentation ToQueryString() and What's New in EF Core 5.0请参阅文档 ToQueryString()EF Core 5.0 中的新增功能

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

For older net core frameworks an Extension can be used.对于较旧的网络核心框架,可以使用扩展。

Core 2.1.2核心 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英孚核心 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;
        }

see Gist from RosiOliRosiOli 的要点

EF Core 3.1英孚核心 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);

The issue is also tracked by the EF net core team and is scheduled for the next release.该问题也由EF 网络核心团队跟踪,并计划在下一个版本中发布。

This answer is for EF Core 2.1.此答案适用于 EF Core 2.1。

For EF Core 3.0 and 3.1 see the @Thom Kiesewetter's answer对于 EF Core 3.0 和 3.1,请参阅@Thom Kiesewetter 的回答

For EF Core 5 there will be built-in method ToQueryString() used on IQueryable<>对于 EF Core 5,将在IQueryable<>上使用内置方法ToQueryString()

Since EF 7 is renamed to Entity Framework Core I will summarize you the options for EF Core.由于 EF 7 已重命名为 Entity Framework Core,因此我将为您总结 EF Core 的选项。

There are 3 approaches for logging SQL statements from IQueryable<> :IQueryable<>记录 SQL 语句有 3 种方法:

  • Using Built-in or Custom Logging .使用内置或自定义日志记录 Logging the executing query using your logger of choice or the built-in Logger in .NET Core as mentioned in this tutorial .本教程中所述,使用您选择的记录器或 .NET Core 中的内置记录器记录正在执行的查询。
  • Using a Profiler .使用探查器 Using an SQL Profiler like MiniProfiler to monitor the executing query.使用像MiniProfiler这样的 SQL Profiler 来监视正在执行的查询。
  • Using Crazy Reflection Code .使用疯狂的反射代码 You can implement some custom reflection code similar to the older approach to perform the same basic concept.您可以实现一些类似于旧方法的自定义反射代码来执行相同的基本概念。

Here is the crazy reflection code (extension method):这是疯狂的反射代码(扩展方法):

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;
    }
}

After adding this extension method to your code, you can use the method as follows:将此扩展方法添加到您的代码后,您可以按如下方式使用该方法:

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

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

For anyone just trying to diagnose a one-off misfiring EF Core query or the like and not wanting to change their code, there are a couple of options:对于任何只是试图诊断一次性错误的 EF Core 查询或类似问题而不想更改其代码的人,有以下几种选择:

Use SQL Server Management Studio (SSMS) SQL Profiler使用 SQL Server Management Studio (SSMS) SQL Profiler

If you've got SQL Server Management Studio (SSMS) installed you can just fire up the SQL Profiler from the Tools menu in SSMS:如果您已经安装了 SQL Server Management Studio (SSMS),则可以从 SSMS 的“工具”菜单中启动 SQL Profiler

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

And then start a new trace running in SQL Profiler once it opens.然后在 SQL Profiler 打开后开始在它中运行的新跟踪。

You'll then be able to see the incoming SQL request from EF, they are generally pretty well formed and easy to read.然后您将能够看到来自 EF 的传入 SQL 请求,它们通常格式良好且易于阅读。

Check the Output Window in Visual Studio检查 Visual Studio 中的输出窗口

In my copy of VS2019, using EF2.2 I can change the output window to show the output from the Web Server (select the name of your app and web server in the "Show output from" combo at the top of the Output pane) and the outgoing SQL is also shown in there.在我的 VS2019 副本中,使用 EF2.2 我可以更改输出窗口以显示来自 Web 服务器的输出(在“输出”窗格顶部的“显示输出”组合中选择您的应用程序和 Web 服务器的名称)并且传出的 SQL 也显示在那里。 I've checked my code and as far as I can see I haven't done anything to enable that, so I think it must do this by default:我已经检查了我的代码,据我所知,我没有做任何事情来启用它,所以我认为它必须默认这样做:

在此处输入图片说明

If you want to see the parameters sent to SQL server in the queries you can switch that on when setting up the DBContext with the EnableSensitiveDataLogging method, eg如果您想在查询中查看发送到 SQL 服务器的参数,您可以在使用EnableSensitiveDataLogging方法设置 DBContext 时将其打开,例如

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

@Tich -- Lil3p mentions in the comments that they also needed to use a switch to turn on SQL Debugging in the Debug tab of the project's Properties page (which sets "sqlDebugging": true in LaunchSettings.json). @Tich——Lil3p在评论中提到,他们还需要使用一个开关来打开项目属性页面的调试选项卡中的SQL 调试(在 LaunchSettings.json 中设置"sqlDebugging": true )。 I checked and I haven't got that switched on for any of my projects, but that may be worth experimenting with too if the above isn't working for you.我检查过,我没有为我的任何项目打开它,但如果上述方法不适合你,那也可能值得尝试。

My take based on @nikolay-kostov answer.我的看法基于@nikolay-kostov 的回答。

The difference is that I get the SQL command with parameters extracted instead of hard coded which is more in line with how EF Core send commands to the database.不同之处在于我得到的 SQL 命令是提取参数而不是硬编码,这更符合 EF Core 向数据库发送命令的方式。 Also, if you want to edit and send the command to the database, it is a better practice to use parameters.另外,如果要编辑命令并将命令发送到数据库,最好使用参数。

    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);
        }
    }

Entity Framework Core 3.x实体框架核心 3.x

You can get it through logging.可以通过日志获取。

Create the factory:创建工厂:

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
    .AddConsole((options) => { })
    .AddFilter((category, level) =>
        category == DbLoggerCategory.Database.Command.Name
        && level == LogLevel.Information);
});

Tell the DbContext which factory to use:告诉DbContext使用哪个工厂:

optionsBuilder.UseLoggerFactory(_loggerFactory);

From this post这个帖子

You can get more information if you want to implement ILogger:如果您想实现 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
}

Adding this answer because all the suggestions here have broken with new EF Core releases (ie, all the answers here are broken on EF Core 2.2).添加此答案是因为此处的所有建议已因新的 EF Core 版本而失效(即,此处的所有答案在 EF Core 2.2 上均已失效)。 Here's code that worked for me on the first try, and seems to be .NET Core version agnostic (so far): https://blogs.msdn.microsoft.com/dbrowne/2017/09/22/simple-logging-for-ef-core/这是第一次尝试时对我有用的代码,似乎与 .NET Core 版本无关(到目前为止): https : //blogs.msdn.microsoft.com/dbrowne/2017/09/22/simple-logging-for -ef核心/

For EF Core 3.1 with variables, I have the following (based on some GitHub comments from halllo ) that was linked above in the comment from @ Thom Kiesewetter et al.对于带有变量的 EF Core 3.1,我有以下内容(基于来自 Halllo 的一些GitHub 评论),在上面来自 @ Thom Kiesewetter等人的评论中链接。

/// <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
        };
    }
}

This doesn't substitute all types perhaps but most are covered.这可能无法替代所有类型,但大多数都已涵盖。 Feel free to extend.随意扩展。

For EF Core 3 and above, EFCore.BulkExtensions has a ToParametrizedSql method.对于 EF Core 3 及更高版本,EFCore.BulkExtensions 有一个 ToParametrizedSql 方法。 My only gripe is that it returns the parameters as Microsoft.Data.SqlClient, so sometimes I have to convert them to System.Data.SqlClient if that is my connection type.我唯一的抱怨是它将参数作为 Microsoft.Data.SqlClient 返回,所以有时我必须将它们转换为 System.Data.SqlClient 如果这是我的连接类型。

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

EFCore.BulkExtensions.IQueryableExtensions.ToParametrizedSql

For the EF Core 3.1 solution above you might encounter an System.InvalidCastException (for details, see below) when using queries like myQueryable.Where(x => ids.Contains(x.Id)) .对于上面的EF Core 3.1 解决方案,在使用类似myQueryable.Where(x => ids.Contains(x.Id))查询时您可能会遇到System.InvalidCastException (有关详细信息,请参见下文myQueryable.Where(x => ids.Contains(x.Id)) (Produces sql statements of the form SELECT ... FROM ... WHERE ID IN (...) ) (产生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

In order to fix this, adjust the code to something like that:为了解决这个问题,将代码调整为如下所示:

EF Core 3.1英孚核心 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);

As a public service:作为公共服务:

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

And then these extension methods (IQueryableExtensions1 for .NET Core 1.0, IQueryableExtensions for .NET Core 2.0) :然后是这些扩展方法(.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://stackoverflow.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;
            }


        }


    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM