我正在尝试使用 ObjectQuery 的 ToTraceString() 在 EF (4.3) 上构建一些自定义扩展,以从 LINQ 代码生成原始 SQL。
我注意到,虽然在某些情况下,SQL 中的列名称与查询元素类型的属性名称相匹配,但在其他情况下,它们被命名为 C1、C2 等。这让我很难做到与生成的 SQL 相关的任何内容。有谁知道如何将这些列名称映射回属性名称(我只关心能够对诸如匿名类型之类的平面元组执行此操作,如果有帮助的话,我不需要任何层次结构)。
依赖于私有/内部反射的解决方案是完全可以接受的。我知道我可能需要在未来的 EF 版本中对其进行调整。另外,如果有帮助的话,我只使用 MS SqlServer (2008+)。
我刚刚问并回答了一个类似的问题(您可以忽略所有 DataTable 内容,但它可能很有用,因为它显示了如何使用映射):
展示如何获取对象属性位置和 SQL 语句列位置之间映射的原始帖子可以在这里找到:
注意: 第二个链接仅提供对象属性和 SQL 语句列之间的 positions (作为整数值)的映射...您必须使用反射来获取实际的对象属性名称和然后对 SQL 语句进行某种字符串分析以获取 SQL 语句列名称(您可以为此使用正则表达式,但这可能有点过头了)。
对于任何尝试将 IQueryable linq 查询转换为其 TSQL 表示形式的人,您可以在将 Entity Framework - IQueryable 到 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();
}