Como criar uma tree de expressões LINQ para selecionar um tipo anônimo

Gostaria de gerar a seguinte instrução select dinamicamente usando trees de expressão:

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

Eu tenho trabalhado como gerar

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

mas não consigo encontrar um construtor / sobrecarga que me permita especificar várias propriedades no meu lambda selecionado.

Isso pode ser feito, como mencionado, com a ajuda do Reflection Emit e de uma class auxiliar incluída abaixo. O código abaixo é um trabalho em andamento, então leve para o que vale … “funciona na minha checkbox”. A class de método SelectDynamic deve ser lançada em uma class de método de extensão estática.

Como esperado, você não obterá nenhum Intellisense, pois o tipo não é criado até o tempo de execução. Funciona bem com controles de dados atrasados.

 public static IQueryable SelectDynamic(this IQueryable source, IEnumerable fieldNames) { Dictionary sourceProperties = fieldNames.ToDictionary(name => name, name => source.ElementType.GetProperty(name)); Type dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(sourceProperties.Values); ParameterExpression sourceItem = Expression.Parameter(source.ElementType, "t"); IEnumerable bindings = dynamicType.GetFields().Select(p => Expression.Bind(p, Expression.Property(sourceItem, sourceProperties[p.Name]))).OfType(); 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 builtTypes = new Dictionary(); static LinqRuntimeTypeBuilder() { moduleBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run).DefineDynamicModule(assemblyName.Name); } private static string GetTypeKey(Dictionary 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 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 fields) { return GetTypeKey(fields.ToDictionary(f => f.Name, f => f.PropertyType)); } public static Type GetDynamicType(IEnumerable fields) { return GetDynamicType(fields.ToDictionary(f => f.Name, f => f.PropertyType)); } } 

A resposta aceita é muito útil, mas eu precisava de algo um pouco mais próximo de um tipo anônimo real.

Um tipo anônimo real tem propriedades somente leitura, um construtor para preencher todos os valores, uma implementação de Equals / GetHashCode para comparar os valores de cada propriedade e uma implementação ToString que inclui o nome / valor de cada propriedade. (Consulte https://msdn.microsoft.com/en-us/library/bb397696.aspx para obter uma descrição completa dos tipos anônimos.)

Com base nessa definição de classs anônimas, eu coloco uma class que gera tipos anônimos dynamics no github em https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs . O projeto também contém alguns testes de unidade para garantir que os tipos anônimos falsos se comportem como os reais.

Aqui está um exemplo muito básico de como usá-lo:

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

Além disso, outra nota: Descobri que, ao usar um tipo anônimo dynamic com o Entity Framework, o construtor deve ser chamado com o conjunto de parâmetros “membros”. Por exemplo:

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

Se você usou uma das versões do Expression.New que não inclui o parâmetro “members”, o Entity Framework não a reconheceria como o construtor de um tipo anônimo. Então eu suponho que significa que a expressão de construtor de um tipo anônimo real includeia as informações de “membros”.

Você poderia usar o IQueryable-Extensions aqui, que é uma implementação da solução descrita por “Ethan J. Brown”:

https://github.com/thiscode/DynamicSelectExtensions

A extensão cria dinamicamente um tipo anônimo.

Então você pode fazer isso:

 var YourDynamicListOfFields = new List( "field1", "field2", [...] ) var query = query.SelectPartially(YourDynamicListOfFields); 

Eu não acredito que você será capaz de conseguir isso. Embora quando você select new { c.Name, c.Population } , parece que você não está criando uma class que você realmente é. Se você der uma olhada na saída compilada no Reflector ou na IL bruta, você poderá ver isso.

Você terá uma class que seria algo como isto:

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

(Ok, limpei um toque, já que uma propriedade é realmente apenas um conjunto de get_Name() e set_Name(name) )

O que você está tentando fazer é criar uma class dinâmica adequada, algo que não estará disponível até que o .NET 4.0 seja lançado (e mesmo assim eu não tenho certeza se será capaz de conseguir o que você quer).

Você é a melhor solução para definir as diferentes classs anônimas e, em seguida, ter algum tipo de verificação lógica para determinar qual deles criar e, para criá-lo, você pode usar o object System.Linq.Expressions.NewExpression .

Mas, pode ser (pelo menos em teoria) possível fazê-lo, se você estiver realmente empenhado no fornecedor LINQ subjacente. Se você está escrevendo seu próprio provedor LINQ, você pode detectar se a expressão atualmente analisada é uma Select, então você determina a class CompilerGenerated , reflete para seu construtor e cria.

Certamente não é uma tarefa simples, mas seria como o LINQ to SQL, o LINQ to XML, etc.

Você poderia usar uma class de parâmetro em vez de trabalhar com um tipo anônimo. No seu exemplo, você pode criar uma class de parâmetro como esta:

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

… E coloque no seu select assim:

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

O que você sai é algo do tipo IQueryable .

Isso compila, eu não sei se funciona no entanto …

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

Assumindo p é o que você está transformando, e a instrução select está retornando um tipo anon, usando a declaração de function de lambda.

Edit: Eu também não sei como você iria gerar isso dinamicamente. Mas pelo menos mostra como usar o lambda select para retornar um tipo anon com múltiplos valores

Edit2:

Você também deve ter em mente que o compilador c # na verdade gera classs estáticas do tipo anon. Portanto, o tipo anon realmente possui um tipo após o tempo de compilation. Portanto, se você gerar essas consultas em tempo de execução (o que eu suponho que seja), talvez seja necessário construir um tipo usando os vários methods de reflection (acredito que você possa usá-los para criar tipos dinamicamente) carregar os tipos criados em contexto de execução e use-os em sua saída gerada.

Eu acho que a maioria das coisas já foram respondidas – como o Slace disse, você precisa de alguma class que seria retornada do método Select . Depois de ter a class, você pode usar o método System.Linq.Expressions.NewExpression para criar a expressão.

Se você realmente quiser fazer isso, você pode gerar class em tempo de execução também. É um pouco mais trabalhoso, porque não pode ser feito usando trees de expressão LINQ, mas é possível. Você pode usar o namespace System.Reflection.Emit para fazer isso – eu fiz uma pesquisa rápida e aqui está um artigo que explica isso:

  • Introdução à criação de tipos dynamics com Reflection.Emit

Talvez um pouco atrasado, mas pode ajudar alguém.

Você pode gerar seleção dinâmica por chamada DynamicSelectGenerator em selecionar de uma entidade.

 public static Func DynamicSelectGenerator() { // 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>(xInit, xParameter); // compile to Func return lambda.Compile(); } 

E use por este código:

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

Você poderia usar a Dynamic Expression API, que permite que você construa dinamicamente sua instrução select, desta forma:

  Select("new(,,...)"); 

Você precisa do arquivo Dynamics.cs do LINQ e exemplos de linguagem para o Visual Studio para que isso funcione, ambos estão vinculados na parte inferior desta página . Você também pode ver um exemplo de trabalho mostrando isso em ação no mesmo URL.