Entity Framework Core IQueryable <T>에서 SQL 코드 가져 오기


92

Entity Framework Core를 사용하고 있으며 어떤 SQL 코드가 생성되고 있는지 확인해야합니다. 이전 버전의 Entity Framework에서는 다음을 사용할 수있었습니다.

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

쿼리가 IQueryable 개체 인 경우 ... 그러나 ToTraceString은 EF Core에서 사용할 수 없습니다.

EF Core에서 비슷한 작업을 수행하려면 어떻게해야합니까?



시도해 볼 수 있습니다 : rion.io/2016/10/19/… .
mikebridge

답변:


82

EF 코어 5 / Net 5

query.ToQueryString()EF Core 5.0의 새로운 기능보기

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

이전 넷 코어 프레임 워크의 경우 확장을 사용할 수 있습니다.

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

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>?
byrnedo

나는 당신이 항상 IQueryable<T>. widget위의 예를 참조하십시오 . IQueryable 만있는 예제가 있습니까?
Thom Kiesewetter

저는 github.com/StefH/System.Linq.Dynamic.Core를 사용 하고 있습니다 . 이것은 당신에게 IQueryable
byrnedo

프레임 워크에서 쿼리는 엔터티 유형 <T>를 기반으로합니다. ToSql은 SQL 문을 생성하기 위해 필드와 테이블 이름을 알아야하므로 enityType이 필요합니다. 이 정보 없이는 할 수 없습니다.
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에 대한 옵션을 요약하겠습니다.

다음에서 SQL 문을 로깅하는 세 가지 방법이 있습니다 IQueryable<>.

  • 기본 제공 또는 사용자 지정 로깅 사용 . 이 자습서 에서 언급 한대로 선택한 로거 또는 .NET Core의 기본 제공 로거를 사용하여 실행 쿼리를 로깅합니다 .
  • 사용하여 프로파일을 . MiniProfiler 와 같은 SQL 프로파일 러를 사용 하여 실행중인 쿼리를 모니터링합니다.
  • 사용 미친 반사 코드 . 동일한 기본 개념을 수행하기 위해 이전 접근 방식과 유사한 사용자 지정 리플렉션 코드를 구현할 수 있습니다.

다음은 미친 반사 코드 (확장 방법)입니다.

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
이제 EF Core 3.0과 함께 .Net Core 3.0이 GA로 릴리스되었으며 ToSql이라는 메서드와 관련된 주요 변경 사항이 있습니다. 3.0을 위해 다시 구현하는 방법을 아십니까? 더 많은 정보 : github.com/aspnet/EntityFrameworkCore/issues/18029
borisdj

40

일회성 잘못된 EF Core 쿼리 등을 진단하려고하고 코드를 변경하고 싶지 않은 사람을 위해 다음과 같은 몇 가지 옵션이 있습니다.

SSMS (SQL Server Management Studio) SQL 프로파일 러 사용

SSMS (SQL Server Management Studio)가 설치되어 있는 경우 SSMS의 도구 메뉴에서 SQL 프로필러 를 실행할 수 있습니다 .

SSMS (SQL Server Management Studio)의 도구 메뉴에있는 SQL 프로파일 러 옵션

그런 다음 SQL 프로필러가 열리면 실행중인 새 추적을 시작합니다.

그러면 EF에서 들어오는 SQL 요청을 볼 수 있습니다. 일반적으로 형식이 매우 좋고 읽기 쉽습니다.

Visual Studio에서 출력 창 확인

내 VS2019 사본에서 EF2.2를 사용하여 웹 서버의 출력을 표시하도록 출력 창을 변경할 수 있습니다 (출력 창 상단의 "다음에서 출력 표시"콤보에서 앱 및 웹 서버의 이름 선택). 나가는 SQL도 거기에 표시됩니다. 나는 내 코드를 확인했고 내가 그것을 활성화하기 위해 아무것도하지 않은 것을 볼 수 있으므로 기본적으로 이것을해야한다고 생각합니다.

여기에 이미지 설명 입력

쿼리에서 SQL 서버로 전송 된 매개 변수를 보려면 EnableSensitiveDataLogging메소드를 사용 하여 DBContext를 설정할 때이를 켤 수 있습니다. 예 :

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

@Tich-Lil3p 는 프로젝트 속성 페이지 ( LaunchSettings.json에서 설정)의 디버그 탭에서 스위치 를 사용 하여 SQL 디버깅을 켜야 한다는 의견을 언급했습니다 "sqlDebugging": true. 내가 확인한 결과 내 프로젝트에 대해 해당 기능이 켜져 있지 않지만 위의 내용이 작동하지 않으면 실험 해 볼 가치가 있습니다.


3
푸른 SQL에 대한 옵션을 선택하지 않습니다
에밀

@batmaci 나는 다른 방법을 추가 한 그 푸른에 대한 힘의 일
tomRedox

EF Core에서 출력을 얻었지만 __p_0 등에 사용하는 변수가 표시되지 않습니다.
DaleyKD

@DaleyKD 메모리가 나에게 올바른 서비스를 제공한다면 보안 문제입니다. MVC는 민감한 데이터를 포함 할 수 있기 때문에 기본적으로 매개 변수를 숨 깁니다. MVC에 대한 디버깅 옵션 중 하나가 매개 변수를 표시 할 것이라고 생각하지만 어느 것이인지 기억할 수 없습니다. 내 코드를 보면 app.UseDeveloperExceptionPage()Startup.Configure 및 services.AddServerSideBlazor() .AddCircuitOptions(options => { options.DetailedErrors = true; });Startup.ConfigureServices에 있습니다. 그중 하나가 매개 변수를 표시 할 수 있습니다.
tomRedox

1
이 링크는 나를 도왔습니다-> thecodebuzz.com/adding-logging-in-entity-framework-core .
Yuri Cardoso

3

@ nikolay-kostov 답변을 기반으로 한 내 의견.

차이점은 EF Core가 데이터베이스에 명령을 보내는 방법과 더 일치하는 하드 코딩 된 대신 추출 된 매개 변수가있는 SQL 명령을 얻는다는 것입니다. 또한 명령을 편집하여 데이터베이스로 보내려면 매개 변수를 사용하는 것이 좋습니다.

    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

로깅을 통해 얻을 수 있습니다.

팩토리 만들기 :

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의 경우 @ Thom Kiesewetter 등 의 주석에서 위에 링크 된 다음 ( halllo의 일부 GitHub 주석을 기반으로 )이 있습니다 .

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

그리고 다음 확장 메서드 (.NET Core 1.0의 경우 IQueryableExtensions1, .NET Core 2.0의 경우 IQueryableExtensions) :

    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에서는 더 이상 작동하지 않습니다. 개인 정적 읽기 전용 오류 PropertyInfo NodeTypeProviderField = QueryCompilerTypeInfo.DeclaredProperties.Single (x => x.Name == "NodeTypeProvider");
Stef Heyenrath

@Stef Heyenrath : 내 대답에 2.1 또는 2.2가 아닌 .NET Core 1.0 및 2.0이 명시되어 있다고 생각합니다. 다른 사람들은 이미 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.