197

I am using Entity Framework Core and I need to see which SQL code is being generated. In previous versions of Entity Framework I could use the following:

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

Where query is an IQueryable object ... But ToTraceString is not available in EF Core.

How can I do something similar in EF Core?

tomRedox
  • 28,092
  • 24
  • 117
  • 154
Miguel Moura
  • 36,732
  • 85
  • 259
  • 481
  • 3
    Possible duplicate of [How to log queries using Entity Framework 7?](http://stackoverflow.com/questions/26747837/how-to-log-queries-using-entity-framework-7) – Thomas Boby Jun 01 '16 at 09:16
  • You could try this: http://rion.io/2016/10/19/accessing-entity-framework-core-queries-behind-the-scenes-in-asp-net-core/. – mikebridge Apr 05 '17 at 21:41

10 Answers10

329

EF core 5/6 / Net 5/6

query.ToQueryString()

See Documentation ToQueryString() and What's New in EF Core 5.0

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

For older net core frameworks an Extension can be used.

Core 2.1.2


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)
        {
            using var enumerator = query.Provider.Execute<IEnumerable<TEntity>>(query.Expression).GetEnumerator();
            var enumeratorType = enumerator.GetType();
            var selectFieldInfo = enumeratorType.GetField("_selectExpression", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException($"cannot find field _selectExpression on type {enumeratorType.Name}");
            var sqlGeneratorFieldInfo = enumeratorType.GetField("_querySqlGeneratorFactory", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException($"cannot find field _querySqlGeneratorFactory on type {enumeratorType.Name}");
            var selectExpression = selectFieldInfo.GetValue(enumerator) as SelectExpression ?? throw new InvalidOperationException($"could not get SelectExpression");
            var factory = sqlGeneratorFieldInfo.GetValue(enumerator) as IQuerySqlGeneratorFactory ?? throw new InvalidOperationException($"could not get IQuerySqlGeneratorFactory");
            var sqlGenerator = factory.Create();
            var command = sqlGenerator.GetCommand(selectExpression);
            var sql = command.CommandText;
            return sql;
        }

see Gist from 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
{
    using var enumerator = query.Provider.Execute<IEnumerable<TEntity>>(query.Expression).GetEnumerator();
    var relationalCommandCache = enumerator.Private("_relationalCommandCache");
    var selectExpression = relationalCommandCache.Private<SelectExpression>("_selectExpression");
    var factory = relationalCommandCache.Private<IQuerySqlGeneratorFactory>("_querySqlGeneratorFactory");

    var sqlGenerator = factory.Create();
    var command = sqlGenerator.GetCommand(selectExpression);

    string sql = command.CommandText;
    return sql;
}

private static object Private(this object obj, string privateField) => obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);
private static T Private<T>(this object obj, string privateField) => (T)obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);

The issue is also tracked by the EF net core team and is scheduled for the next release.

Thom Kiesewetter
  • 6,703
  • 3
  • 28
  • 41
  • 1
    Could you give an example of how this should be written to work with an `IQueryable` and not an `IQueryable`? – byrnedo Feb 12 '19 at 13:26
  • I think you always have a `IQueryable`. See `widget` example above. Do you have an example that has only a IQueryable. – Thom Kiesewetter Feb 12 '19 at 16:21
  • I've been using https://github.com/StefH/System.Linq.Dynamic.Core, which gives you an `IQueryable` just – byrnedo Feb 13 '19 at 12:03
  • In your framework your queries are based on an enitity type . ToSql needs an enityType because it needs to know the field and table name to create an sql statement. It cann't be done without this information. – Thom Kiesewetter Feb 14 '19 at 09:23
  • the following line is how to use `string str = articles.ToSql();` – hosam hemaily Jul 27 '19 at 12:22
  • To use with just an IQueryable (I am also using Linq.Dynamic.Core), use like this: `query.ToSql()` – John Aug 24 '19 at 20:06
  • 1
    var relationalCommandCache = enumerator.Private("_relationalCommandCache"); returns null – Khurram Ali Mar 31 '20 at 10:21
  • 2
    For `EF Core 2.1`, these are the only `using`s I needed (to prevent the problem of ambiguous references). `using System.Linq; using System.Reflection; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Storage;` – Ash K Dec 01 '20 at 20:44
  • 1
    Note that the **.ToQueryString()** method does throw a exception if the Query included a `Func`. – FireEmerald Jan 15 '21 at 07:53
  • How about for inserts and updates? – Rei Miyasaka Sep 29 '21 at 07:47
101

This answer is for EF Core 2.1.

For EF Core 3.0 and 3.1 see the @Thom Kiesewetter's answer

For EF Core 5 there will be built-in method ToQueryString() used on IQueryable<>

Since EF 7 is renamed to Entity Framework Core I will summarize you the options for EF Core.

There are 3 approaches for logging SQL statements from IQueryable<>:

  • Using Built-in or Custom Logging. Logging the executing query using your logger of choice or the built-in Logger in .NET Core as mentioned in this tutorial.
  • Using a Profiler. Using an SQL Profiler like MiniProfiler to monitor the executing query.
  • Using Crazy Reflection Code. You can implement some custom reflection code similar to the older approach to perform the same basic concept.

Here is the crazy reflection code (extension method):

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

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

    private static readonly FieldInfo QueryModelGeneratorField = QueryCompilerTypeInfo.DeclaredFields.First(x => x.Name == "_queryModelGenerator");

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

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

    public static string ToSql<TEntity>(this IQueryable<TEntity> query) where TEntity : class
    {
        var queryCompiler = (QueryCompiler)QueryCompilerField.GetValue(query.Provider);
        var modelGenerator = (QueryModelGenerator)QueryModelGeneratorField.GetValue(queryCompiler);
        var queryModel = modelGenerator.ParseQuery(query.Expression);
        var database = (IDatabase)DataBaseField.GetValue(queryCompiler);
        var databaseDependencies = (DatabaseDependencies)DatabaseDependenciesField.GetValue(database);
        var queryCompilationContext = databaseDependencies.QueryCompilationContextFactory.Create(false);
        var modelVisitor = (RelationalQueryModelVisitor)queryCompilationContext.CreateQueryModelVisitor();
        modelVisitor.CreateQueryExecutor<TEntity>(queryModel);
        var sql = modelVisitor.Queries.First().ToString();

        return sql;
    }
}

After adding this extension method to your code, you can use the method as follows:

// Build a query using Entity Framework
var query = _context.Widgets.Where(w => w.IsReal && w.Id == 42);  
// Get the generated SQL
var sql = query.ToSql();  

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

Peter B
  • 22,460
  • 5
  • 32
  • 69
Nikolay Kostov
  • 16,433
  • 23
  • 85
  • 123
  • `NodeTypeProviderField ` throw error. My EF Core is 2.1.1 – Dongdong Aug 24 '18 at 17:29
  • Doesnt work for me, EF core 2.0, always throws `ArgumentException` – meow Aug 30 '18 at 07:55
  • 1
    Thank you for the comments. I updated the code so it should work with 2.1 now. – Nikolay Kostov Aug 30 '18 at 08:07
  • I used this for caching query results, but the ToSql logic is so dam slow. :( – Steffen Mangold Dec 21 '18 at 11:36
  • 1
    @SteffenMangold it is for debugging purposes :) It is not intended to be fast. – Nikolay Kostov Dec 23 '18 at 13:04
  • You forgot to mention that you copied the code from my blog post at https://weblogs.asp.net/ricardoperes/implementing-missing-features-in-entity-framework-core-part-5-getting-the-sql-for-a-query! – Ricardo Peres Feb 12 '19 at 16:01
  • 1
    @RicardoPeres: no, they reference http://rion.io/2016/10/19/accessing-entity-framework-core-queries-behind-the-scenes-in-asp-net-core/, which credits your post. – Martijn Pieters Feb 12 '19 at 16:06
  • Could this be adjusted to support expressions such as `ctx.Users.First()`? I tried to pass a `System.Linq.Expression` obtained from the previous snippet to the model generator and it threw *Only query sources (that is, expressions that implement IEnumerable) and query operators can be parsed.*. What is this query operator they speak of? – Tomáš Hübelbauer Feb 19 '19 at 16:42
  • ``: 'Unable to cast object of type 'Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryQueryModelVisitor' to type 'Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor'.'`` – Joelty Apr 19 '19 at 08:36
  • @Joelty - I have a similar error when using InMemoryDatabase provider. However, you should keep in mind that "crazy reflection code" is a hack rather than an actual solution since it might break when changing something in the context (EF upgrade, changing the provider etc.). – Alexei - check Codidact Apr 22 '19 at 08:03
  • 1
    @Alexei I started using ``optionsBuilder.UseLoggerFactory(LoggerFactory);`` ``public static readonly LoggerFactory LoggerFactory = new LoggerFactory(new[] { new ConsoleLoggerProvider((_, __) => true, true) });`` because it generates even more beautiful sql, but unfortunely also a lot of spam. – Joelty Apr 26 '19 at 09:12
  • you have to put this extension class in library that get data for you(repositories) – hosam hemaily Jul 27 '19 at 12:20
  • 2
    .Net Core 3.0 along with EF Core 3.0 is now released in GA, and it has breaking changes regarding the method: ToSql. Any idea how to reimplement it for 3.0? More info: https://github.com/aspnet/EntityFrameworkCore/issues/18029 – borisdj Sep 25 '19 at 17:39
  • @borisdj Could you please let me know how you fixed it in 3.0? I went through the github link too. Thank you. – John Oct 01 '19 at 16:33
  • "If you just want to get the Sql, you can probably use `IDbCommandInterceptor` in 3.0 release." – Nikolay Kostov Oct 02 '19 at 09:02
  • is there a way to do it when working with lazy loading?( ef core 2.1) – AJ222 Oct 04 '19 at 12:30
  • 1
    @John, find the link in the comment with: 'the solution for ToSql'. https://github.com/borisdj/EFCore.BulkExtensions/issues/231 – borisdj Oct 06 '19 at 23:31
  • How to make it work for Scalar queries - e.g. dbContext.Entities.Count() ? – Philipp Munin Oct 14 '19 at 19:58
54

For anyone just trying to diagnose a one-off misfiring EF Core query or the like and not wanting to change their code, there are a couple of options:

Use SQL Server Management Studio (SSMS) SQL Profiler

If you've got SQL Server Management Studio (SSMS) installed you can just fire up the SQL Profiler from the Tools menu in SSMS:

SQL Profiler option in the Tools menu in SQL Server Management Studio (SSMS)

And then start a new trace running in SQL Profiler once it opens.

You'll then be able to see the incoming SQL request from EF, they are generally pretty well formed and easy to read.

Check the Output Window in Visual Studio

In my copy of VS2019, using EF2.2 I can change the output window to show the output from the Web Server (select the name of your app and web server in the "Show output from" combo at the top of the Output pane) and the outgoing SQL is also shown in there. I've checked my code and as far as I can see I haven't done anything to enable that, so I think it must do this by default:

enter image description here

If you want to see the parameters sent to SQL server in the queries you can switch that on when setting up the DBContext with the EnableSensitiveDataLogging method, e.g.

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

@Tich -- Lil3p mentions in the comments that they also needed to use a switch to turn on SQL Debugging in the Debug tab of the project's Properties page (which sets "sqlDebugging": true in LaunchSettings.json). I checked and I haven't got that switched on for any of my projects, but that may be worth experimenting with too if the above isn't working for you.

tomRedox
  • 28,092
  • 24
  • 117
  • 154
  • 4
    is not an option for Azure Sql – Emil Aug 17 '19 at 17:09
  • @batmaci I've added another method that might work for Azure – tomRedox Aug 17 '19 at 17:18
  • I do get the output from EF Core, but it doesn't show me the variables it uses for @__p_0, etc. – DaleyKD Mar 20 '20 at 16:08
  • @DaleyKD if memory serves me right that's a security issue - I think MVC hides the parameters by default because they could include sensitive data. I think one of the debugging options for MVC will cause the parameters to be shown, but I can't remember which one. Looking at my code I have `app.UseDeveloperExceptionPage()` in Startup.Configure and `services.AddServerSideBlazor() .AddCircuitOptions(options => { options.DetailedErrors = true; });` in Startup.ConfigureServices. One of those might get the parameters showing. – tomRedox Mar 20 '20 at 20:00
  • @tomRedox thanks, one small detail though to get the SQL to show in the output window you have to enable SQL debugging in the project properties under the debugging tab by default this is not enabled. – Tich -- Lil3p Jun 04 '20 at 01:21
  • @Tich--Lil3p, many thanks for the info. I've just had a look and weirdly I don't have that switched on in any of my projects. I've added some notes about it in my answer. – tomRedox Jun 04 '20 at 08:46
  • 1
    This link helped me -> https://thecodebuzz.com/adding-logging-in-entity-framework-core/. – Yuri Cardoso Jun 08 '20 at 03:15
11

For the EF Core 3.1 solution above you might encounter an System.InvalidCastException (for details, see below) when using queries like myQueryable.Where(x => ids.Contains(x.Id)). (Produces sql statements of the form SELECT ... FROM ... WHERE ID IN (...))

System.InvalidCastException
  Message=Unable to cast object of type 'Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlParameterExpression' to type 'Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlConstantExpression'.
  Source=Microsoft.EntityFrameworkCore.Relational
  StackTrace:
   at Microsoft.EntityFrameworkCore.Query.QuerySqlGenerator.VisitIn(InExpression inExpression) in /_/src/EFCore.Relational/Query/QuerySqlGenerator.cs:line 577
   at Microsoft.EntityFrameworkCore.Query.SqlExpressionVisitor.VisitExtension(Expression extensionExpression) in /_/src/EFCore.Relational/Query/SqlExpressionVisitor.cs:line 37
   at System.Linq.Expressions.Expression.Accept(ExpressionVisitor visitor) in /_/src/System.Linq.Expressions/src/System/Linq/Expressions/Expression.cs:line 164
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) in /_/src/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs:line 34

In order to fix this, adjust the code to something like that:

EF Core 3.1

using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Query;

public static string ToSql<TEntity>(this IQueryable<TEntity> query) where TEntity : class
{
    using var enumerator = query.Provider.Execute<IEnumerable<TEntity>>(query.Expression).GetEnumerator();
    var relationalCommandCache = enumerator.Private("_relationalCommandCache");
    var selectExpression = relationalCommandCache.Private<SelectExpression>("_selectExpression");
    var factory = relationalCommandCache.Private<IQuerySqlGeneratorFactory>("_querySqlGeneratorFactory");
    var relationalQueryContext = enumerator.Private<RelationalQueryContext>("_relationalQueryContext");
    var parameterValueBasedSelectExpressionOptimizer = relationalCommandCache.Private<ParameterValueBasedSelectExpressionOptimizer>("_parameterValueBasedSelectExpressionOptimizer");

    (selectExpression, _) = parameterValueBasedSelectExpressionOptimizer.Optimize(selectExpression, relationalQueryContext.ParameterValues);

    var sqlGenerator = factory.Create();
    var command = sqlGenerator.GetCommand(selectExpression);

    string sql = command.CommandText;
    return sql;
}

private static object Private(this object obj, string privateField) => obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);
private static T Private<T>(this object obj, string privateField) => (T)obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);
5

My take based on @nikolay-kostov answer.

The difference is that I get the SQL command with parameters extracted instead of hard coded which is more in line with how EF Core send commands to the database. Also, if you want to edit and send the command to the database, it is a better practice to use parameters.

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

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

        private static readonly FieldInfo QueryModelGeneratorField = QueryCompilerTypeInfo.DeclaredFields.First(x => x.Name == "_queryModelGenerator");
        private static readonly FieldInfo queryContextFactoryField = QueryCompilerTypeInfo.DeclaredFields.First(x => x.Name == "_queryContextFactory");
        private static readonly FieldInfo loggerField = QueryCompilerTypeInfo.DeclaredFields.First(x => x.Name == "_logger");
        private static readonly FieldInfo DataBaseField = QueryCompilerTypeInfo.DeclaredFields.Single(x => x.Name == "_database");

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

        public static (string sql, IReadOnlyDictionary<string, object> parameters) ToSql<TEntity>(IQueryable<TEntity> query) where TEntity : class
        {
            var queryCompiler = (QueryCompiler)QueryCompilerField.GetValue(query.Provider);
            var queryContextFactory = (IQueryContextFactory)queryContextFactoryField.GetValue(queryCompiler);
            var logger = (Microsoft.EntityFrameworkCore.Diagnostics.IDiagnosticsLogger<DbLoggerCategory.Query>)loggerField.GetValue(queryCompiler);
            var queryContext = queryContextFactory.Create();
            var modelGenerator = (QueryModelGenerator)QueryModelGeneratorField.GetValue(queryCompiler);
            var newQueryExpression = modelGenerator.ExtractParameters(logger, query.Expression, queryContext);
            var queryModel = modelGenerator.ParseQuery(newQueryExpression);
            var database = (IDatabase)DataBaseField.GetValue(queryCompiler);
            var databaseDependencies = (DatabaseDependencies)DatabaseDependenciesField.GetValue(database);
            var queryCompilationContext = databaseDependencies.QueryCompilationContextFactory.Create(false);
            var modelVisitor = (RelationalQueryModelVisitor)queryCompilationContext.CreateQueryModelVisitor();

            modelVisitor.CreateQueryExecutor<TEntity>(queryModel);
            var command = modelVisitor.Queries.First().CreateDefaultQuerySqlGenerator()
                .GenerateSql(queryContext.ParameterValues);

            return (command.CommandText, queryContext.ParameterValues);
        }
    }

Andrija
  • 14,037
  • 18
  • 60
  • 87
Yepeekai
  • 2,545
  • 29
  • 22
3

For EF Core 3.1 with variables, I have the following (based on some GitHub comments from halllo) that was linked above in the comment from @Thom Kiesewetter et al.

/// <summary>
/// SQL Extension methods to get the SQL and check correctness
/// Class can be removed with EF Core 5 (https://github.com/dotnet/efcore/issues/6482#issuecomment-587605366) (although maybe variable substitution might still be necessary if we want them inline)
/// </summary>
public static class SqlExtensions
{
    private static object Private(this object obj, string privateField) => obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);
    private static T Private<T>(this object obj, string privateField) => (T)obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);

    /// <summary>
    /// Gets a SQL statement from an IQueryable
    /// </summary>
    /// <param name="query">The query to get the SQL statement for</param>
    /// <returns>Formatted SQL statement as a string</returns>
    public static string ToQueryString<TEntity>(this IQueryable<TEntity> query) where TEntity : class
    {
        using var enumerator = query.Provider.Execute<IEnumerable<TEntity>>(query.Expression).GetEnumerator();
        var relationalCommandCache = enumerator.Private("_relationalCommandCache");
        var selectExpression = relationalCommandCache.Private<SelectExpression>("_selectExpression");
        var factory = relationalCommandCache.Private<IQuerySqlGeneratorFactory>("_querySqlGeneratorFactory");
        var relationalQueryContext = enumerator.Private<RelationalQueryContext>("_relationalQueryContext");

        var sqlGenerator = factory.Create();
        var command = sqlGenerator.GetCommand(selectExpression);
        var parametersDict = relationalQueryContext.ParameterValues;

        return SubstituteVariables(command.CommandText, parametersDict);
    }

    private static string SubstituteVariables(string commandText, IReadOnlyDictionary<string, object> parametersDictionary)
    {
        var sql = commandText;
        foreach (var (key, value) in parametersDictionary)
        {
            var placeHolder = "@" + key;
            var actualValue = GetActualValue(value);
            sql = sql.Replace(placeHolder, actualValue);
        }

        return sql;
    }

    private static string GetActualValue(object value)
    {
        var type = value.GetType();

        if (type.IsNumeric())
            return value.ToString();

        if (type == typeof(DateTime) || type == typeof(DateTimeOffset))
        {
            switch (type.Name)
            {
                case nameof(DateTime):
                    return $"'{(DateTime)value:u}'";

                case nameof(DateTimeOffset):
                    return $"'{(DateTimeOffset)value:u}'";
            }
        }

        return $"'{value}'";
    }

    private static bool IsNullable(this Type type)
    {
        return
            type != null &&
            type.IsGenericType &&
            type.GetGenericTypeDefinition() == typeof(Nullable<>);
    }

    private static bool IsNumeric(this Type type)
    {
        if (IsNullable(type))
            type = Nullable.GetUnderlyingType(type);

        if (type == null || type.IsEnum)
            return false;

        return Type.GetTypeCode(type) switch
        {
            TypeCode.Byte => true,
            TypeCode.Decimal => true,
            TypeCode.Double => true,
            TypeCode.Int16 => true,
            TypeCode.Int32 => true,
            TypeCode.Int64 => true,
            TypeCode.SByte => true,
            TypeCode.Single => true,
            TypeCode.UInt16 => true,
            TypeCode.UInt32 => true,
            TypeCode.UInt64 => true,
            _ => false
        };
    }
}

This doesn't substitute all types perhaps but most are covered. Feel free to extend.

Rubenisme
  • 787
  • 1
  • 8
  • 15
3

Entity Framework Core 3.x

You can get it through logging.

Create the factory:

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

Tell the DbContext which factory to use:

optionsBuilder.UseLoggerFactory(_loggerFactory);

From this post

You can get more information if you want to implement 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
  • 6,770
  • 5
  • 51
  • 103
2

Adding this answer because all the suggestions here have broken with new EF Core releases (ie, all the answers here are broken on EF Core 2.2). Here's code that worked for me on the first try, and seems to be .NET Core version agnostic (so far): https://blogs.msdn.microsoft.com/dbrowne/2017/09/22/simple-logging-for-ef-core/

Gabriel Magana
  • 4,338
  • 24
  • 23
1

For EF Core 3 and above, EFCore.BulkExtensions has a ToParametrizedSql method. My only gripe is that it returns the parameters as Microsoft.Data.SqlClient, so sometimes I have to convert them to System.Data.SqlClient if that is my connection type.

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

EFCore.BulkExtensions.IQueryableExtensions.ToParametrizedSql
Eric
  • 2,120
  • 1
  • 17
  • 34
0

As a public service:

    var someQuery = (
        from projects in _context.projects
        join issues in _context.issues on projects.Id equals issues.ProjectId into tmpMapp
        from issues in tmpMapp.DefaultIfEmpty()
        select issues
    ) //.ToList()
    ;

    // string sql = someQuery.ToString();
    // string sql = Microsoft.EntityFrameworkCore.IQueryableExtensions.ToSql(someQuery);
    // string sql = Microsoft.EntityFrameworkCore.IQueryableExtensions1.ToSql(someQuery);
    // using Microsoft.EntityFrameworkCore;
    string sql = someQuery.ToSql();
    System.Console.WriteLine(sql);

And then these extension methods (IQueryableExtensions1 for .NET Core 1.0, IQueryableExtensions for .NET Core 2.0) :

    using System;
    using System.Linq;
    using System.Reflection;
    using Microsoft.EntityFrameworkCore.Internal;
    using Microsoft.EntityFrameworkCore.Query;
    using Microsoft.EntityFrameworkCore.Query.Internal;
    using Microsoft.EntityFrameworkCore.Storage;
    using Remotion.Linq.Parsing.Structure;


    namespace Microsoft.EntityFrameworkCore
    {

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

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

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

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

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

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

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

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

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

                return sql;
            }
        }



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

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

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

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

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

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


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

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

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

                return sql;
            }


        }


    }
Andrija
  • 14,037
  • 18
  • 60
  • 87
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
  • With latest EF Core 2.1.1 , this does not work anymore. Error at private static readonly PropertyInfo NodeTypeProviderField = QueryCompilerTypeInfo.DeclaredProperties.Single(x => x.Name == "NodeTypeProvider"); – Stef Heyenrath Jul 21 '18 at 09:45
  • @Stef Heyenrath: I think my answer clearly states .NET Core 1.0 & 2.0 and, not 2.1 or 2.2. The others have already given the code for 2.2, 3.0 and 3.1. .NET Core 2.1 was not released at the time I wrote this answer. It's perfectly valid for .NET Core 2.0 and 1.0 – Stefan Steiger Feb 28 '20 at 10:17