我正在使用Entity Framework Core,需要查看正在生成的SQL代码。在早期版本的Entity Framework中,我可以使用以下内容:
string sql = ((System.Data.Objects.ObjectQuery)query).ToTraceString();
在哪里查询是一个IQueryable对象...但是ToTraceString在EF Core中不可用。
如何在EF Core中做类似的事情?
我正在使用Entity Framework Core,需要查看正在生成的SQL代码。在早期版本的Entity Framework中,我可以使用以下内容:
string sql = ((System.Data.Objects.ObjectQuery)query).ToTraceString();
在哪里查询是一个IQueryable对象...但是ToTraceString在EF Core中不可用。
如何在EF Core中做类似的事情?
Answers:
query.ToQueryString()
查看EF Core 5.0的新增功能
var query = _context.Widgets.Where(w => w.IsReal && w.Id == 42);
var sql = query.ToQueryString();
对于较旧的网络核心框架,可以使用扩展。
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;
}
}
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的Gist
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网络核心团队也跟踪了该问题,并计划在下一个版本中发布。
IQueryable
和not一起使用IQueryable<T>
?
IQueryable<T>
。请参见widget
上面的示例。您是否有一个仅具有IQueryable的示例。
IQueryable
公正的机会
该答案适用于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语句有3种方法IQueryable<>
:
这是疯狂的反射代码(扩展方法):
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
optionsBuilder.UseLoggerFactory(LoggerFactory);
public static readonly LoggerFactory LoggerFactory = new LoggerFactory(new[] { new ConsoleLoggerProvider((_, __) => true, true) });
因为它生成了更漂亮的sql,但不幸的是还产生了很多垃圾邮件。
对于只想诊断一次点火失败的EF Core查询等而不希望更改其代码的任何人,有两种选择:
如果您已经安装了SQL Server Management Studio(SSMS),则可以从SSMS的“工具”菜单中启动SQL事件探查器:
一旦它打开,然后启动在SQL Profiler中运行的新跟踪。
然后,您将能够看到来自EF的传入SQL请求,它们通常结构良好且易于阅读。
在我的VS2019副本中,使用EF2.2,我可以更改输出窗口以显示Web服务器的输出(在“输出”窗格顶部的“显示输出来自”组合中选择应用程序和Web服务器的名称)传出的SQL也显示在其中。我已经检查了我的代码,据我所知我还没有做任何事情来启用它,所以我认为默认情况下它必须这样做:
如果要在查询中查看发送到SQL Server的参数,则可以在使用EnableSensitiveDataLogging
方法设置DBContext时将其打开,例如
services.AddDbContext<FusionContext>(options => options
.UseSqlServer(connectionString))
//.EnableDetailedErrors()
.EnableSensitiveDataLogging()
@Tich-Lil3p在评论中提到他们还需要使用开关来打开项目的“属性”页面("sqlDebugging": true
在LaunchSettings.json中设置)的“调试”选项卡中的SQL调试。我检查了一下,但尚未在我的任何项目中启用该功能,但是如果以上操作都不适合您,则可能值得尝试。
app.UseDeveloperExceptionPage()
在Startup.Configure和 services.AddServerSideBlazor() .AddCircuitOptions(options => { options.DetailedErrors = true; });
Startup.ConfigureServices中拥有的代码。其中之一可能会显示参数。
我基于@ 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);
}
}
添加此答案的原因是,此处的所有建议在新的EF Core版本中均已失效(即,此处的所有答案在EF Core 2.2中均已失效)。这是第一次尝试对我有用的代码,到目前为止似乎与.NET Core版本无关(https://blogs.msdn.microsoft.com/dbrowne/2017/09/22/simple-logging-for -ef-core /
您可以通过日志获取它。
创建工厂:
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,我有以下内容(基于halllo的GitHub注释),这些内容已链接在@ Thom Kiesewetter等人的注释中。
/// <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
};
}
}
也许这并不能替代所有类型,但是大多数都涵盖了。随意扩展。
作为一项公共服务:
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 3和更高版本,EFCore.BulkExtensions具有ToParametrizedSql方法。我唯一的抱怨是它以Microsoft.Data.SqlClient的形式返回参数,因此有时我必须将它们转换为System.Data.SqlClient(如果这是我的连接类型)。
https://github.com/borisdj/EFCore.BulkExtensions
EFCore.BulkExtensions.IQueryableExtensions.ToParametrizedSql