Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Entity Framework manage mapping query result to anonymous type?

Consider the following example LINQ to entity query

from history in entities.foreignuserhistory
select new { history.displayname, login=history.username, history.foreignuserid }

ToTraceString() return string looks like:

SELECT "Extent1"."foreignuserid" AS "foreignuserid",
   "Extent1"."displayname"       AS "displayname",
   "Extent1"."username"          AS "username"
FROM "integration"."foreignuserhistory" AS "Extent1"

The problem for me is that columns come in different order from query and do not take aliases like login in the example. Where does Entity Framework store mapping information for anonymous types?

Background: I'm going to develop insert with select operation using LINQ to entity for mass operations.

Update: Insert with select is not that hard except for an unknown column to property mapping algorithm. One can get table and column names for destination ObjectSet using metadata, build INSERT INTO tableName (column_name1, …) sql statement string and then append some ObjectQuery.ToTraceString SELECT statement. Then create a DbCommand with resulting text using ((EntityConnection)ObjectContext.Connection).StoreConnection and fill command’s parameters from ObjectQuery. So the problem is to find matching column order in inserted and selected records.

like image 451
Mike Avatar asked Oct 10 '22 17:10

Mike


1 Answers

Here’s my solution all the way down of privates and internals. It travels with reflection into cached query plan which will exist after ToTraceString call or query execution to get what is called _columnMap. Column map contains ScalarColumnMap objects going in the order of anonymous object’s properties and pointing to the corresponding column position with ColumnPos property.

using System;
using System.Data.Objects;
using System.Reflection;

static class EFQueryUtils
{
    public static int[] GetPropertyPositions(ObjectQuery query)
    {
        // get private ObjectQueryState ObjectQuery._state;
        // of actual type internal class
        //      System.Data.Objects.ELinq.ELinqQueryState
        object queryState = GetProperty(query, "QueryState");
        AssertNonNullAndOfType(queryState, "System.Data.Objects.ELinq.ELinqQueryState");

        // get protected ObjectQueryExecutionPlan ObjectQueryState._cachedPlan;
        // of actual type internal sealed class
        //      System.Data.Objects.Internal.ObjectQueryExecutionPlan
        object plan = GetField(queryState, "_cachedPlan");
        AssertNonNullAndOfType(plan, "System.Data.Objects.Internal.ObjectQueryExecutionPlan");

        // get internal readonly DbCommandDefinition ObjectQueryExecutionPlan.CommandDefinition;
        // of actual type internal sealed class
        //      System.Data.EntityClient.EntityCommandDefinition
        object commandDefinition = GetField(plan, "CommandDefinition");
        AssertNonNullAndOfType(commandDefinition, "System.Data.EntityClient.EntityCommandDefinition");

        // get private readonly IColumnMapGenerator EntityCommandDefinition._columnMapGenerator;
        // of actual type private sealed class
        //      System.Data.EntityClient.EntityCommandDefinition.ConstantColumnMapGenerator
        object columnMapGenerator = GetField(commandDefinition, "_columnMapGenerator");
        AssertNonNullAndOfType(columnMapGenerator, "System.Data.EntityClient.EntityCommandDefinition+ConstantColumnMapGenerator");

        // get private readonly ColumnMap ConstantColumnMapGenerator._columnMap;
        // of actual type internal class
        //      System.Data.Query.InternalTrees.SimpleCollectionColumnMap
        object columnMap = GetField(columnMapGenerator, "_columnMap");
        AssertNonNullAndOfType(columnMap, "System.Data.Query.InternalTrees.SimpleCollectionColumnMap");

        // get internal ColumnMap CollectionColumnMap.Element;
        // of actual type internal class
        //      System.Data.Query.InternalTrees.RecordColumnMap
        object columnMapElement = GetProperty(columnMap, "Element");
        AssertNonNullAndOfType(columnMapElement, "System.Data.Query.InternalTrees.RecordColumnMap");

        // get internal ColumnMap[] StructuredColumnMap.Properties;
        // array of internal abstract class
        //      System.Data.Query.InternalTrees.ColumnMap
        Array columnMapProperties = GetProperty(columnMapElement, "Properties") as Array;
        AssertNonNullAndOfType(columnMapProperties, "System.Data.Query.InternalTrees.ColumnMap[]");

        int n = columnMapProperties.Length;
        int[] propertyPositions = new int[n];
        for (int i = 0; i < n; ++i)
        {
            // get value at index i in array
            // of actual type internal class
            //      System.Data.Query.InternalTrees.ScalarColumnMap
            object column = columnMapProperties.GetValue(i);
            AssertNonNullAndOfType(column, "System.Data.Query.InternalTrees.ScalarColumnMap");

            //string colName = (string)GetProp(column, "Name");
            // can be used for more advanced bingings

            // get internal int ScalarColumnMap.ColumnPos;
            object columnPositionOfAProperty = GetProperty(column, "ColumnPos");
            AssertNonNullAndOfType(columnPositionOfAProperty, "System.Int32");

            propertyPositions[i] = (int)columnPositionOfAProperty;
        }
        return propertyPositions;
    }

    static object GetProperty(object obj, string propName)
    {
        PropertyInfo prop = obj.GetType().GetProperty(propName, BindingFlags.NonPublic | BindingFlags.Instance);
        if (prop == null) throw EFChangedException();
        return prop.GetValue(obj, new object[0]);
    }

    static object GetField(object obj, string fieldName)
    {
        FieldInfo field = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
        if (field == null) throw EFChangedException();
        return field.GetValue(obj);
    }

    static void AssertNonNullAndOfType(object obj, string fullName)
    {
        if (obj == null) throw EFChangedException();
        string typeFullName = obj.GetType().FullName;
        if (typeFullName != fullName) throw EFChangedException();
    }

    static InvalidOperationException EFChangedException()
    {
        return new InvalidOperationException("Entity Framework internals has changed, please review and fix reflection code");
    }
}

I think some assertions can be relaxed to check not the exact type but base type containing necessary property.

Is there a solution without reflection?

like image 105
Mike Avatar answered Oct 18 '22 07:10

Mike