Как Entity Framework управляет отображением результата запроса в анонимный тип? - PullRequest
5 голосов
/ 18 октября 2011

Рассмотрим следующий пример LINQ to entity query

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

ToTraceString() возвращаемая строка выглядит следующим образом:

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

Для меня проблема в том, что столбцы располагаются в другом порядке, чем запроси не используйте псевдонимы типа login в примере.Где Entity Framework хранит информацию о сопоставлении для анонимных типов?

Справочная информация: Я собираюсь разработать вставку с операцией выбора с использованием LINQ для сущности для массовых операций.

Обновление: Вставить с помощью select не так сложно, за исключением неизвестного алгоритма сопоставления столбца и свойства.Можно получить имена таблиц и столбцов для пункта назначения ObjectSet, используя метаданные, построить строку оператора INSERT INTO tableName (column_name1, …) sql, а затем добавить несколько операторов ObjectQuery.ToTraceString SELECT.Затем создайте DbCommand с результирующим текстом, используя ((EntityConnection)ObjectContext.Connection).StoreConnection, и заполните параметры команды из ObjectQuery.Поэтому проблема состоит в том, чтобы найти соответствующий порядок столбцов во вставленных и выбранных записях.

Ответы [ 3 ]

4 голосов
/ 20 октября 2011

Вот мое решение для рядовых и внутренних работников. Он отражается в кэшированном плане запросов, который будет существовать после ToTraceString вызова или выполнения запроса, чтобы получить то, что называется _columnMap. Карта столбцов содержит ScalarColumnMap объектов, расположенных в порядке свойств анонимного объекта и указывающих на соответствующую позицию столбца с помощью свойства ColumnPos.

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");
    }
}

Я думаю, что некоторые утверждения можно смягчить, чтобы проверить не точный тип, а базовый тип, содержащий необходимое свойство.

Есть ли решение без отражения?

0 голосов
/ 12 июня 2012

Обновлено описание для EF 4.4 (5-RC)

полный пост на http://imaginarydevelopment.blogspot.com/2012/06/compose-complex-inserts-from-select.html

используя эту функциональность / логику для выполнения массовой вставки из выбора с некоторыми параметрами

int Insert<T>(IQueryable query,IQueryable<T> targetSet)
{
    var oQuery=(ObjectQuery)this.QueryProvider.CreateQuery(query.Expression);
    var sql=oQuery.ToTraceString();
    var propertyPositions = GetPropertyPositions(oQuery);

    var targetSql=((ObjectQuery)targetSet).ToTraceString();
    var queryParams=oQuery.Parameters.ToArray();
    System.Diagnostics.Debug.Assert(targetSql.StartsWith("SELECT"));
    var queryProperties=query.ElementType.GetProperties();
    var selectParams=sql.Substring(0,sql.IndexOf("FROM "));
    var selectAliases=Regex.Matches(selectParams,@"\sAS \[([a-zA-Z0-9_]+)\]").Cast<Match>().Select(m=>m.Groups[1].Value).ToArray();

    var from=targetSql.Substring(targetSql.LastIndexOf("FROM [")+("FROM [".Length-1));
    var fromAlias=from.Substring(from.LastIndexOf("AS ")+"AS ".Length);
    var target=targetSql.Substring(0,targetSql.LastIndexOf("FROM ["));
    target=target.Replace("SELECT","INSERT INTO "+from+" (")+")";
    target=target.Replace(fromAlias+".",string.Empty);
    target=Regex.Replace(target,@"\sAS \[[a-zA-z0-9]+\]",string.Empty);
    var insertParams=target.Substring(target.IndexOf('('));
    target = target.Substring(0, target.IndexOf('('));
    var names=Regex.Matches(insertParams,@"\[([a-zA-Z0-9]+)\]");

    var remaining=names.Cast<Match>().Select(m=>m.Groups[1].Value).Where(m=>queryProperties.Select(qp=>qp.Name).Contains(m)).ToArray(); //scrape out items that the anonymous select doesn't include a name/value for

      //selectAliases[propertyPositions[10]]
      //remaining[10]
    var insertParamsOrdered = remaining.Select((s, i) => new { Position = propertyPositions[i], s })
       .OrderBy(o => o.Position).Select(x => x.s).ToArray();
   var insertParamsDelimited = insertParamsOrdered.Aggregate((s1, s2) => s1 + "," + s2);
   var commandText = target + "(" + insertParamsDelimited + ")" + sql;
   var result=this.ExecuteStoreCommand(commandText,queryParams.Select(qp=>new System.Data.SqlClient.SqlParameter{ ParameterName=qp.Name, Value=qp.Value}).ToArray());
   return result;
}
0 голосов
/ 18 октября 2011

То, как столбцы в запросе имеют псевдонимы, не должно иметь значения, равно как и их порядок.Entity Framework обрабатывает заполнение нового экземпляра вашего анонимного типа каждым результатом, и именно здесь вы получаете псевдоним, такой как login.

. В качестве примечания, я думаю, что Entity Framework может работать не совсем так, как вы думаете.Вы не можете сделать выбор / вставку в одной операции, как вы можете использовать обычный запрос SQL.Entity Framework выполнит ваш выбор, вернет результаты обратно, использует эти результаты для создания новых экземпляров ваших сущностей (или, в вашем случае, анонимного типа), а затем вам придется использовать каждый результат для создания нового экземпляра вашей цели.введите, добавив каждый из них в контекст вашей сущности / объекта, и, наконец, вызовите сохранение изменений в контексте вашей сущности / объекта.Это приведет к выполнению отдельного оператора вставки для каждой новой добавленной вами сущности.

Если вы хотите сделать все это за одну операцию без создания новой сущности для каждой записи, вам потребуетсялибо использовать хранимую процедуру, отображаемую в вашем контексте, либо выполнить встроенный SQL-запрос, используя ObjectContext.ExecuteStoreCommand

ОБНОВЛЕНИЕ: На основе ваших ответовто, что вы действительно понимаете, ближе к метапрограммированию, которое больше зависит от вашей модели сущностей, чем от фактического использования структуры сущностей.Я не знаю, какую версию EF вы используете (EF 4.0? 4.1 сначала с кодом и DbContext?), Но я добился большого успеха, используя шаблон C # POCO с EF 4.0 (шаблон POCO являетсяскачать из онлайн визуальной студии (галерея).Он использует шаблон T4 для генерации классов POCO из модели данных .edmx.В своем шаблоне T4 вы можете добавить в свой контекст методы, которые по сути будут вызывать ExecuteStoreCommand, но разница будет в том, что вы сможете сгенерировать запрос, который будет выполнен на основе вашей модели данных.Таким образом, каждый раз, когда меняется ваша модель данных, ваш запрос будет синхронизироваться с изменениями.

...