Nhận mã SQL từ một Entity Framework Core IQueryable <T>


94

Tôi đang sử dụng Entity Framework Core và tôi cần xem mã SQL nào đang được tạo. Trong các phiên bản trước của Entity Framework, tôi có thể sử dụng như sau:

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

Trong đó truy vấn là một đối tượng IQueryable ... Nhưng ToTraceString không có sẵn trong EF Core.

Làm cách nào tôi có thể làm điều gì đó tương tự trong EF Core?



Bạn có thể thử cái này: rion.io/2016/10/19/… .
mikebridge

Câu trả lời:


84

EF lõi 5 / Net 5

query.ToQueryString()Xem có gì mới trong EF Core 5.0

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

Đối với các khung lõi mạng cũ hơn, một Phần mở rộng có thể được sử dụng.

Cốt lõi 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;
        }

xem Gist từ 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);

Vấn đề cũng được theo dõi bởi nhóm nòng cốt mạng của EF và được lên lịch cho bản phát hành tiếp theo.


1
Bạn có thể cho một ví dụ về cách điều này nên được viết để làm việc với một IQueryablevà không một IQueryable<T>?
byrnedo

Tôi nghĩ rằng bạn luôn luôn có một IQueryable<T>. Xem widgetví dụ trên. Bạn có một ví dụ chỉ có IQueryable.
Thom Kiesewetter

Tôi đang sử dụng github.com/StefH/System.Linq.Dynamic.Core , cung cấp cho bạn IQueryablechỉ
byrnedo 13/02/19

Trong khuôn khổ của bạn, các truy vấn của bạn dựa trên loại enitity <T>. ToSql cần enityType vì nó cần biết trường và tên bảng để tạo câu lệnh sql. Không thể thực hiện được nếu không có thông tin này.
Thom Kiesewetter 14/02/19

1
var relationalCommandCache = enumerator.Private ("_ relationalCommandCache"); trả về null
Khurram Ali

81

Câu trả lời này dành cho EF Core 2.1. Đối với EF Core 3.0 và 3.1, hãy xem câu trả lời của @Thom Kiesewetter

Đối với EF Core 5, sẽ có phương pháp tích hợp ToQueryString()được sử dụng trênIQueryable<>

Vì EF 7 được đổi tên thành Entity Framework Core nên tôi sẽ tóm tắt cho bạn các tùy chọn cho EF Core.

Có 3 cách tiếp cận để ghi các câu lệnh SQL từ IQueryable<>:

  • Sử dụng ghi nhật ký tùy chỉnh hoặc tích hợp . Ghi nhật ký truy vấn đang thực thi bằng trình ghi mà bạn chọn hoặc Trình ghi tích hợp trong .NET Core như được đề cập trong hướng dẫn này .
  • Sử dụng một Profiler . Sử dụng một SQL Profiler như MiniProfiler để theo dõi truy vấn đang thực thi.
  • Sử dụng mã phản chiếu điên rồ . Bạn có thể triển khai một số mã phản chiếu tùy chỉnh tương tự như cách tiếp cận cũ hơn để thực hiện cùng một khái niệm cơ bản.

Đây là mã phản chiếu điên rồ (phương thức mở rộng):

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

Sau khi thêm phương thức tiện ích mở rộng này vào mã của bạn, bạn có thể sử dụng phương pháp như sau:

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

Giới thiệu: 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
Cảm ơn bạn đã cho ý kiến. Tôi đã cập nhật mã để nó hoạt động với 2.1 ngay bây giờ.
Nikolay Kostov

1
@SteffenMangold nó dành cho mục đích gỡ lỗi :) Nó không nhằm mục đích nhanh.
Nikolay Kostov

1
@RicardoPeres: không, họ tham chiếu rion.io/2016/10/19/… , ghi công cho bài đăng của bạn.
Martijn Pieters

1
@Alexei Tôi bắt đầu sử dụng optionsBuilder.UseLoggerFactory(LoggerFactory); public static readonly LoggerFactory LoggerFactory = new LoggerFactory(new[] { new ConsoleLoggerProvider((_, __) => true, true) });vì nó tạo ra sql thậm chí còn đẹp hơn, nhưng rất tiếc là cũng có rất nhiều thư rác.
Joelty

2
.Net Core 3.0 cùng với EF Core 3.0 hiện đã được phát hành trong GA và nó có những thay đổi đột phá về phương pháp: ToSql. Bất kỳ ý tưởng làm thế nào để thực hiện lại nó cho 3.0? Thông tin thêm: github.com/aspnet/EntityFrameworkCore/issues/18029
borisdj

40

Đối với bất kỳ ai chỉ cố gắng chẩn đoán một truy vấn EF Core sai một lần hoặc tương tự và không muốn thay đổi mã của họ, có một số tùy chọn:

Sử dụng SQL Server Management Studio (SSMS) SQL Profiler

Nếu bạn đã cài đặt SQL Server Management Studio (SSMS), bạn có thể kích hoạt SQL Profiler từ menu Công cụ trong SSMS:

Tùy chọn SQL Profiler trong menu Tools trong SQL Server Management Studio (SSMS)

Và sau đó bắt đầu một dấu vết mới chạy trong SQL Profiler khi nó mở ra.

Sau đó, bạn sẽ có thể thấy yêu cầu SQL đến từ EF, chúng thường được định dạng khá tốt và dễ đọc.

Kiểm tra cửa sổ đầu ra trong Visual Studio

Trong bản sao VS2019 của tôi, sử dụng EF2.2, tôi có thể thay đổi cửa sổ đầu ra để hiển thị đầu ra từ Máy chủ Web (chọn tên của ứng dụng và máy chủ web của bạn trong tổ hợp "Hiển thị đầu ra từ" ở đầu ngăn Đầu ra) và SQL gửi đi cũng được hiển thị trong đó. Tôi đã kiểm tra mã của mình và theo như tôi có thể thấy, tôi chưa làm bất cứ điều gì để kích hoạt điều đó, vì vậy tôi nghĩ rằng nó phải làm điều này theo mặc định:

nhập mô tả hình ảnh ở đây

Nếu bạn muốn xem các tham số được gửi đến máy chủ SQL trong các truy vấn, bạn có thể bật điều đó khi thiết lập DBContext bằng EnableSensitiveDataLoggingphương thức này, ví dụ:

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

@Tich - Lil3p đề cập trong các nhận xét rằng họ cũng cần sử dụng công tắc để bật tính năng Gỡ lỗi SQL trong tab Gỡ lỗi của trang Thuộc tính của dự án (được đặt "sqlDebugging": truetrong LaunchSettings.json). Tôi đã kiểm tra và tôi chưa bật tính năng đó cho bất kỳ dự án nào của mình, nhưng điều đó cũng có thể đáng để thử nghiệm nếu những điều trên không phù hợp với bạn.


3
không phải là một lựa chọn cho Azure Sql
Emil

@batmaci Tôi đã thêm một phương pháp khác mà công việc sức cho Azure
tomRedox

Tôi nhận được đầu ra từ EF Core, nhưng nó không hiển thị cho tôi các biến mà nó sử dụng cho @__ p_0, v.v.
DaleyKD

@DaleyKD nếu bộ nhớ phục vụ tôi đúng thì đó là vấn đề bảo mật - Tôi nghĩ MVC ẩn các tham số theo mặc định vì chúng có thể bao gồm dữ liệu nhạy cảm. Tôi nghĩ rằng một trong các tùy chọn gỡ lỗi cho MVC sẽ khiến các thông số được hiển thị, nhưng tôi không thể nhớ cái nào. Nhìn vào mã của tôi, tôi có app.UseDeveloperExceptionPage()trong Startup.Configure và services.AddServerSideBlazor() .AddCircuitOptions(options => { options.DetailedErrors = true; });trong Startup.ConfigureServices. Một trong số đó có thể hiển thị các thông số.
tomRedox


3

Ý kiến ​​của tôi dựa trên câu trả lời @ nikolay-kostov.

Sự khác biệt là tôi nhận được lệnh SQL với các tham số được trích xuất thay vì được mã hóa cứng, phù hợp hơn với cách EF Core gửi lệnh đến cơ sở dữ liệu. Ngoài ra, nếu bạn muốn chỉnh sửa và gửi lệnh đến cơ sở dữ liệu, cách tốt hơn là sử dụng các tham số.

    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

Entity Framework Core 3.x

Bạn có thể lấy nó thông qua ghi nhật ký.

Tạo nhà máy:

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

Cho biết DbContextnhà máy sẽ sử dụng:

optionsBuilder.UseLoggerFactory(_loggerFactory);

Từ bài đăng này

Bạn có thể lấy thêm thông tin nếu bạn muốn triển khai 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

Đối với EF Core 3.1 với các biến, tôi có thông tin sau (dựa trên một số nhận xét GitHub từ halllo ) được liên kết ở trên trong nhận xét từ @ 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
        };
    }
}

Điều này có lẽ không thay thế tất cả các loại nhưng hầu hết đều được bảo hiểm. Vui lòng mở rộng.


0

Là một dịch vụ công cộng:

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

Và sau đó là các phương thức mở rộng này (IQueryableExtensions1 cho .NET Core 1.0, IQueryableExtensions cho .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;
            }


        }


    }

Với EF Core 2.1.1 mới nhất, điều này không hoạt động nữa. Lỗi tại private static readonly PropertyInfo NodeTypeProviderField = QueryCompilerTypeInfo.DeclaredProperties.Single (x => x.Name == "NodeTypeProvider");
Stef Heyenrath 21/07/18

@Stef Heyenrath: Tôi nghĩ câu trả lời của tôi đã nêu rõ .NET Core 1.0 & 2.0 chứ không phải 2.1 hoặc 2.2. Những người khác đã cung cấp mã cho 2.2, 3.0 và 3.1. .NET Core 2.1 chưa được phát hành vào thời điểm tôi viết câu trả lời này. Nó hoàn toàn hợp lệ cho .NET Core 2.0 và 1.0
Stefan Steiger

0

Đối với EF Core 3 trở lên, EFCore.BulkExtensions có phương thức ToParametrizedSql. Mối quan tâm duy nhất của tôi là nó trả về các tham số dưới dạng Microsoft.Data.SqlClient, vì vậy đôi khi tôi phải chuyển đổi chúng thành System.Data.SqlClient nếu đó là loại kết nối của tôi.

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

EFCore.BulkExtensions.IQueryableExtensions.ToParametrizedSql
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.