Как создать LINQ Expression Tree для выбора анонимного типа

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

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

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

9 ответов

Решение

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

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

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

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

Вы можете генерировать динамический выбор по вызову 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>());

Я не верю, что вы сможете достичь этого. Хотя, когда вы делаете select new { c.Name, c.Population } кажется, что вы не создаете класс, который вы на самом деле. Если вы посмотрите на скомпилированный вывод в Reflector или на сырой 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, вы можете определить, является ли текущее проанализированное выражение выбором, а затем определить CompilerGenerated класс, отразить для его конструктора и создать.

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

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

https://github.com/thiscode/DynamicSelectExtensions

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

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

var YourDynamicListOfFields = new List<string>(

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

)
var query = query.SelectPartially(YourDynamicListOfFields);

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

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>,

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

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

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

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

Edit2:

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

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

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

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

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

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

Другие вопросы по тегам