Używam Entity Framework Core i muszę zobaczyć, który kod SQL jest generowany. W poprzednich wersjach Entity Framework mogłem użyć:
string sql = ((System.Data.Objects.ObjectQuery)query).ToTraceString();
Gdzie zapytanie jest obiektem IQueryable ... Ale ToTraceString nie jest dostępne w EF Core.
Jak mogę zrobić coś podobnego w EF Core?
entity-framework-core
Miguel Moura
źródło
źródło
Odpowiedzi:
Rdzeń EF 5 / Net 5
query.ToQueryString()
Zobacz, co nowego w EF Core 5.0var query = _context.Widgets.Where(w => w.IsReal && w.Id == 42); var sql = query.ToQueryString();
W przypadku starszych frameworków sieciowych można użyć rozszerzenia.
Rdzeń 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; }
zobacz Gist z RosiOli
EF Core 3.1.0
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);
Problem jest również śledzony przez główny zespół EF net i jest zaplanowany na następne wydanie.
źródło
IQueryable
a nieIQueryable<T>
?IQueryable<T>
. Zobaczwidget
przykład powyżej. Czy masz przykład, który ma tylko IQueryable.IQueryable
właśniePonieważ nazwa EF 7 została zmieniona na Entity Framework Core, podsumuję opcje dla EF Core.
Istnieją 3 sposoby rejestrowania instrukcji SQL z
IQueryable<>
:Oto szalony kod odbicia (metoda rozszerzenia):
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; } }
Po dodaniu tej metody rozszerzenia do kodu możesz użyć następującej metody:
// 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();
Polecenie: http://rion.io/2016/10/19/accessing-entity-framework-core-queries-behind-the-scenes-in-asp-net-core/ i https://gist.github.com / rionmonster / 2c59f449e67edf8cd6164e9fe66c545a
źródło
optionsBuilder.UseLoggerFactory(LoggerFactory);
public static readonly LoggerFactory LoggerFactory = new LoggerFactory(new[] { new ConsoleLoggerProvider((_, __) => true, true) });
ponieważ generuje jeszcze piękniejszy sql, ale niestety również dużo spamu.Dla każdego, kto próbuje zdiagnozować jednorazową nieprawidłową kwerendę EF Core lub podobną i nie chce zmieniać swojego kodu, istnieje kilka opcji:
Użyj SQL Server Management Studio (SSMS) SQL Profiler
Jeśli masz zainstalowany program SQL Server Management Studio (SSMS), możesz po prostu uruchomić program SQL Profiler z menu Narzędzia w SSMS:
Po otwarciu uruchom nowe śledzenie w programie SQL Profiler.
Będziesz wtedy mógł zobaczyć przychodzące żądanie SQL z EF, są one ogólnie dość dobrze sformułowane i łatwe do odczytania.
Sprawdź okno danych wyjściowych w programie Visual Studio
W mojej kopii VS2019 przy użyciu EF2.2 mogę zmienić okno danych wyjściowych, aby wyświetlić dane wyjściowe z serwera internetowego (wybierz nazwę aplikacji i serwera internetowego w menu „Pokaż dane wyjściowe z” w górnej części okienka danych wyjściowych) i wychodzący SQL jest tam również pokazany. Sprawdziłem mój kod i, o ile widzę, nie zrobiłem nic, aby to włączyć, więc myślę, że domyślnie musi to robić:
Jeśli chcesz widzieć parametry wysyłane do serwera SQL w zapytaniach możesz to włączyć podczas konfigurowania DBContext
EnableSensitiveDataLogging
metodą np.services.AddDbContext<FusionContext>(options => options .UseSqlServer(connectionString)) //.EnableDetailedErrors() .EnableSensitiveDataLogging()
@Tich - Lil3p wspomina w komentarzach, że musieli również użyć przełącznika, aby włączyć debugowanie SQL na karcie Debugowanie na stronie Właściwości projektu (która jest
"sqlDebugging": true
ustawiana w LaunchSettings.json). Sprawdziłem i nie włączyłem tego w żadnym z moich projektów, ale może warto z tym eksperymentować, jeśli powyższe nie działa.źródło
app.UseDeveloperExceptionPage()
w Startup.Configure iservices.AddServerSideBlazor() .AddCircuitOptions(options => { options.DetailedErrors = true; });
Startup.ConfigureServices. Jeden z nich może wyświetlać parametry.Moje zdanie na podstawie odpowiedzi @ nikolay-kostov.
Różnica polega na tym, że otrzymuję polecenie SQL z wyodrębnionymi parametrami zamiast na sztywno zakodowanych, co jest bardziej zgodne z tym, jak EF Core wysyła polecenia do bazy danych. Ponadto, jeśli chcesz edytować i wysłać polecenie do bazy danych, lepszą praktyką jest użycie parametrów.
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); } }
źródło
Dodanie tej odpowiedzi, ponieważ wszystkie sugestie w tym miejscu zostały zerwane w nowych wersjach EF Core (tj. Wszystkie odpowiedzi tutaj są uszkodzone w EF Core 2,2). Oto kod, który zadziałał dla mnie za pierwszym razem i wydaje się być niezależny od wersji .NET Core (jak dotąd): https://blogs.msdn.microsoft.com/dbrowne/2017/09/22/simple-logging-for -ef-core /
źródło
Entity Framework Core 3.x
Możesz to uzyskać poprzez logowanie.
Utwórz fabrykę:
Powiedz,
DbContext
której fabryki użyć:Z tego postu
Możesz uzyskać więcej informacji, jeśli chcesz wdrożyć ILogger:
źródło
W przypadku EF Core 3.1 ze zmiennymi mam następujące elementy (na podstawie niektórych komentarzy GitHub z halllo ), które zostały połączone powyżej w komentarzu @ Thom Kiesewetter et al.
Być może nie zastępuje to wszystkich typów, ale większość jest objęta gwarancją. Zapraszam do przedłużenia.
źródło
Jako usługa publiczna:
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);
A następnie te metody rozszerzające (IQueryableExtensions1 dla .NET Core 1.0, IQueryableExtensions dla .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; } } }
źródło
W przypadku EF Core 3 i nowszych EFCore.BulkExtensions ma metodę ToParametrizedSql. Moim jedynym zastrzeżeniem jest to, że zwraca parametry jako Microsoft.Data.SqlClient, więc czasami muszę je przekonwertować na System.Data.SqlClient, jeśli to jest mój typ połączenia.
https://github.com/borisdj/EFCore.BulkExtensions
źródło