Abrufen von SQL-Code von einem Entity Framework Core IQueryable <T>

101

Ich verwende Entity Framework Core und muss sehen, welcher SQL-Code generiert wird. In früheren Versionen von Entity Framework konnte ich Folgendes verwenden:

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

Wo Abfrage ein IQueryable-Objekt ist ... ToTraceString ist in EF Core jedoch nicht verfügbar.

Wie kann ich in EF Core etwas Ähnliches tun?

Miguel Moura
quelle
3
Mögliches Duplikat von Wie protokolliere ich Abfragen mit Entity Framework 7?
Thomas Boby
Sie könnten dies versuchen: rion.io/2016/10/19/… .
Mikebridge

Antworten:

104

EF Core 5 / Net 5

query.ToQueryString()

Siehe Dokumentation ToQueryString () und Neuigkeiten in EF Core 5.0

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

Für ältere Net Core Frameworks kann eine Erweiterung verwendet werden.

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

siehe Gist von 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);

Das Problem wird auch vom EF Net Core Team verfolgt und ist für die nächste Version geplant.

Thom Kiesewetter
quelle
1
Können Sie ein Beispiel geben, wie dies geschrieben werden sollte, um mit einem IQueryableund nicht mit einem zu arbeiten IQueryable<T>?
Byrno
Ich denke du hast immer eine IQueryable<T>. Siehe widgetBeispiel oben. Haben Sie ein Beispiel, das nur ein IQueryable hat?
Thom Kiesewetter
Ich habe mit github.com/StefH/System.Linq.Dynamic.Core , mit dem Sie eine gibt IQueryablenur
byrnedo
In Ihrem Framework basieren Ihre Abfragen auf einem Enitity-Typ <T>. ToSql benötigt einen enityType, da es den Feld- und Tabellennamen kennen muss, um eine SQL-Anweisung zu erstellen. Ohne diese Informationen geht es nicht.
Thom Kiesewetter
1
var relationalCommandCache = enumerator.Private ("_ relationalCommandCache"); gibt null zurück
Khurram Ali
82

Diese Antwort gilt für EF Core 2.1. Informationen zu EF Core 3.0 und 3.1 finden Sie in der Antwort von @Thom Kiesewetter

Für EF Core 5 wird eine integrierte Methode ToQueryString()verwendetIQueryable<>

Da EF 7 in Entity Framework Core umbenannt wird, fasse ich Ihnen die Optionen für EF Core zusammen.

Es gibt drei Ansätze zum Protokollieren von SQL-Anweisungen IQueryable<>:

  • Verwenden der integrierten oder benutzerdefinierten Protokollierung . Protokollieren der ausgeführten Abfrage mit dem Logger Ihrer Wahl oder dem in .NET Core integrierten Logger, wie in diesem Lernprogramm beschrieben .
  • Verwenden eines Profilers . Verwenden eines SQL-Profilers wie MiniProfiler zum Überwachen der ausgeführten Abfrage.
  • Verwenden von Crazy Reflection Code . Sie können einen benutzerdefinierten Reflektionscode ähnlich dem älteren Ansatz implementieren, um dasselbe Grundkonzept auszuführen.

Hier ist der verrückte Reflexionscode (Erweiterungsmethode):

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

Nachdem Sie diese Erweiterungsmethode zu Ihrem Code hinzugefügt haben, können Sie die Methode wie folgt verwenden:

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

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

Nikolay Kostov
quelle
1
Vielen Dank für die Kommentare. Ich habe den Code aktualisiert, damit er jetzt mit 2.1 funktioniert.
Nikolay Kostov
1
@SteffenMangold es ist für Debugging-Zwecke :) Es soll nicht schnell sein.
Nikolay Kostov
1
@ RicardoPeres: Nein, sie verweisen auf rion.io/2016/10/19/… , wodurch Ihr Beitrag gutgeschrieben wird.
Martijn Pieters
1
@Alexei Ich habe angefangen zu verwenden, optionsBuilder.UseLoggerFactory(LoggerFactory); public static readonly LoggerFactory LoggerFactory = new LoggerFactory(new[] { new ConsoleLoggerProvider((_, __) => true, true) });weil es noch schönere SQL generiert, aber leider auch viel Spam.
Joelty
2
.Net Core 3.0 zusammen mit EF Core 3.0 ist jetzt in GA verfügbar und enthält wichtige Änderungen in Bezug auf die Methode: ToSql. Irgendeine Idee, wie man es für 3.0 neu implementiert? Weitere Informationen: github.com/aspnet/EntityFrameworkCore/issues/18029
borisdj
41

Für alle, die nur versuchen, eine einmalige fehlerhafte EF Core-Abfrage oder ähnliches zu diagnostizieren und ihren Code nicht ändern möchten, gibt es mehrere Optionen:

Verwenden Sie SQL Server Management Studio (SSMS) SQL Profiler

Wenn Sie SQL Server Management Studio (SSMS) installiert haben, können Sie den SQL Profiler einfach über das Menü Extras in SSMS starten:

SQL Profiler-Option im Menü Extras in SQL Server Management Studio (SSMS)

Starten Sie anschließend eine neue Ablaufverfolgung, die in SQL Profiler ausgeführt wird, sobald sie geöffnet wird.

Sie können dann die eingehende SQL-Anfrage von EF sehen. Sie ist im Allgemeinen ziemlich gut geformt und leicht zu lesen.

Überprüfen Sie das Ausgabefenster in Visual Studio

In meiner Kopie von VS2019 kann ich mit EF2.2 das Ausgabefenster so ändern, dass die Ausgabe vom Webserver angezeigt wird (wählen Sie den Namen Ihrer App und Ihres Webservers in der Kombination "Ausgabe anzeigen von" oben im Ausgabebereich aus). Dort wird auch das ausgehende SQL angezeigt. Ich habe meinen Code überprüft und soweit ich sehen kann, habe ich nichts getan, um dies zu aktivieren. Ich denke, dies muss standardmäßig geschehen:

Geben Sie hier die Bildbeschreibung ein

Wenn Sie die an SQL Server gesendeten Parameter in den Abfragen sehen möchten, können Sie dies aktivieren, wenn Sie den DBContext mit der EnableSensitiveDataLoggingMethode einrichten , z

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

@Tich - Lil3p erwähnt in den Kommentaren, dass sie auch einen Schalter verwenden müssen, um das SQL-Debugging auf der Registerkarte Debug auf der Eigenschaftenseite des Projekts (die "sqlDebugging": truein LaunchSettings.json festgelegt ist) zu aktivieren. Ich habe es überprüft und ich habe es für keines meiner Projekte aktiviert, aber es kann sich auch lohnen, damit zu experimentieren, wenn das oben Genannte für Sie nicht funktioniert.

tomRedox
quelle
3
ist keine Option für Azure Sql
Emil
@ Batmaci Ich habe eine andere Methode hinzugefügt, die für Azure
funktionieren
Ich bekomme die Ausgabe von EF Core, aber es zeigt mir nicht die Variablen, die es für @__ p_0 usw. verwendet.
DaleyKD
@DaleyKD Wenn der Speicher mir richtig dient, ist dies ein Sicherheitsproblem. Ich denke, MVC verbirgt die Parameter standardmäßig, da sie vertrauliche Daten enthalten können. Ich denke, eine der Debugging-Optionen für MVC führt dazu, dass die Parameter angezeigt werden, aber ich kann mich nicht erinnern, welche. Ich schaue mir meinen Code an, den ich app.UseDeveloperExceptionPage()in Startup.Configure und services.AddServerSideBlazor() .AddCircuitOptions(options => { options.DetailedErrors = true; });in Startup.ConfigureServices habe. Bei einem dieser Parameter werden möglicherweise die Parameter angezeigt.
TomRedox
1
Dieser Link hat mir geholfen -> thecodebuzz.com/adding-logging-in-entity-framework-core .
Yuri Cardoso
3

Meine Einstellung basiert auf der Antwort von @ nikolay-kostov.

Der Unterschied besteht darin, dass ich den SQL-Befehl mit extrahierten Parametern anstelle von fest codierten Parametern erhalte, was eher der Art und Weise entspricht, wie EF Core Befehle an die Datenbank sendet. Wenn Sie den Befehl bearbeiten und an die Datenbank senden möchten, empfiehlt es sich, Parameter zu verwenden.

    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
quelle
2

Entity Framework Core 3.x.

Sie können es durch Protokollierung erhalten.

Erstellen Sie die Fabrik:

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

Sagen Sie der DbContextzu verwendenden Fabrik:

optionsBuilder.UseLoggerFactory(_loggerFactory);

Aus diesem Beitrag

Weitere Informationen erhalten Sie, wenn Sie ILogger implementieren möchten:

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
quelle
1

Für EF Core 3.1 mit Variablen habe ich Folgendes (basierend auf einigen GitHub-Kommentaren von halllo ), das oben im Kommentar von @ 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
        };
    }
}

Dies ersetzt möglicherweise nicht alle Typen, aber die meisten sind abgedeckt. Fühlen Sie sich frei zu verlängern.

Rubenisme
quelle
0

Als öffentlicher Dienst:

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

Und dann diese Erweiterungsmethoden (IQueryableExtensions1 für .NET Core 1.0, IQueryableExtensions für .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
quelle
Mit dem neuesten EF Core 2.1.1 funktioniert dies nicht mehr. Fehler beim privaten statischen schreibgeschützten PropertyInfo NodeTypeProviderField = QueryCompilerTypeInfo.DeclaredProperties.Single (x => x.Name == "NodeTypeProvider");
Stef Heyenrath
@Stef Heyenrath: Ich denke, meine Antwort lautet eindeutig .NET Core 1.0 & 2.0 und nicht 2.1 oder 2.2. Die anderen haben bereits den Code für 2.2, 3.0 und 3.1 angegeben. .NET Core 2.1 wurde zum Zeitpunkt des Schreibens dieser Antwort noch nicht veröffentlicht. Es ist perfekt gültig für .NET Core 2.0 und 1.0
Stefan Steiger
0

Für EF Core 3 und höher verfügt EFCore.BulkExtensions über eine ToParametrizedSql-Methode. Mein einziger Kritikpunkt ist, dass die Parameter als Microsoft.Data.SqlClient zurückgegeben werden. Manchmal muss ich sie in System.Data.SqlClient konvertieren, wenn dies mein Verbindungstyp ist.

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

EFCore.BulkExtensions.IQueryableExtensions.ToParametrizedSql
Eric
quelle