Dapatkan kode SQL dari Entity Framework Core IQuerable <T>

93

Saya menggunakan Entity Framework Core dan saya perlu melihat kode SQL mana yang sedang dibuat. Di versi sebelumnya dari Entity Framework saya bisa menggunakan yang berikut ini:

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

Jika kueri merupakan objek IQuerable ... Tapi ToTraceString tidak tersedia di EF Core.

Bagaimana saya bisa melakukan hal serupa di EF Core?

Miguel Moura
sumber
Anda dapat mencoba ini: rion.io/2016/10/19/… .
mikebridge

Jawaban:

83

EF inti 5 / Net 5

query.ToQueryString()Lihat Yang Baru di EF Core 5.0

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

Untuk kerangka inti bersih yang lebih lama, Ekstensi dapat digunakan.

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

lihat Inti dari 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
{
    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);

Masalah ini juga dilacak oleh tim inti bersih EF dan dijadwalkan untuk rilis berikutnya.

Thom Kiesewetter
sumber
1
Bisakah Anda memberi contoh bagaimana ini harus ditulis untuk bekerja dengan IQueryabledan bukan IQueryable<T>?
byrnedo
Saya pikir Anda selalu memiliki file IQueryable<T>. Lihat widgetcontoh di atas. Apakah Anda memiliki contoh yang hanya memiliki IQuerizable.
Thom Kiesewetter
Saya telah menggunakan github.com/StefH/System.Linq.Dynamic.Core , yang memberi Anda IQueryablehanya
byrnedo
Dalam kerangka kerja Anda, kueri Anda didasarkan pada jenis entitas <T>. ToSql memerlukan enityType karena ia perlu mengetahui bidang dan nama tabel untuk membuat pernyataan sql. Ini tidak dapat dilakukan tanpa informasi ini.
Thom Kiesewetter
1
var relationalCommandCache = enumerator.Private ("_ relationalCommandCache"); mengembalikan null
Khurram Ali
81

Jawaban ini untuk EF Core 2.1. Untuk EF Core 3.0 dan 3.1 lihat jawaban @Thom Kiesewetter

Untuk EF Core 5 akan ada metode built-in yang ToQueryString()digunakanIQueryable<>

Karena EF 7 diubah namanya menjadi Entity Framework Core, saya akan meringkas opsi untuk EF Core.

Ada 3 pendekatan untuk mencatat pernyataan SQL dari IQueryable<>:

  • Menggunakan Built-in atau Custom Logging . Membuat log kueri pelaksana menggunakan logger pilihan Anda atau Logger bawaan di .NET Core seperti yang disebutkan dalam tutorial ini .
  • Menggunakan Profiler . Menggunakan SQL Profiler seperti MiniProfiler untuk memantau kueri yang sedang dijalankan.
  • Menggunakan Kode Refleksi Gila . Anda dapat menerapkan beberapa kode refleksi kustom yang mirip dengan pendekatan lama untuk menjalankan konsep dasar yang sama.

Berikut adalah kode refleksi gila (metode ekstensi):

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

Setelah menambahkan metode ekstensi ini ke kode Anda, Anda dapat menggunakan metode sebagai berikut:

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

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

Nikolay Kostov
sumber
1
Terima kasih atas komentarnya Saya memperbarui kode sehingga seharusnya berfungsi dengan 2.1 sekarang.
Nikolay Kostov
1
@SteffenMangold itu untuk tujuan debugging :) Ini tidak dimaksudkan untuk menjadi cepat.
Nikolay Kostov
1
@RicardoPeres: tidak, mereka mereferensikan rion.io/2016/10/19/… , yang memberi kredit pada pos Anda.
Martijn Pieters
1
@Alexei Saya mulai menggunakan optionsBuilder.UseLoggerFactory(LoggerFactory); public static readonly LoggerFactory LoggerFactory = new LoggerFactory(new[] { new ConsoleLoggerProvider((_, __) => true, true) });karena menghasilkan sql yang lebih indah, tetapi sayangnya juga banyak spam.
Joelty
2
.Net Core 3.0 bersama dengan EF Core 3.0 sekarang dirilis di GA, dan memiliki perubahan besar terkait metode: ToSql. Ada ide bagaimana menerapkannya kembali untuk 3.0? Info lebih lanjut: github.com/aspnet/EntityFrameworkCore/issues/18029
borisdj
40

Untuk siapa pun yang hanya mencoba mendiagnosis kueri EF Core yang salah sasaran atau sejenisnya dan tidak ingin mengubah kodenya, ada beberapa opsi:

Gunakan SQL Server Management Studio (SSMS) SQL Profiler

Jika Anda telah menginstal SQL Server Management Studio (SSMS), Anda dapat menjalankan SQL Profiler dari menu Tools di SSMS:

Opsi SQL Profiler di menu Alat di SQL Server Management Studio (SSMS)

Dan kemudian mulai jejak baru yang berjalan di SQL Profiler setelah terbuka.

Anda kemudian akan dapat melihat permintaan SQL yang masuk dari EF, biasanya format tersebut cukup baik dan mudah dibaca.

Periksa Output Window di Visual Studio

Dalam salinan VS2019 saya, menggunakan EF2.2 saya dapat mengubah jendela keluaran untuk menampilkan keluaran dari Server Web (pilih nama aplikasi dan server web Anda di kombo "Tampilkan keluaran dari" di bagian atas panel Keluaran) dan SQL keluar juga ditampilkan di sana. Saya telah memeriksa kode saya dan sejauh yang saya lihat saya belum melakukan apa pun untuk mengaktifkannya, jadi saya pikir itu harus melakukan ini secara default:

masukkan deskripsi gambar di sini

Jika Anda ingin melihat parameter yang dikirim ke server SQL dalam kueri, Anda dapat mengaktifkannya saat menyiapkan DBContext dengan EnableSensitiveDataLoggingmetode, mis.

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

@Tich - Lil3p menyebutkan di komentar bahwa mereka juga perlu menggunakan sakelar untuk mengaktifkan SQL Debugging di tab Debug pada halaman Properti proyek (yang "sqlDebugging": truedisetel di LaunchSettings.json). Saya telah memeriksa dan saya belum mengaktifkannya untuk salah satu proyek saya, tetapi itu mungkin layak untuk dicoba juga jika hal di atas tidak berhasil untuk Anda.

tomRedox
sumber
3
bukan merupakan pilihan untuk Azure Sql
Emil
@batmaci Saya telah menambahkan metode lain yang mungkin berfungsi untuk Azure
tomRedox
Saya mendapatkan output dari EF Core, tetapi tidak menunjukkan kepada saya variabel yang digunakannya untuk @__ p_0, dll.
DaleyKD
@DaleyKD jika memori melayani saya dengan benar, itu masalah keamanan - saya pikir MVC menyembunyikan parameter secara default karena mereka dapat menyertakan data sensitif. Saya pikir salah satu opsi debugging untuk MVC akan menyebabkan parameter ditampilkan, tetapi saya tidak dapat mengingat yang mana. Melihat kode saya yang saya miliki app.UseDeveloperExceptionPage()di Startup.Configure dan services.AddServerSideBlazor() .AddCircuitOptions(options => { options.DetailedErrors = true; });di Startup.ConfigureServices. Salah satunya mungkin menampilkan parameter.
tomRedox
1
Tautan ini membantu saya -> thecodebuzz.com/adding-logging-in-entity-framework-core .
Yuri Cardoso
3

Saya mengambil berdasarkan jawaban @ nikolay-kostov.

Perbedaannya adalah saya mendapatkan perintah SQL dengan parameter yang diekstrak daripada hard code yang lebih sejalan dengan cara EF Core mengirim perintah ke database. Selain itu, jika Anda ingin mengedit dan mengirim perintah ke database, sebaiknya gunakan parameter.

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

Yepeekai
sumber
2

Entity Framework Core 3.x

Anda bisa mendapatkannya melalui logging.

Buat pabrik:

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

Beri tahu DbContextpabrik mana yang akan digunakan:

optionsBuilder.UseLoggerFactory(_loggerFactory);

Dari postingan ini

Anda bisa mendapatkan lebih banyak informasi jika Anda ingin menerapkan 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
}
Christian Findlay
sumber
1

Untuk EF Core 3.1 dengan variabel, saya memiliki berikut ini (berdasarkan beberapa komentar GitHub dari halllo ) yang ditautkan di atas dalam komentar dari @ Thom Kiesewetter et al.

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

Ini mungkin tidak menggantikan semua jenis, tetapi sebagian besar sudah tercakup. Jangan ragu untuk memperpanjang.

Rubenisme
sumber
0

Sebagai layanan publik:

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

Dan kemudian metode ekstensi ini (IQueryableExtensions1 untuk .NET Core 1.0, IQueryableExtensions untuk .NET Core 2.0):

    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
    {

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


        }


    }
Stefan Steiger
sumber
Dengan EF Core 2.1.1 terbaru, ini tidak berfungsi lagi. Kesalahan pada hanya baca statis pribadi PropertyInfo NodeTypeProviderField = QueryCompilerTypeInfo.DeclaredProperties.Single (x => x.Name == "NodeTypeProvider");
Stef Heyenrath
@ Stef Heyenrath: Saya pikir jawaban saya dengan jelas menyatakan .NET Core 1.0 & 2.0 dan, bukan 2.1 atau 2.2. Yang lain sudah memberikan kode untuk 2.2, 3.0 dan 3.1. .NET Core 2.1 tidak dirilis pada saat saya menulis jawaban ini. Ini sangat valid untuk .NET Core 2.0 dan 1.0
Stefan Steiger
0

Untuk EF Core 3 dan yang lebih baru, EFCore.BulkExtensions memiliki metode ToParametrizedSql. Satu-satunya keluhan saya adalah ia mengembalikan parameter sebagai Microsoft.Data.SqlClient, jadi terkadang saya harus mengubahnya menjadi System.Data.SqlClient jika itu adalah jenis koneksi saya.

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

EFCore.BulkExtensions.IQueryableExtensions.ToParametrizedSql
Eric
sumber