我试图弄清楚是否有办法检索在数据库服务器上执行的(完整)sql 语句。
我已经找到了一些东西,但它并不完全是我想要的:
IQueryable<SomeType> someQuery = ...
string command = dataContext.GetCommand(query).CommandText;
就我而言,这给了我一个类似以下的命令字符串:
SELECT TOP (50) [t0].[ID], ....
FROM [dbo].[someTable] AS [t0]
WHERE ([t0].[someColumn] IS NOT NULL) AND (([t0].[someColumn]) IN (@p0))
在数据库上执行:
exec sp_executesql N'SELECT TOP (50) [t0].[ID], ...
FROM [dbo].[someTable] AS [t0]
WHERE ([t0].[someColumn] IS NOT NULL) AND (([t0].[someColumn]) IN (@p0, @p1))',N'@p0 int,@p1 int',@p0=401,@p1=201
有没有办法从 C# 代码中检索这个“完整”语句(还有参数值)?
如果您有以下实例,您还可以看到生成的 sql 查询
IQueryable<T>
并调用 .ToString()
方法。var db = new DbContext();
IQueryable<Blog> query = db.Blog.Where(tt=> tt.Id > 100).OrderByDescending(tt=>tt.Id);
var sqlString = query.ToString();
Console.WriteLine(sqlString);
这将生成以下输出:
SELECT [Extent1].[Id] AS [Id],
[Extent1].[Title] AS [Title],
[Extent1].[Author] AS [Author],
[Extent1].[Text] AS [Text],
[Extent1].[CreatedAt] AS [CreatedAt],
[Extent1].[UpdatedAt] AS [UpdatedAt]
FROM [dbo].[Blogs] AS [Extent1]
WHERE [Extent1].[Id] > 100
ORDER BY [Extent1].[Id] DESC
在最新版本的 EF Core 5 ToQueryString,
query.ToQueryString()
获得命令后,您可以打印 CommandText,然后循环遍历参数集合并打印所有单独的参数。
还有 linq-to-sql 调试可视化工具,它在调试模式下执行相同的操作。
查看正在发生的查询的一个非常好的工具是 Linq-to-sql 探查器
(SqlCommand)dataContext.GetCommand(query)
您可以访问参数集合。
我正在使用
Datacontext.Log
属性来获取生成的 SQL 语句 (它包括语句文本和参数)。
只需设置
YourDataContext.Log = SomeTextWriter
。
它可以写入文件(
Log = new StreamWriter(@"c:\temp\linq.log")
)或调试窗口,参见这篇文章
在 Locals 窗格中查看
IQueryable
时,您可以访问 DebugView > Query,其中将包含 SQL 语句。
对于任何尝试将 IQueryable linq 查询转换为其 TSQL 表示形式的人,您可以使用以下内容,这是针对 EntityFramework 6 的。
解决方案由Entity Framework - IQueryable to DataTable提供的以下解决方案组成,以及Entity Framework如何管理将查询结果映射到匿名类型?上面由HydroPowerDeveloper指出。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Core.Objects;
using System.Data.SqlClient;
using System.Reflection;
using System.Text.RegularExpressions;
public class DbQuerySQL
{
public string TSQL { get; } // Use readonly property
public List<SqlParameter> SqlParameters { get; } // Use readonly property
public DbQuerySQL(string tsql, List<SqlParameter> sqlParameters)
{
TSQL = tsql;
SqlParameters = sqlParameters;
}
}
public static class DbQueryExtensions
{
/// <summary>
/// Converts an IQueryable<T> query to a SQL string representation along with its parameters.
/// </summary>
/// <typeparam name="T">The type of elements in the query.</typeparam>
/// <param name="query">The IQueryable<T> query to convert.</param>
/// <returns>A DbQuerySQL object containing the SQL string representation and its parameters.</returns>
public static DbQuerySQL ToDbQuerySQL<T>(this IQueryable<T> query)
{
// Retrieve the ObjectQuery<T> from the IQueryable<T> query
var objectQuery = (query as DbQuery<T>).GetObjectQuery();
// Get the SQL string representation of the query
var sqlTrace = objectQuery.ToTraceString();
// Transform the SQL string to include mapped column names and store it in finalSql
var finalSql = GetFinalSqlFromTrace(sqlTrace, objectQuery);
// Extract parameters from the ObjectQuery<T> and convert them to SqlParameters
var sqlParameters = objectQuery.Parameters
.Select(parameter => new SqlParameter("@" + parameter.Name, parameter.Value))
.ToList();
// Return a DbQuerySQL object containing the final SQL string and its parameters
return new DbQuerySQL(finalSql, sqlParameters);
}
/// <summary>
/// Modifies the SQL trace string to replace column names with their corresponding entity properties.
/// </summary>
/// <typeparam name="T">The type of elements in the ObjectQuery.</typeparam>
/// <param name="sqlTrace">The SQL trace string to modify.</param>
/// <param name="objectQuery">The ObjectQuery representing the SQL query.</param>
/// <returns>The modified SQL trace string with replaced column names.</returns>
private static string GetFinalSqlFromTrace<T>(string sqlTrace, ObjectQuery<T> objectQuery)
{
// Map the column names to entity properties
var columnMappings = GetPropertyPositions(objectQuery);
// Define a regular expression to capture the entire SELECT clause, including its columns, from a multiline SQL query string.
var regex = new Regex("^(SELECT(?<columns>.*?))(FROM)", RegexOptions.Singleline);
// Apply the regular expression to the SQL trace
Match match = regex.Match(sqlTrace);
// Check if the regular expression matched successfully
if (match.Success)
{
// Get the captured columns group
var cols = match.Groups["columns"];
if (cols.Success)
{
// Split the captured columns by comma to extract each column name
const string As = " AS ";
var colNames = cols.Value.Split(',');
// Construct the new SELECT clause with replaced columns
var newSelectClause = new StringBuilder();
foreach (var colName in colNames)
{
// Get the corresponding column mapping and column name
var index = Array.IndexOf(colNames, colName);
if (index < columnMappings.Length)
{
var columnMapping = columnMappings[index];
var colNameAs = colName.Substring(colName.IndexOf(As, StringComparison.InvariantCulture) + As.Length);
var newCol = colName.Replace($"{As}{colNameAs}", $"{As}[{columnMapping}]");
newSelectClause.Append(newCol);
if (index < colNames.Length - 1)
{
newSelectClause.Append(", ");
}
}
}
// Replace only the columns within the original SELECT clause
sqlTrace = sqlTrace.Replace(cols.Value, newSelectClause.ToString());
}
}
return sqlTrace;
}
/// <summary>
/// Retrieves the ObjectQuery<T> from the provided DbQuery<T>.
/// </summary>
/// <typeparam name="T">The type of elements in the query.</typeparam>
/// <param name="query">The DbQuery<T> instance from which to retrieve the ObjectQuery<T>.</param>
/// <returns>The ObjectQuery<T> instance corresponding to the DbQuery<T>.</returns>
private static ObjectQuery<T> GetObjectQuery<T>(this DbQuery<T> query)
{
// Get the _internalQuery field from the DbQuery<T> using reflection
var internalQueryField = query.GetType().GetField("_internalQuery", BindingFlags.NonPublic | BindingFlags.Instance);
// Retrieve the value of the _internalQuery field
var internalQuery = internalQueryField.GetValue(query);
// Get the _objectQuery field from the internal query object using reflection
var objectQueryField = internalQuery.GetType().GetField("_objectQuery", BindingFlags.NonPublic | BindingFlags.Instance);
// Retrieve the value of the _objectQuery field, which should be an ObjectQuery<T>
return (ObjectQuery<T>)objectQueryField.GetValue(internalQuery);
}
/// <summary>
/// Retrieves the property positions (column names) from the ObjectQuery's column mappings.
/// </summary>
/// <param name="query">The ObjectQuery from which to retrieve the property positions.</param>
/// <returns>An array of strings representing the property positions (column names).</returns>
private static string[] GetPropertyPositions(this ObjectQuery query)
{
// Retrieve the QueryState object from the ObjectQuery using reflection
object queryState = GetProperty(query, "QueryState");
//// Ensure that the retrieved object is of the expected type
//AssertNonNullAndOfType(queryState, "System.Data.Entity.Core.Objects.ELinq.ELinqQueryState");
// Retrieve the cached query execution plan from the QueryState object
object plan = GetField(queryState, "_cachedPlan");
//// Ensure that the retrieved object is of the expected type
//AssertNonNullAndOfType(plan, "System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlan");
// Retrieve the command definition from the query execution plan
object commandDefinition = GetField(plan, "CommandDefinition");
//// Ensure that the retrieved object is of the expected type
//AssertNonNullAndOfType(commandDefinition, "System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition");
// Retrieve the column map generators from the command definition
var columnMapGeneratorArray = GetField(commandDefinition, "_columnMapGenerators") as object[];
//// Ensure that the retrieved object is of the expected type
//AssertNonNullAndOfType(columnMapGeneratorArray, "System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition+IColumnMapGenerator[]");
var columnMapGenerator = columnMapGeneratorArray[0];
// Retrieve the column map from the column map generator
object columnMap = GetField(columnMapGenerator, "_columnMap");
//// Ensure that the retrieved object is of the expected type
//AssertNonNullAndOfType(columnMap, "System.Data.Entity.Core.Query.InternalTrees.SimpleCollectionColumnMap");
// Retrieve the element from the column map
object columnMapElement = GetProperty(columnMap, "Element");
//// Ensure that the retrieved object is of the expected type
//AssertNonNullAndOfType(columnMapElement, "System.Data.Entity.Core.Query.InternalTrees.RecordColumnMap");
// Retrieve the properties (column maps) from the element
Array columnMapProperties = GetProperty(columnMapElement, "Properties") as Array;
//// Ensure that the retrieved object is of the expected type
//AssertNonNullAndOfType(columnMapProperties, "System.Data.Entity.Core.Query.InternalTrees.ColumnMap[]");
// Determine the number of properties (columns)
int n = columnMapProperties.Length;
string[] propertyPositions = new string[n];
// Iterate through the properties (columns) to retrieve their names and positions
for (int i = 0; i < n; ++i)
{
// Retrieve the column map at index i
object column = columnMapProperties.GetValue(i);
//// Ensure that the retrieved object is of the expected type
//AssertNonNullAndOfType(column, "System.Data.Entity.Core.Query.InternalTrees.ScalarColumnMap");
// Retrieve the name of the column
string colName = (string)GetProperty(column, "Name");
// Retrieve the position of the column
object columnPositionOfAProperty = GetProperty(column, "ColumnPos");
//// Ensure that the retrieved object is of the expected type
//AssertNonNullAndOfType(columnPositionOfAProperty, "System.Int32");
// Store the column name at its corresponding position in the array
propertyPositions[(int)columnPositionOfAProperty] = colName;
}
return propertyPositions;
}
/// <summary>
/// Retrieves the value of a non-public instance property from an object using reflection.
/// </summary>
/// <param name="obj">The object from which to retrieve the property value.</param>
/// <param name="propName">The name of the property to retrieve.</param>
/// <returns>The value of the specified property.</returns>
private static object GetProperty(object obj, string propName)
{
// Get the PropertyInfo object for the specified property name
PropertyInfo prop = obj.GetType().GetProperty(propName, BindingFlags.NonPublic | BindingFlags.Instance);
// If the property does not exist, throw an exception indicating that the Entity Framework internals have changed
if (prop == null) throw EFChangedException();
// Retrieve and return the value of the property from the object
return prop.GetValue(obj, new object[0]);
}
/// <summary>
/// Retrieves the value of a non-public instance field from an object using reflection.
/// </summary>
/// <param name="obj">The object from which to retrieve the field value.</param>
/// <param name="fieldName">The name of the field to retrieve.</param>
/// <returns>The value of the specified field.</returns>
private static object GetField(object obj, string fieldName)
{
// Get the FieldInfo object for the specified field name
FieldInfo field = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
// If the field does not exist, throw an exception indicating that the Entity Framework internals have changed
if (field == null) throw EFChangedException();
// Retrieve and return the value of the field from the object
return field.GetValue(obj);
}
/// <summary>
/// Asserts that the specified object is not null and is of the expected type.
/// </summary>
/// <param name="obj">The object to assert.</param>
/// <param name="fullName">The full name of the expected type.</param>
private static void AssertNonNullAndOfType(object obj, string fullName)
{
// If the object is null, throw an exception indicating that the Entity Framework internals have changed
if (obj == null) throw EFChangedException();
// Retrieve the full name of the object's type
string typeFullName = obj.GetType().FullName;
// If the type does not match the expected type, throw an exception indicating that the Entity Framework internals have changed
if (typeFullName != fullName) throw EFChangedException();
}
/// <summary>
/// Constructs and returns an InvalidOperationException indicating that the Entity Framework internals have changed.
/// </summary>
/// <returns>An InvalidOperationException indicating that the Entity Framework internals have changed.</returns>
private static InvalidOperationException EFChangedException()
{
// Create and return an InvalidOperationException with an appropriate error message
return new InvalidOperationException("Entity Framework internals have changed, please review and fix reflection code");
}
}
方法调用示例:
using (MyDbEntities dataContext = new MyDbEntities())
{
int someTableAId = 1234;
var query = dataContext.SomeTableA.AsNoTracking().Where(ps => ps.SomeTableAId == someTableAId)
.Join(dataContext.SomeTableB.AsNoTracking(), ps => ps.SomeTableBId, sta => sta.SomeTableBId, (someTableA, someTableB) => new { someTableA, someTableB })
.Select(s => new MySpecialSummary // using linq projection into a class that is not part of the database
{
SomeTableBId = s.someTableB.SomeTableBId,
SomeFlag = s.someTableB.IsSomeFlag,
SomeTableCode = s.someTableB.IdentificationCode,
SomeTableAliases = dataContext.Fn_GetAliases(s.someTableB.SomeTableBId).Select(tvFn => tvFn.AliasNames).FirstOrDefault(), // database table function call
SomeTableCombo = s.someTableA.ComboName,
SomeTableName = s.someTableB.SomeTableName
});
DbQuerySQL dbQuery = query.ToDbQuerySQL(); // Get the TSQL code and sql parameters
var locations = query.ToList();
}