Entity Framework Core를 사용하고 있으며 어떤 SQL 코드가 생성되고 있는지 확인해야합니다. 이전 버전의 Entity Framework에서는 다음을 사용할 수있었습니다.
string sql = ((System.Data.Objects.ObjectQuery)query).ToTraceString();
쿼리가 IQueryable 개체 인 경우 … 그러나 ToTraceString은 EF Core에서 사용할 수 없습니다.
EF Core에서 비슷한 작업을 수행하려면 어떻게해야합니까?
답변
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 팀 에서도 추적 하며 다음 릴리스로 예정되어 있습니다.
답변
이 답변은 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
답변
일회성 잘못된 EF Core 쿼리 등을 진단하려고하고 코드를 변경하고 싶지 않은 사람을 위해 다음과 같은 몇 가지 옵션이 있습니다.
SSMS (SQL Server Management Studio) SQL 프로파일 러 사용
SSMS (SQL Server Management Studio)가 설치되어 있는 경우 SSMS의 도구 메뉴에서 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
. 내가 확인한 결과 내 프로젝트에 대해 해당 기능이 켜져 있지 않지만 위의 내용이 작동하지 않으면 실험 해 볼 가치가 있습니다.
답변
@ 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);
}
}
답변
여기에있는 모든 제안이 새로운 EF Core 릴리스에서 깨 졌기 때문에이 답변을 추가합니다 (즉, 여기에있는 모든 답변이 EF Core 2.2에서 깨짐). 다음은 첫 번째 시도에서 저에게 효과가 있었고 .NET Core 버전에 구애받지 않는 것 같습니다 (지금까지) : https://blogs.msdn.microsoft.com/dbrowne/2017/09/22/simple-logging-for -ef-core /
답변
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
}
답변
변수가있는 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
};
}
}
이것은 아마도 모든 유형을 대체하지는 않지만 대부분이 다룹니다. 자유롭게 연장하십시오.