Как создать дерево выражений LINQ, чтобы выбрать анонимный тип

c# entity-framework expression-trees linq linq-to-entities

Вопрос

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

var v = from c in Countries
        where c.City == "London"
        select new {c.Name, c.Population};

Я разработал способ генерации

var v = from c in Countries
        where c.City == "London"
        select new {c.Name};

но я не могу найти конструктор / перегрузку, что позволит мне указать несколько свойств в моей избранной лямбда.

Принятый ответ

Это можно сделать, как уже упоминалось, с помощью Reflection Emit и вспомогательного класса, который я включил ниже. Код ниже - это незавершенная работа, поэтому возьмите ее за то, что она стоит ... «она работает на моей коробке». Класс метода SelectDynamic следует бросить в класс статического метода расширения.

Как и ожидалось, вы не получите Intellisense, так как тип не создается до выполнения. Хорошо работает с поздними данными.

public static IQueryable SelectDynamic(this IQueryable source, IEnumerable<string> fieldNames)
{
    Dictionary<string, PropertyInfo> sourceProperties = fieldNames.ToDictionary(name => name, name => source.ElementType.GetProperty(name));
    Type dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(sourceProperties.Values);

    ParameterExpression sourceItem = Expression.Parameter(source.ElementType, "t");
    IEnumerable<MemberBinding> bindings = dynamicType.GetFields().Select(p => Expression.Bind(p, Expression.Property(sourceItem, sourceProperties[p.Name]))).OfType<MemberBinding>();

    Expression selector = Expression.Lambda(Expression.MemberInit(
        Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)), bindings), sourceItem);

    return source.Provider.CreateQuery(Expression.Call(typeof(Queryable), "Select", new Type[] { source.ElementType, dynamicType },
                 Expression.Constant(source), selector));
}



public static class LinqRuntimeTypeBuilder
{
    private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
    private static AssemblyName assemblyName = new AssemblyName() { Name = "DynamicLinqTypes" };
    private static ModuleBuilder moduleBuilder = null;
    private static Dictionary<string, Type> builtTypes = new Dictionary<string, Type>();

    static LinqRuntimeTypeBuilder()
    {
        moduleBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run).DefineDynamicModule(assemblyName.Name);
    }

    private static string GetTypeKey(Dictionary<string, Type> fields)
    {
        //TODO: optimize the type caching -- if fields are simply reordered, that doesn't mean that they're actually different types, so this needs to be smarter
        string key = string.Empty;
        foreach (var field in fields)
            key += field.Key + ";" + field.Value.Name + ";";

        return key;
    }

    public static Type GetDynamicType(Dictionary<string, Type> fields)
    {
        if (null == fields)
            throw new ArgumentNullException("fields");
        if (0 == fields.Count)
            throw new ArgumentOutOfRangeException("fields", "fields must have at least 1 field definition");

        try
        {
            Monitor.Enter(builtTypes);
            string className = GetTypeKey(fields);

            if (builtTypes.ContainsKey(className))
                return builtTypes[className];

            TypeBuilder typeBuilder = moduleBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Serializable);

            foreach (var field in fields)                    
                typeBuilder.DefineField(field.Key, field.Value, FieldAttributes.Public);

            builtTypes[className] = typeBuilder.CreateType();

            return builtTypes[className];
        }
        catch (Exception ex)
        {
            log.Error(ex);
        }
        finally
        {
            Monitor.Exit(builtTypes);
        }

        return null;
    }


    private static string GetTypeKey(IEnumerable<PropertyInfo> fields)
    {
        return GetTypeKey(fields.ToDictionary(f => f.Name, f => f.PropertyType));
    }

    public static Type GetDynamicType(IEnumerable<PropertyInfo> fields)
    {
        return GetDynamicType(fields.ToDictionary(f => f.Name, f => f.PropertyType));
    }
}

Популярные ответы

Принятый ответ очень полезен, но мне нужно что-то немного ближе к реальному анонимному типу.

У реального анонимного типа есть свойства только для чтения, конструктор для заполнения всех значений, реализация Equals / GetHashCode для сравнения значений каждого свойства и реализация ToString, которая включает имя / значение каждого свойства. (См. Https://msdn.microsoft.com/en-us/library/bb397696.aspx для полного описания анонимных типов.)

Основываясь на этом определении анонимных классов, я помещаю класс, который генерирует динамические анонимные типы в github по адресу https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs . Проект также содержит некоторые модульные тесты, чтобы убедиться, что поддельные анонимные типы ведут себя как настоящие.

Вот очень простой пример того, как его использовать:

AnonymousTypeUtils.CreateObject(new Dictionary<string, object>
{
    { "a", 1 },
    { "b", 2 }
});

Кроме того, еще одно замечание: я обнаружил, что при использовании динамического анонимного типа с Entity Framework конструктор должен вызываться с помощью набора параметров «члены». Например:

Expression.New(
    constructor: anonymousType.GetConstructors().Single(), 
    arguments: propertyExpressions,
    members: anonymousType.GetProperties().Cast<MemberInfo>().ToArray()
); 

Если вы использовали одну из версий Expression.New, которая не включает параметр «members», Entity Framework не распознает его как конструктор анонимного типа. Поэтому я предполагаю, что это означает, что выражение конструктора реального анонимного типа будет включать информацию о «членах».



Related

Лицензировано согласно: CC-BY-SA with attribution
Не связан с Stack Overflow
Является ли этот КБ законным? Да, узнайте, почему
Лицензировано согласно: CC-BY-SA with attribution
Не связан с Stack Overflow
Является ли этот КБ законным? Да, узнайте, почему