实体框架在哪里存储属性名称与其在 SQL 中选择的列之间的映射?

问题描述 投票:0回答:2

我正在尝试使用 ObjectQuery 的 ToTraceString() 在 EF (4.3) 上构建一些自定义扩展,以从 LINQ 代码生成原始 SQL。

我注意到,虽然在某些情况下,SQL 中的列名称与查询元素类型的属性名称相匹配,但在其他情况下,它们被命名为 C1、C2 等。这让我很难做到与生成的 SQL 相关的任何内容。有谁知道如何将这些列名称映射回属性名称(我只关心能够对诸如匿名类型之类的平面元组执行此操作,如果有帮助的话,我不需要任何层次结构)。

依赖于私有/内部反射的解决方案是完全可以接受的。我知道我可能需要在未来的 EF 版本中对其进行调整。另外,如果有帮助的话,我只使用 MS SqlServer (2008+)。

c# sql linq entity-framework
2个回答
2
投票

我刚刚问并回答了一个类似的问题(您可以忽略所有 DataTable 内容,但它可能很有用,因为它显示了如何使用映射):

实体框架 - IQueryable 到 DataTable

展示如何获取对象属性位置和 SQL 语句列位置之间映射的原始帖子可以在这里找到:

实体框架如何管理将查询结果映射到匿名类型?

注意: 第二个链接仅提供对象属性和 SQL 语句列之间的 positions (作为整数值)的映射...您必须使用反射来获取实际的对象属性名称和然后对 SQL 语句进行某种字符串分析以获取 SQL 语句列名称(您可以为此使用正则表达式,但这可能有点过头了)。


0
投票

对于任何尝试将 IQueryable linq 查询转换为其 TSQL 表示形式的人,您可以在将 Entity Framework - IQueryable 到 DataTableEntity 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();
}
© www.soinside.com 2019 - 2024. All rights reserved.