รับโค้ด SQL จาก Entity Framework Core IQueryable <T>


93

ฉันใช้ Entity Framework Core และฉันต้องการดูว่ามีการสร้างโค้ด SQL ใด ใน Entity Framework เวอร์ชันก่อนหน้าฉันสามารถใช้สิ่งต่อไปนี้:

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

แบบสอบถามคือวัตถุ IQueryable ที่ไหน ... แต่ ToTraceString ไม่มีใน EF Core

ฉันจะทำสิ่งที่คล้ายกันใน EF Core ได้อย่างไร


3
อาจซ้ำกันได้ของHow to log queries โดยใช้ Entity Framework 7?
Thomas Boby

คุณสามารถลองสิ่งนี้: rion.io/2016/10/19/… .
mikebridge

คำตอบ:


83

EF core 5 / Net 5

query.ToQueryString()ดูว่ามีอะไรใหม่ใน EF Core 5.0

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

สำหรับ net core framework ที่เก่ากว่าสามารถใช้ Extension ได้

แกน 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;
        }

ดู Gist จาก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);

นอกจากนี้ยังติดตามปัญหานี้โดยทีมEF net coreและมีกำหนดวางจำหน่ายครั้งต่อไป


1
คุณช่วยยกตัวอย่างIQueryableได้IQueryable<T>ไหมว่าสิ่งนี้ควรเขียนอย่างไรกับ an และ not ?
byrnedo

ฉันคิดว่าคุณมักจะมีไฟล์IQueryable<T>. ดูwidgetตัวอย่างด้านบน คุณมีตัวอย่างที่มีเพียง IQueryable
Thom Kiesewetter

ฉันใช้github.com/StefH/System.Linq.Dynamic.Coreซึ่งให้คุณIQueryableเพียง
byrnedo

ในกรอบการค้นหาของคุณจะขึ้นอยู่กับประเภท enitity <T> ToSql ต้องการ enityType เนื่องจากจำเป็นต้องทราบเขตข้อมูลและชื่อตารางเพื่อสร้างคำสั่ง sql ไม่สามารถทำได้หากไม่มีข้อมูลนี้
Thom Kiesewetter

1
var relationalCommandCache = enumerator.Private ("_ relationalCommandCache"); คืนค่า null
Khurram Ali

81

คำตอบนี้สำหรับ EF Core 2.1 สำหรับ EF Core 3.0 และ 3.1 ดูคำตอบของ @Thom Kiesewetter

สำหรับ EF Core 5 จะมีวิธีการในตัวที่ToQueryString()ใช้IQueryable<>

เนื่องจาก EF 7 ถูกเปลี่ยนชื่อเป็น Entity Framework Core ฉันจะสรุปตัวเลือกสำหรับ EF Core ให้คุณ

มี 3 วิธีในการบันทึกคำสั่ง SQL จากIQueryable<>:

  • การใช้Built-in หรือเข้าสู่ระบบที่กำหนดเอง การบันทึกคิวรีที่ดำเนินการโดยใช้คนตัดไม้ที่คุณเลือกหรือ Logger ในตัวใน. NET Core ตามที่กล่าวไว้ในบทช่วยสอนนี้
  • ใช้Profiler การใช้ SQL Profiler เช่นMiniProfilerเพื่อตรวจสอบการดำเนินการสืบค้น
  • ใช้บ้าสะท้อนรหัส คุณสามารถใช้โค้ดการสะท้อนแบบกำหนดเองบางอย่างที่คล้ายคลึงกับแนวทางเก่าเพื่อดำเนินการตามแนวคิดพื้นฐานเดียวกัน

นี่คือรหัสสะท้อนที่บ้าคลั่ง (วิธีการขยาย):

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

หลังจากเพิ่มวิธีการขยายนี้ลงในโค้ดของคุณแล้วคุณสามารถใช้วิธีการดังต่อไปนี้:

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

ผู้อ้างอิง: http://rion.io/2016/10/19/accessing-entity-framework-core-queries-behind-the-scenes-in-asp-net-core/และhttps://gist.github.com / rionmonster / 2c59f449e67edf8cd6164e9fe66c545a


1
ขอบคุณสำหรับคอมเม้น ฉันอัปเดตรหัสดังนั้นควรใช้กับ 2.1 ได้แล้ว
Nikolay Kostov

1
@SteffenMangold มันมีไว้เพื่อจุดประสงค์ในการดีบั๊ก :) ไม่ได้ตั้งใจให้เร็ว
Nikolay Kostov

1
@RicardoPeres: ไม่พวกเขาอ้างอิงrion.io/2016/10/19/…ซึ่งให้เครดิตโพสต์ของคุณ
Martijn Pieters

1
@ Alexei ฉันเริ่มใช้optionsBuilder.UseLoggerFactory(LoggerFactory); public static readonly LoggerFactory LoggerFactory = new LoggerFactory(new[] { new ConsoleLoggerProvider((_, __) => true, true) });เพราะมันสร้าง sql ที่สวยงามยิ่งขึ้น แต่โชคร้ายก็มีสแปมมากมาย
Joelty

2
.Net Core 3.0 พร้อมกับ EF Core 3.0 เปิดตัวใน GA แล้วและมีการเปลี่ยนแปลงที่ผิดปกติเกี่ยวกับวิธีการ: ToSql มีความคิดอย่างไรในการติดตั้งใหม่สำหรับ 3.0? ข้อมูลเพิ่มเติม: github.com/aspnet/EntityFrameworkCore/issues/18029
borisdj

40

สำหรับใครก็ตามที่พยายามวินิจฉัยการค้นหา EF Core ที่ผิดพลาดเพียงครั้งเดียวหรือสิ่งที่คล้ายกันและไม่ต้องการเปลี่ยนรหัสมีสองตัวเลือก:

ใช้ SQL Server Management Studio (SSMS) SQL Profiler

หากคุณได้ติดตั้ง SQL Server Management Studio (SSMS) คุณสามารถเปิดใช้งาน SQL Profilerจากเมนู Tools ใน SSMS:

ตัวเลือก SQL Profiler ในเมนู Tools ใน SQL Server Management Studio (SSMS)

จากนั้นเริ่มการติดตามใหม่ที่ทำงานใน SQL Profiler เมื่อเปิดขึ้น

จากนั้นคุณจะสามารถเห็นคำขอ SQL ที่เข้ามาจาก EF ซึ่งโดยทั่วไปแล้วจะมีรูปแบบที่ค่อนข้างดีและอ่านง่าย

ตรวจสอบหน้าต่างเอาต์พุตใน Visual Studio

ในสำเนา VS2019 ของฉันโดยใช้ EF2.2 ฉันสามารถเปลี่ยนหน้าต่างผลลัพธ์เพื่อแสดงผลลัพธ์จากเว็บเซิร์ฟเวอร์ (เลือกชื่อแอปและเว็บเซิร์ฟเวอร์ของคุณในคำสั่งผสม "แสดงผลลัพธ์จาก" ที่ด้านบนของบานหน้าต่างผลลัพธ์) และ SQL ขาออกจะแสดงอยู่ในนั้นด้วย ฉันได้ตรวจสอบรหัสของฉันแล้วและเท่าที่ฉันเห็นฉันยังไม่ได้ทำอะไรเพื่อเปิดใช้งานดังนั้นฉันคิดว่ามันจะต้องทำสิ่งนี้โดยค่าเริ่มต้น:

ป้อนคำอธิบายภาพที่นี่

หากคุณต้องการดูพารามิเตอร์ที่ส่งไปยังเซิร์ฟเวอร์ SQL ในแบบสอบถามคุณสามารถเปิดใช้เมื่อตั้งค่า DBContext ด้วยEnableSensitiveDataLoggingวิธีการเช่น

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

@Tich - Lil3pกล่าวถึงในความคิดเห็นว่าพวกเขาจำเป็นต้องใช้สวิตช์เพื่อเปิดการดีบัก SQL ในแท็บ Debugของหน้าคุณสมบัติของโปรเจ็กต์ (ซึ่งตั้งค่า"sqlDebugging": trueใน LaunchSettings.json) ฉันตรวจสอบแล้วและยังไม่ได้เปิดใช้งานสำหรับโครงการใด ๆ ของฉัน แต่อาจคุ้มค่าที่จะทดลองด้วยหากข้างต้นไม่ได้ผลสำหรับคุณ


3
ไม่ใช่ตัวเลือกสำหรับ Azure Sql
Emil

@batmaci ฉันได้เพิ่มวิธีการอื่นที่อาจใช้ได้กับ Azure
tomRedox

ฉันได้ผลลัพธ์จาก EF Core แต่มันไม่แสดงตัวแปรที่ใช้สำหรับ @__ p_0 ฯลฯ
DaleyKD

@DaleyKD ถ้าหน่วยความจำให้บริการฉันถูกต้องนั่นเป็นปัญหาด้านความปลอดภัย - ฉันคิดว่า MVC จะซ่อนพารามิเตอร์ตามค่าเริ่มต้นเนื่องจากอาจมีข้อมูลที่ละเอียดอ่อน ฉันคิดว่าหนึ่งในตัวเลือกการดีบักสำหรับ MVC จะทำให้พารามิเตอร์แสดงขึ้น แต่ฉันจำไม่ได้ว่าอันไหน ดูรหัสของฉันที่ฉันมีapp.UseDeveloperExceptionPage()ใน Startup กำหนดค่าและ services.AddServerSideBlazor() .AddCircuitOptions(options => { options.DetailedErrors = true; });ใน Startup.ConfigureServices หนึ่งในนั้นอาจได้รับพารามิเตอร์ที่แสดง
tomRedox

1
การเชื่อมโยงนี้ช่วยให้ฉัน -> thecodebuzz.com/adding-logging-in-entity-framework-core
Yuri Cardoso

3

การใช้เวลาของฉันขึ้นอยู่กับคำตอบของ @ nikolay-kostov

ความแตกต่างคือฉันได้รับคำสั่ง SQL พร้อมพารามิเตอร์ที่แยกออกมาแทนที่จะเป็นฮาร์ดโค้ดซึ่งสอดคล้องกับวิธีที่ EF Core ส่งคำสั่งไปยังฐานข้อมูล นอกจากนี้หากคุณต้องการแก้ไขและส่งคำสั่งไปยังฐานข้อมูลคุณควรใช้พารามิเตอร์

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


2

การเพิ่มคำตอบนี้เนื่องจากคำแนะนำทั้งหมดในที่นี้ได้หักล้างกับ EF Core รุ่นใหม่ (กล่าวคือคำตอบทั้งหมดที่นี่ใช้กับ EF Core 2.2 ไม่ได้) นี่คือรหัสที่ใช้ได้กับฉันในการลองครั้งแรกและดูเหมือนว่าจะเป็น. NET Core เวอร์ชันที่ไม่เชื่อเรื่องพระเจ้า (จนถึงปัจจุบัน): https://blogs.msdn.microsoft.com/dbrowne/2017/09/22/simple-logging-for -ef-core /


2

Entity Framework Core 3.x

คุณสามารถรับได้ผ่านการบันทึก

สร้างโรงงาน:

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

บอกDbContextโรงงานที่จะใช้:

optionsBuilder.UseLoggerFactory(_loggerFactory);

จากกระทู้นี้

คุณสามารถรับข้อมูลเพิ่มเติมหากคุณต้องการใช้ 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
}

1

สำหรับ EF Core 3.1 ที่มีตัวแปรฉันมีสิ่งต่อไปนี้ (ตามความคิดเห็น GitHubบางส่วนจาก halllo ) ที่เชื่อมโยงด้านบนในความคิดเห็นจาก @ 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
        };
    }
}

สิ่งนี้ไม่ได้ทดแทนทุกประเภท แต่ส่วนใหญ่จะครอบคลุม อย่าลังเลที่จะขยาย


0

ในฐานะบริการสาธารณะ:

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

จากนั้นวิธีการขยายเหล่านี้ (IQueryableExtensions1 สำหรับ. NET Core 1.0, IQueryableExtensions สำหรับ. 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;
            }


        }


    }

ด้วย EF Core 2.1.1 ล่าสุดสิ่งนี้จะใช้ไม่ได้อีกต่อไป ข้อผิดพลาดที่ Private static readonly PropertyInfo NodeTypeProviderField = QueryCompilerTypeInfo.DeclaredProperties.Single (x => x.Name == "NodeTypeProvider");
Stef Heyenrath

@Stef Heyenrath: ฉันคิดว่าคำตอบของฉันระบุอย่างชัดเจนว่า. NET Core 1.0 & 2.0 และไม่ใช่ 2.1 หรือ 2.2 คนอื่น ๆ ได้ให้รหัสสำหรับ 2.2, 3.0 และ 3.1 ไปแล้ว .NET Core 2.1 ไม่ได้เปิดตัวในเวลาที่ฉันเขียนคำตอบนี้ ใช้ได้อย่างสมบูรณ์แบบสำหรับ. NET Core 2.0 และ 1.0
Stefan Steiger

0

สำหรับ EF Core 3 ขึ้นไป EFCore.BulkExtensions มีเมธอด ToParametrizedSql สิ่งเดียวที่ฉันจับได้คือมันส่งคืนพารามิเตอร์เป็น Microsoft.Data.SqlClient ดังนั้นบางครั้งฉันต้องแปลงเป็น System.Data.SqlClient ถ้านั่นคือประเภทการเชื่อมต่อของฉัน

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

EFCore.BulkExtensions.IQueryableExtensions.ToParametrizedSql
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.