Estoy usando Entity Framework Core y necesito ver qué código SQL se está generando. En versiones anteriores de Entity Framework, podría usar lo siguiente:
string sql = ((System.Data.Objects.ObjectQuery)query).ToTraceString();
Donde la consulta es un objeto IQueryable ... Pero ToTraceString no está disponible en EF Core.
¿Cómo puedo hacer algo similar en EF Core?
entity-framework-core
Miguel Moura
fuente
fuente
Respuestas:
EF core 5 / Net 5
query.ToQueryString()
Vea las novedades de EF Core 5.0var query = _context.Widgets.Where(w => w.IsReal && w.Id == 42); var sql = query.ToQueryString();
Para los frameworks net core más antiguos, se puede utilizar una extensión.
Básico 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; }
ver Gist de 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);
El equipo central de EF net también realiza un seguimiento del problema y está programado para la próxima versión.
fuente
IQueryable
y no con unaIQueryable<T>
?IQueryable<T>
. Vea elwidget
ejemplo anterior. ¿Tiene un ejemplo que solo tiene un IQueryable.IQueryable
soloDado que EF 7 se renombra a Entity Framework Core, resumiré las opciones para EF Core.
Hay 3 enfoques para registrar declaraciones SQL desde
IQueryable<>
:Aquí está el código de reflexión loco (método de extensión):
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; } }
Después de agregar este método de extensión a su código, puede usar el método de la siguiente manera:
// 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();
Referencia: http://rion.io/2016/10/19/accessing-entity-framework-core-queries-behind-the-scenes-in-asp-net-core/ y https://gist.github.com / rionmonster / 2c59f449e67edf8cd6164e9fe66c545a
fuente
optionsBuilder.UseLoggerFactory(LoggerFactory);
public static readonly LoggerFactory LoggerFactory = new LoggerFactory(new[] { new ConsoleLoggerProvider((_, __) => true, true) });
porque genera un sql aún más hermoso, pero desafortunadamente también una gran cantidad de spam.Para cualquiera que esté tratando de diagnosticar una consulta EF Core fallida única o similar y no quiera cambiar su código, hay un par de opciones:
Usar SQL Server Management Studio (SSMS) SQL Profiler
Si tiene instalado SQL Server Management Studio (SSMS), puede iniciar SQL Profiler desde el menú Herramientas en SSMS:
Y luego inicie una nueva traza ejecutándose en SQL Profiler una vez que se abra.
Luego podrá ver la solicitud SQL entrante de EF, generalmente están bastante bien formados y son fáciles de leer.
Compruebe la ventana de salida en Visual Studio
En mi copia de VS2019, usando EF2.2 puedo cambiar la ventana de salida para mostrar la salida del servidor web (seleccione el nombre de su aplicación y servidor web en el combo "Mostrar salida de" en la parte superior del panel Salida) y el SQL saliente también se muestra allí. Revisé mi código y, por lo que puedo ver, no he hecho nada para habilitarlo, así que creo que debe hacer esto de forma predeterminada:
Si desea ver los parámetros enviados al servidor SQL en las consultas, puede activarlos al configurar DBContext con el
EnableSensitiveDataLogging
método, por ejemploservices.AddDbContext<FusionContext>(options => options .UseSqlServer(connectionString)) //.EnableDetailedErrors() .EnableSensitiveDataLogging()
@Tich - Lil3p menciona en los comentarios que también necesitaban usar un interruptor para activar la depuración de SQL en la pestaña Depurar de la página de Propiedades del proyecto (que se establece
"sqlDebugging": true
en LaunchSettings.json). Lo verifiqué y no lo tengo encendido para ninguno de mis proyectos, pero también puede valer la pena experimentar con eso si lo anterior no funciona para usted.fuente
app.UseDeveloperExceptionPage()
en Startup.Configure yservices.AddServerSideBlazor() .AddCircuitOptions(options => { options.DetailedErrors = true; });
en Startup.ConfigureServices. Uno de ellos podría mostrar los parámetros.Mi opinión basada en la respuesta de @ nikolay-kostov.
La diferencia es que obtengo el comando SQL con parámetros extraídos en lugar de codificados, lo que está más en línea con la forma en que EF Core envía comandos a la base de datos. Además, si desea editar y enviar el comando a la base de datos, es una mejor práctica utilizar parámetros.
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); } }
fuente
Añadiendo esta respuesta porque todas las sugerencias aquí se han roto con las nuevas versiones de EF Core (es decir, todas las respuestas aquí están rotas en EF Core 2.2). Aquí hay un código que funcionó para mí en el primer intento, y parece ser una versión independiente de .NET Core (hasta ahora): https://blogs.msdn.microsoft.com/dbrowne/2017/09/22/simple-logging-for -ef-core /
fuente
Entity Framework Core 3.x
Puede obtenerlo mediante el registro.
Crea la fábrica:
Indique
DbContext
qué fábrica utilizar:De esta publicación
Puede obtener más información si desea implementar ILogger:
fuente
Para EF Core 3.1 con variables, tengo lo siguiente (basado en algunos comentarios de GitHub de halllo ) que se vinculó anteriormente en el comentario de @ Thom Kiesewetter et al.
Quizás esto no sustituya a todos los tipos, pero la mayoría están cubiertos. Siéntete libre de extender.
fuente
Como servicio público:
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);
Y luego estos métodos de extensión (IQueryableExtensions1 para .NET Core 1.0, IQueryableExtensions para .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; } } }
fuente
Para EF Core 3 y superior, EFCore.BulkExtensions tiene un método ToParametrizedSql. Mi única queja es que devuelve los parámetros como Microsoft.Data.SqlClient, por lo que a veces tengo que convertirlos a System.Data.SqlClient si ese es mi tipo de conexión.
https://github.com/borisdj/EFCore.BulkExtensions
fuente