Pergunta

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

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

Eu tenho trabalhado para fora como gerar

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

mas eu não consigo encontrar um construtor / sobrecarga que vai deixar-me especificar várias propriedades na minha selecione lambda.

Foi útil?

Solução

Isto pode ser feito, como mencionado, com a ajuda de reflexão emitir e uma classe auxiliar Eu incluí abaixo. O código abaixo é um trabalho em andamento, para levá-lo para o que vale a pena ... 'ele funciona em minha caixa'. A classe método SelectDynamic deve ser lançado em uma classe método de extensão estático.

Como esperado, você não vai obter qualquer Intellisense já que o tipo não é criado até a execução. Funciona bem em controles de dados de ligação tardia.

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

Outras dicas

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

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

Com base nessa definição de classes anônimas, eu coloquei uma classe que gera tipos anônimos dinâmicos 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 falsificados comportam-se como os reais.

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

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

Além disso, uma outra nota: Descobri que quando se utiliza um tipo anônimo dinâmica com Entity Framework, o construtor deve ser chamado com o "membros" conjunto de parâmetros. Por exemplo:

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

Se você usou uma das versões do Expression.New que não inclui o parâmetro "membros", Entity Framework não iria reconhecê-lo como o construtor de um tipo anônimo. Então eu supor que significa a expressão construtor de um tipo anônimo real seria incluem que "membros" informações.

Você pode usar IQueryable-extensões aqui, que é um implemantation da solução descrita por "Ethan J. Brown":

https://github.com/thiscode/DynamicSelectExtensions

A extensão constrói dinamicamente um tipo anônimo.

Em seguida, você pode fazer isso:

var YourDynamicListOfFields = new List<string>(

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

)
var query = query.SelectPartially(YourDynamicListOfFields);

Talvez um pouco tarde, mas pode ajudar a alguém.

Você pode gerar dinâmica escolha por DynamicSelectGenerator chamada na seleção a partir de uma entidade.

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

E uso por este código:

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

Eu não acredito que você será capaz de conseguir isso. Embora quando você faz select new { c.Name, c.Population } parece que você não está criando uma classe que realmente são. Se você tem um olhar para a saída compilada no refletor ou a IL-prima que você será capaz de ver isso.

Você vai ter uma classe que seria algo parecido com isto:

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

(Ok, eu limpa-lo um toque, uma vez que a propriedade é realmente apenas uma get_Name() e set_Name(name) método conjunto de qualquer maneira)

O que você está tentando fazer é adequada criação classe dinâmica, algo que não vai estar disponível até .NET 4.0 sai (e até então eu não tenho certeza se ele vai ser capaz de conseguir o que você quiser).

Você é a melhor solução seria a de definir as diferentes anonymous aulas e depois ter algum tipo de verificação lógica para determinar qual deles para criar, e para criá-lo você pode usar o objeto System.Linq.Expressions.NewExpression.

Mas, pode ser (pelo menos em teoria) possível fazê-lo, se você está ficando muito hard-core sobre o provedor LINQ subjacente. Se você são escrever o seu próprio provedor de LINQ você pode detectar se a expressão atualmente analisado é um Select, então você determinar a classe CompilerGenerated, refletir por seu construtor e criar.

Defiantly não é uma tarefa simples, mas seria como LINQ to SQL, LINQ to XML, etc todos fazê-lo.

Você pode usar uma classe parâmetro em vez de trabalhar com um tipo anônimo. No seu exemplo você pode criar uma classe de parâmetros como este:

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

... e colocá-lo em seu seleto como este:

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<ParamClass>.

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

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

Assumindo que p é o que a sua transformação, e a instrução select está retornando um tipo anon, usando a declaração da função de lambda do.

Edit: Eu também não sei como você iria gerar este dinamicamente. Mas pelo menos ele mostra como usar o seleto lambda retornar um tipo Anon com vários valores

Edit2:

Você também tem que ter em mente, que o compilador C # realmente gera classes estáticas do tipo Anon. Então, o tipo Anon tem realmente um tipo após o tempo de compilação. Portanto, se seu gerar essas consultas em tempo de execução (que eu suponho que você é) você pode ter que construir um tipo usando os vários métodos de reflexão (eu acredito que você pode usá-los para fazer tipos on the fly) carregar os tipos criados em contexto de execução e usá-los em sua saída gerada.

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

Se você realmente quer fazer isso, você pode gerar classe em tempo de execução também. É um pouco mais de trabalho, porque não pode ser feito usando árvores LINQ expressão, mas é possível. Você pode usar namespace System.Reflection.Emit fazer isso - eu apenas fiz uma busca rápida e aqui está um artigo que explica o seguinte:

Você pode usar o Expression API dinâmico que permite que você construa dinamicamente sua instrução SELECT como esta:

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

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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top