[entity-framework] Comment afficher le code SQL généré par Entity Framework?



Answers

Pour ceux qui utilisent Entity Framework 6 et plus, si vous voulez voir la sortie SQL dans Visual Studio (comme je l'ai fait), vous devez utiliser la nouvelle fonctionnalité de journalisation / interception.

L'ajout de la ligne suivante crachera le SQL généré (ainsi que d'autres détails liés à l'exécution) dans le panneau de sortie de Visual Studio:

using (MyDatabaseEntities context = new MyDatabaseEntities())
{
    context.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
    // query the database using EF here.
}

Plus d'informations sur la connexion dans EF6 dans cette série de blogs astucieux: http://blog.oneunicorn.com/2013/05/08/ef6-sql-logging-part-1-simple-logging/

Remarque: Assurez-vous de lancer votre projet en mode DEBUG.

Question

Comment afficher le SQL généré par le framework d'entité?

(Dans mon cas particulier, j'utilise le fournisseur mysql - si c'est important)




Dans mon cas pour EF 6+, au lieu de l'utiliser dans la fenêtre Exécution pour trouver la chaîne de requête:

var sql = ((System.Data.Entity.Core.Objects.ObjectQuery)query).ToTraceString();

J'ai fini par devoir l'utiliser pour obtenir la commande SQL générée:

var sql = ((System.Data.Entity.Infrastructure.DbQuery<<>f__AnonymousType3<string,string,string,short,string>>)query).ToString();

Bien sûr, votre signature de type anonyme peut être différente.

HTH.




Nécromage.
Cette page est le premier résultat de recherche lors de la recherche d'une solution pour n'importe quel .NET Framework, donc ici en tant que service public, comment cela se fait dans EntityFramework Core (pour .NET Core 1 & 2):

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
{

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

    public static class IQueryableExtensions
    {
        private static readonly TypeInfo QueryCompilerTypeInfo = typeof(QueryCompiler).GetTypeInfo();

        private static readonly FieldInfo QueryCompilerField = typeof(EntityQueryProvider).GetTypeInfo().DeclaredFields
            .First(x => x.Name == "_queryCompiler");

        private static readonly PropertyInfo NodeTypeProviderField =
            QueryCompilerTypeInfo.DeclaredProperties.Single(x => x.Name == "NodeTypeProvider");

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

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

        private static readonly PropertyInfo DatabaseDependenciesField =
            typeof(Database).GetTypeInfo().DeclaredProperties.Single(x => x.Name == "Dependencies");

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

            var queryCompiler = (QueryCompiler) QueryCompilerField.GetValue(query.Provider);
            var nodeTypeProvider = (INodeTypeProvider) NodeTypeProviderField.GetValue(queryCompiler);
            var parser = (IQueryParser) CreateQueryParserMethod.Invoke(queryCompiler, new object[] {nodeTypeProvider});
            var queryModel = parser.GetParsedQuery(query.Expression);
            var database = DataBaseField.GetValue(queryCompiler);
            var databaseDependencies = (DatabaseDependencies) DatabaseDependenciesField.GetValue(database);
            var queryCompilationContext = databaseDependencies.QueryCompilationContextFactory.Create(false);
            var modelVisitor = (RelationalQueryModelVisitor) queryCompilationContext.CreateQueryModelVisitor();
            modelVisitor.CreateQueryExecutor<TEntity>(queryModel);
            var sql = modelVisitor.Queries.First().ToString();

            return sql;
        }
    }



    public class IQueryableExtensions1
    {
        private static readonly TypeInfo QueryCompilerTypeInfo = typeof(QueryCompiler).GetTypeInfo();

        private static readonly FieldInfo QueryCompilerField = typeof(EntityQueryProvider).GetTypeInfo()
            .DeclaredFields
            .First(x => x.Name == "_queryCompiler");

        private static readonly PropertyInfo NodeTypeProviderField =
            QueryCompilerTypeInfo.DeclaredProperties.Single(x => x.Name == "NodeTypeProvider");

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

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

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


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

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

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

            return sql;
        }


    }


}



Eh bien, j'utilise Express profiler à cet effet pour le moment, l'inconvénient est que cela ne fonctionne que pour MS SQL Server. Vous pouvez trouver cet outil ici: https://expressprofiler.codeplex.com/




Vous pouvez effectuer les opérations suivantes dans EF 4.1:

var result = from x in appEntities
             where x.id = 32
             select x;

System.Diagnostics.Trace.WriteLine(result .ToString());

Cela vous donnera le SQL qui a été généré.




Pour moi, en utilisant EF6 et Visual Studio 2015, j'ai entré la query dans la fenêtre immédiate et il m'a donné la déclaration SQL générée




Ma réponse concerne le cœur EF. Je fais référence à ce problème de github et aux documents sur la configuration de DbContext :

Simple

Substituez la méthode OnConfiguring de votre classe DbContext ( YourCustomDbContext ) comme indiqué ici pour utiliser un ConsoleLoggerProvider; vos requêtes doivent se connecter à la console:

public class YourCustomDbContext : DbContext
{
    #region DefineLoggerFactory
    public static readonly LoggerFactory MyLoggerFactory
        = new LoggerFactory(new[] {new ConsoleLoggerProvider((_, __) => true, true)});
    #endregion


    #region RegisterLoggerFactory
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseLoggerFactory(MyLoggerFactory) // Warning: Do not create a new ILoggerFactory instance each time
            //.UseSqlServer("...");
    #endregion
}

Complexe

Ce cas complexe évite de surcharger la méthode DbContext OnConfiguring . , qui est déconseillée dans les docs: "Cette approche ne se prête pas à des tests, sauf si les tests ciblent la base de données complète."

Ce cas complexe utilise:

  • La IServiceCollection dans la classe ConfigureServices Startup (au lieu de surcharger la méthode OnConfiguring , le bénéfice est un couplage plus lâche entre le DbContext et le ILoggerProvider vous voulez utiliser)
  • Une implémentation de ILoggerProvider (au lieu d'utiliser l'implémentation de ConsoleLoggerProvider montrée ci-dessus, avantage est notre implémentation montre comment nous enregistrerions dans un fichier (je ne vois pas un fournisseur de journalisation de fichiers livré avec EF Core ))

Comme ça:

public class Startup

    public void ConfigureServices(IServiceCollection services)
    {
        ...
        var lf = new LoggerFactory();
        lf.AddProvider(new MyLoggerProvider());

        services.AddDbContext<YOUR_DB_CONTEXT>(optionsBuilder => optionsBuilder
                .UseSqlServer(connection_string)
                //Using the LoggerFactory 
                .UseLoggerFactory(lf));
        ...
    }
}

Voici la mise en œuvre d'un MyLoggerProvider (et de son MyLogger qui ajoute ses logs à un fichier que vous pouvez configurer, vos requêtes EF Core apparaîtront dans le fichier).

public class MyLoggerProvider : ILoggerProvider
{
    public ILogger CreateLogger(string categoryName)
    {
        return new MyLogger();
    }

    public void Dispose()
    { }

    private class MyLogger : ILogger
    {
        public bool IsEnabled(LogLevel logLevel)
        {
            return true;
        }

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            File.AppendAllText(@"C:\temp\log.txt", formatter(state, exception));
            Console.WriteLine(formatter(state, exception));
        }

        public IDisposable BeginScope<TState>(TState state)
        {
            return null;
        }
    } 
}



À partir de EF6.1, vous pouvez utiliser Interceptors pour enregistrer un enregistreur de base de données. Voir les chapitres "Intercepteurs" et "Journalisation des opérations de base de données" dans un fichier here

<interceptors> 
  <interceptor type="System.Data.Entity.Infrastructure.Interception.DatabaseLogger, EntityFramework"> 
    <parameters> 
      <parameter value="C:\Temp\LogOutput.txt"/> 
      <parameter value="true" type="System.Boolean"/> 
    </parameters> 
  </interceptor> 
</interceptors>





Related