Подтвердить что ты не робот

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

Я хотел бы генерировать следующий оператор 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};

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

4b9b3361

Ответ 1

Это можно сделать, как уже упоминалось, с помощью 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));
    }
}

Ответ 2

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

Реальный анонимный тип имеет свойства только для чтения, конструктор для заполнения всех значений, реализацию 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 конструктор должен вызываться с набором параметров "members". Например:

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

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

Ответ 3

Здесь вы можете использовать IQueryable-Extensions, который является воплощением решения, описанного "Этаном Дж. Брауном":

https://github.com/thiscode/DynamicSelectExtensions

Расширение строит динамически анонимный тип.

Затем вы можете сделать это:

var YourDynamicListOfFields = new List<string>(

    "field1",
    "field2",
    [...]

)
var query = query.SelectPartially(YourDynamicListOfFields);

Ответ 4

Я не верю, что вы сможете это достичь. Хотя, когда вы делаете select new { c.Name, c.Population }, похоже, что вы не создаете класс, который вы на самом деле. Если вы посмотрите на скомпилированный вывод в Reflector или raw IL, вы сможете это увидеть.

У вас будет класс, который будет выглядеть примерно так:

[CompilerGenerated]
private class <>c__Class {
  public string Name { get; set; }
  public int Population { get; set; }
}

(Хорошо, я очистил его прикосновением, так как свойство действительно всего лишь метод get_Name() и set_Name(name), установленный в любом случае)

То, что вы пытаетесь сделать, - это правильное создание динамического класса, то, что не будет доступно до выхода .NET 4.0 (и даже тогда я не уверен, сможет ли он достичь того, чего вы хотите).

Лучшим решением будет определение различных анонимных классов, а затем какая-то логическая проверка, чтобы определить, какой из них создать, и для ее создания вы можете использовать объект System.Linq.Expressions.NewExpression.

Но, возможно, это возможно (по крайней мере, теоретически) это сделать, если вы очень сильно заработаете базовый LINQ-провайдер. Если вы написали свой собственный поставщик LINQ, вы можете определить, является ли текущее разборное выражение Select, тогда вы определяете класс CompilerGenerated, отражаете его конструктор и создаете.

Определенно не простая задача, но это будет так, как это делают LINQ to SQL, LINQ to XML и т.д.

Ответ 5

Вы можете использовать класс параметров вместо работы с анонимным типом. В вашем примере вы можете создать класс параметров следующим образом:

public struct ParamClass {
    public string Name { get; set; };
    public int Population { get; set; };
}

... и поместите его в свой выбор следующим образом:

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

Что вы получаете, это что-то типа IQueryable<ParamClass>.

Ответ 6

Это компилируется, я не знаю, работает ли это...

myEnumerable.Select((p) => { return new { Name = p.Name, Description = p.Description }; });

Предполагая, что p - это ваше преобразование, а оператор select возвращает тип anon, используя объявление функции лямбда.

Изменить: я также не знаю, как вы могли бы генерировать это динамически. Но по крайней мере это показывает вам, как использовать select lambda для возврата типа анона с несколькими значениями

Edit2:

Вам также следует иметь в виду, что компилятор С# фактически генерирует статические классы типа anon. Таким образом, тип anon действительно имеет тип после компиляции. Поэтому, если вы генерируете эти запросы во время выполнения (что, как я полагаю, вы есть), вам может понадобиться построить тип с использованием различных методов отражения (я считаю, что вы можете использовать их для создания типов на лету) загружать созданные типы в контекст выполнения и используйте их в своем сгенерированном выходе.

Ответ 7

Я думаю, что большинство вещей уже ответили - как сказал Slace, вам нужен какой-то класс, который будет возвращен из метода Select. Когда у вас есть класс, вы можете использовать метод System.Linq.Expressions.NewExpression для создания выражения.

Если вы действительно хотите это сделать, вы можете также генерировать класс во время выполнения. Это немного больше, потому что это невозможно, используя деревья выражений LINQ, но это возможно. Вы можете использовать пространство имен System.Reflection.Emit для этого - я просто сделал быстрый поиск, и вот статья, которая объясняет это:

Ответ 8

Вы можете использовать Dynamic Expression API, который позволяет динамически строить ваш оператор select следующим образом:

 Select("new(<property1>,<property2>,...)");

Вам нужен файл Dynamics.cs из образцов LINQ и языка Visual Studio для этого, оба они связаны внизу this page. Вы также можете увидеть рабочий пример, показывающий это в действии с тем же URL-адресом.

Ответ 9

Может быть, немного поздно, но может помочь кому-то.

Вы можете генерировать динамический выбор по вызову DynamicSelectGenerator при выборе из объекта.

public static Func<T, T> DynamicSelectGenerator<T>()
            {
                // get Properties of the T
                var fields = typeof(T).GetProperties().Select(propertyInfo => propertyInfo.Name).ToArray();

            // input parameter "o"
            var xParameter = Expression.Parameter(typeof(T), "o");

            // new statement "new Data()"
            var xNew = Expression.New(typeof(T));

            // create initializers
            var bindings = fields.Select(o => o.Trim())
                .Select(o =>
                {

                    // property "Field1"
                    var mi = typeof(T).GetProperty(o);

                    // original value "o.Field1"
                    var xOriginal = Expression.Property(xParameter, mi);

                    // set value "Field1 = o.Field1"
                    return Expression.Bind(mi, xOriginal);
                }
            );

            // initialization "new Data { Field1 = o.Field1, Field2 = o.Field2 }"
            var xInit = Expression.MemberInit(xNew, bindings);

            // expression "o => new Data { Field1 = o.Field1, Field2 = o.Field2 }"
            var lambda = Expression.Lambda<Func<T, T>>(xInit, xParameter);

            // compile to Func<Data, Data>
            return lambda.Compile();
        }

И используйте этот код:

var result = dbContextInstancs.EntityClass.Select(DynamicSelectGenerator<EntityClass>());