Obtenir du code SQL à partir d'un Entity Framework Core IQueryable <T>

92

J'utilise Entity Framework Core et j'ai besoin de voir quel code SQL est généré. Dans les versions précédentes d'Entity Framework, je pouvais utiliser ce qui suit:

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

Où la requête est un objet IQueryable ... Mais ToTraceString n'est pas disponible dans EF Core.

Comment puis-je faire quelque chose de similaire dans EF Core?

Miguel Moura
la source
Vous pouvez essayer ceci: rion.io/2016/10/19/… .
mikebridge

Réponses:

82

EF core 5 / Net 5

query.ToQueryString()Voir les nouveautés d'EF Core 5.0

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

Pour les frameworks Net Core plus anciens, une extension peut être utilisée.

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

voir Gist de 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);

Le problème est également suivi par l' équipe principale d'EF net et est prévu pour la prochaine version.

Thom Kiesewetter
la source
1
Pourriez-vous donner un exemple de la façon dont cela devrait être écrit pour fonctionner avec un IQueryableet pas un IQueryable<T>?
byrnedo
Je pense que vous avez toujours un fichier IQueryable<T>. Voir l' widgetexemple ci-dessus. Avez-vous un exemple qui n'a qu'un IQueryable.
Thom Kiesewetter
J'ai utilisé github.com/StefH/System.Linq.Dynamic.Core , qui vous donne un IQueryablejuste
byrnedo
Dans votre cadre, vos requêtes sont basées sur un type d'entité <T>. ToSql a besoin d'un enityType car il doit connaître le nom du champ et de la table pour créer une instruction sql. Cela ne peut se faire sans ces informations.
Thom Kiesewetter le
1
var relationalCommandCache = énumérateur.Private ("_ relationalCommandCache"); renvoie null
Khurram Ali
81

Cette réponse concerne EF Core 2.1. Pour EF Core 3.0 et 3.1, voir la réponse de @Thom Kiesewetter

Pour EF Core 5, une méthode intégrée sera ToQueryString()utilisée surIQueryable<>

Depuis EF 7 est renommé Entity Framework Core, je vais vous résumer les options pour EF Core.

Il existe 3 approches pour la journalisation des instructions SQL à partir de IQueryable<>:

  • Utilisation de la journalisation intégrée ou personnalisée . Journalisation de la requête en cours d'exécution à l'aide de votre enregistreur de choix ou de l'enregistreur intégré dans .NET Core, comme mentionné dans ce didacticiel .
  • Utilisation d'un profileur . Utilisation d'un SQL Profiler comme MiniProfiler pour surveiller la requête en cours d'exécution.
  • Utilisation du code de réflexion fou . Vous pouvez implémenter un code de réflexion personnalisé similaire à l'ancienne approche pour appliquer le même concept de base.

Voici le code de réflexion fou (méthode d'extension):

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

Après avoir ajouté cette méthode d'extension à votre code, vous pouvez utiliser la méthode comme suit:

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

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

Nikolay Kostov
la source
1
Merci pour les commentaires. J'ai mis à jour le code pour qu'il fonctionne avec 2.1 maintenant.
Nikolay Kostov
1
@SteffenMangold c'est à des fins de débogage :) Il n'est pas destiné à être rapide.
Nikolay Kostov
1
@RicardoPeres: non, ils font référence à rion.io/2016/10/19/… , ce qui crédite votre message.
Martijn Pieters
1
@Alexei J'ai commencé à utiliser optionsBuilder.UseLoggerFactory(LoggerFactory); public static readonly LoggerFactory LoggerFactory = new LoggerFactory(new[] { new ConsoleLoggerProvider((_, __) => true, true) });car il génère des sql encore plus beaux, mais malheureusement aussi beaucoup de spam.
Joelty
2
.Net Core 3.0 avec EF Core 3.0 est maintenant publié dans GA, et il a des changements de rupture concernant la méthode: ToSql. Une idée comment le réimplémenter pour 3.0? Plus d'infos: github.com/aspnet/EntityFrameworkCore/issues/18029
borisdj
40

Pour tous ceux qui essaient simplement de diagnostiquer une requête EF Core ayant un raté unique ou autre et ne souhaitant pas modifier leur code, il existe plusieurs options:

Utiliser le profileur SQL SQL Server Management Studio (SSMS)

Si vous avez installé SQL Server Management Studio (SSMS), vous pouvez simplement lancer le SQL Profiler à partir du menu Outils de SSMS:

Option SQL Profiler dans le menu Outils de SQL Server Management Studio (SSMS)

Et puis démarrez une nouvelle trace exécutée dans SQL Profiler une fois qu'elle s'ouvre.

Vous pourrez alors voir la requête SQL entrante d'EF, elles sont généralement assez bien formées et faciles à lire.

Vérifiez la fenêtre de sortie dans Visual Studio

Dans ma copie de VS2019, en utilisant EF2.2, je peux modifier la fenêtre de sortie pour afficher la sortie du serveur Web (sélectionnez le nom de votre application et de votre serveur Web dans la liste déroulante "Afficher la sortie de" en haut du volet Sortie) et le SQL sortant y est également montré. J'ai vérifié mon code et pour autant que je sache, je n'ai rien fait pour l'activer, donc je pense qu'il doit le faire par défaut:

entrez la description de l'image ici

Si vous souhaitez voir les paramètres envoyés au serveur SQL dans les requêtes, vous pouvez l'activer lors de la configuration du DBContext avec la EnableSensitiveDataLoggingméthode, par exemple

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

@Tich - Lil3p mentionne dans les commentaires qu'ils avaient également besoin d'utiliser un commutateur pour activer le débogage SQL dans l'onglet Débogage de la page Propriétés du projet (qui définit "sqlDebugging": truedans LaunchSettings.json). J'ai vérifié et je ne l'ai activé pour aucun de mes projets, mais cela vaut peut-être la peine d'être expérimenté si ce qui précède ne fonctionne pas pour vous.

tomRedox
la source
3
n'est pas une option pour Azure Sql
Emil
@batmaci J'ai ajouté une autre méthode qui pourrait fonctionner pour Azure
tomRedox
Je reçois la sortie d'EF Core, mais il ne me montre pas les variables qu'il utilise pour @__ p_0, etc.
DaleyKD
@DaleyKD si la mémoire me sert correctement, c'est un problème de sécurité - je pense que MVC cache les paramètres par défaut car ils pourraient inclure des données sensibles. Je pense que l'une des options de débogage pour MVC entraînera l'affichage des paramètres, mais je ne me souviens pas laquelle. En regardant mon code que j'ai app.UseDeveloperExceptionPage()dans Startup.Configure et services.AddServerSideBlazor() .AddCircuitOptions(options => { options.DetailedErrors = true; });Startup.ConfigureServices. L'un de ceux-ci pourrait afficher les paramètres.
tomRedox
1
Ce lien m'a aidé -> thecodebuzz.com/adding-logging-in-entity-framework-core .
Yuri Cardoso
3

Mon point de vue basé sur la réponse @ nikolay-kostov.

La différence est que j'obtiens la commande SQL avec des paramètres extraits au lieu de codés en dur, ce qui correspond davantage à la façon dont EF Core envoie des commandes à la base de données. De plus, si vous souhaitez modifier et envoyer la commande à la base de données, il est préférable d'utiliser des paramètres.

    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
la source
2

Entity Framework Core 3.x

Vous pouvez l'obtenir via la journalisation.

Créez l'usine:

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

Dites à l' DbContextusine à utiliser:

optionsBuilder.UseLoggerFactory(_loggerFactory);

De ce post

Vous pouvez obtenir plus d'informations si vous souhaitez implémenter 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
la source
1

Pour EF Core 3.1 avec des variables, j'ai ce qui suit (basé sur certains commentaires GitHub de halllo ) qui a été lié ci-dessus dans le commentaire de @ 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
        };
    }
}

Cela ne remplace peut-être pas tous les types, mais la plupart sont couverts. N'hésitez pas à prolonger.

Rubénisme
la source
0

En tant que service public:

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

Et puis ces méthodes d'extension (IQueryableExtensions1 pour .NET Core 1.0, IQueryableExtensions pour .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
la source
Avec le dernier EF Core 2.1.1, cela ne fonctionne plus. Erreur lors de la lecture seule statique privée PropertyInfo NodeTypeProviderField = QueryCompilerTypeInfo.DeclaredProperties.Single (x => x.Name == "NodeTypeProvider");
Stef Heyenrath
@Stef Heyenrath: Je pense que ma réponse indique clairement .NET Core 1.0 & 2.0 et, pas 2.1 ou 2.2. Les autres ont déjà donné le code pour 2.2, 3.0 et 3.1. .NET Core 2.1 n'était pas publié au moment où j'ai écrit cette réponse. C'est parfaitement valable pour .NET Core 2.0 et 1.0
Stefan Steiger
0

Pour EF Core 3 et supérieur, EFCore.BulkExtensions a une méthode ToParametrizedSql. Mon seul reproche est qu'il renvoie les paramètres en tant que Microsoft.Data.SqlClient, donc parfois je dois les convertir en System.Data.SqlClient si c'est mon type de connexion.

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

EFCore.BulkExtensions.IQueryableExtensions.ToParametrizedSql
Eric
la source