Я хочу сохранить sql, созданный запросами entity framework / LINQ для целей документации. Я использовал метод расширения IQueryable из этого сообщения в блоге, чтобы получить необработанный sql: http://rion.io/2016/10/19/accessing-entity-framework-core-queries-behind-the-scenes-in-asp-net-core/
Это отлично подходит для большинства моих запросов. Однако, когда я попытался получить sql из запроса, содержащего UNION, метод расширения вернул sql только для первой половины объединения, игнорируя вторую половину. Кто-нибудь знает, как получить sql до и после объединения?
Для воспроизводимости я использую
Пример запроса EF / LINQ
// Union query on DbContext with DbSet "Tables"
var query = dbContext.Tables.Take(1).Union(dbContext.Tables.Take(2));
// IQueryable Extension method
var sql = query.ToSql();
// Clean up SQL for easier reading
sql = sql.Replace("\n", "").Replace("\r", "");
// Value of sql (Missing second half of union)
// SELECT TOP(1) [t].[Id], [t].[CreateDate], [t].[Description], [t].[DisplayName], [t].[Name], [t].[SourceId], [t].[Sql], [t].[Status]FROM [metadata].[Tables] AS [t]
Расширение 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;
}
}





Вместо этого используйте Linqpad и после получения результата выберите SQL, чтобы просмотреть получившийся sql. Обратите внимание, если вам нужны определенные библиотеки DLL из проекта, просто включите их в запрос.
Похоже, много работы с небольшой прибылью. Имейте в виду, что сгенерированный EF sql технически может изменяться с каждым выпуском. Если кому-то нужен такой документированный sql, почему бы не использовать / использовать хранимые процедуры для хранения бизнес-логики?
Я хочу позволить владельцам бизнеса увидеть, как рассчитываются их показатели, что добавит прозрачности в их процесс отчетности, который они считают невероятно ценным. Незначительные изменения по сравнению с более новыми версиями не имеют отношения к моему варианту использования. Расчет показателей может часто меняться, и такой подход гарантирует, что документация будет оставаться в актуальном состоянии.
Вы уверены, что получите полный SQL с методом расширения ToSql? Эта строка:
var sql = modelVisitor.Queries.First().ToString();
Может ли Союз иметь два запроса?
Попробуйте сначала просмотреть запросы и, например, записать их в консоль.
foreach (var query in modelVisitor.Queries)
Console.WriteLine(query);
Это вызовет toString () для каждого запроса. Вы видите весь запрос союза? Если это так, сделайте string.Join, чтобы объединить весь Sql.
Я тоже думал об этом, но modelVisitor.Queries содержит только одно значение. Однако у queryModel.ResultOperators есть два значения, соответствующие обеим половинам моего союза. Я подозреваю, что есть проблема с modelVisitor.CreateQueryExecutor<TEntity>(queryModel);, который, как я полагаю, заполняет modelVisitor.Queries только одним значением
Спасибо за Ваш ответ. Это может работать для получения SQL на специальной основе, но я хочу сохранить sql в базе данных и обновлять этот sql каждый раз при выполнении запроса. По сути, мой инструмент должен самодокументировать свой собственный sql для справки конечным пользователям.