45 votes

Comment créer un arbre d'expression LINQ avec un type anonyme dedans

Je voudrais générer dynamiquement l'instruction select suivante à l'aide d'arbres d'expression:

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

J'ai trouvé comment générer

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

mais je n'arrive pas à trouver un constructeur / surcharge qui me permettra de spécifier plusieurs propriétés dans mon lambda sélectionné.

Toute aide appréciée. Merci.

70voto

Ethan J. Brown Points 1285

Cela peut être fait, comme mentionné, à l'aide de Reflection Emit et d'une classe d'assistance que j'ai incluse ci-dessous. Le code ci-dessous est un travail en cours, alors prenez-le pour ce qu'il vaut - `` ça marche sur ma boîte ''; 0. La classe de méthode SelectdDynamic doit être lancée dans une classe de méthode d'extension statique.

Comme prévu, vous n'obtiendrez aucun Intellisense car le type n'est pas créé avant l'exécution. Fonctionne bien sur les contrôles de données à liaison tardive.

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

1voto

Aaron Powell Points 15598

Je ne crois pas que vous serez en mesure d'atteindre cet objectif. Bien que quand vous ne select new { c.Name, c.Population } il semble que vous n'êtes pas la création d'une classe vous êtes réellement. Si vous avez un coup d'oeil à la sortie compilée dans le Réflecteur ou cru, IL vous serez en mesure de voir ça.

Vous aurez une classe qui ressemblerait à quelque chose comme ceci:

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

(Ok, j'ai nettoyé une touche, depuis un bien est vraiment juste un get_Name() et set_Name(name) méthode de jeu de toute façon)

Ce que vous essayez de faire est la bonne dynamique de la création de classes, quelque chose qui ne sera pas disponible jusqu'à ce que .NET 4.0 vient de sortir (et même alors, je ne suis pas vraiment sûr si ça va être en mesure de réaliser ce que vous voulez).

Vous êtes à la meilleure solution serait de définir les différents anonyme classes et ensuite avoir une sorte de logique de contrôle afin de déterminer celui qui à créer, et à le créer, vous pouvez utiliser l'objet System.Linq.Expressions.NewExpression.

Mais, il se peut (en théorie au moins) possible de le faire, si vous vous sentez vraiment hard-core sur le sous-jacent LINQ fournisseur. Si vous êtes de la rédaction de votre propre fournisseur LINQ vous pouvez détecter si actuellement analysé l'expression est de Sélectionner, ensuite, vous déterminez l' CompilerGenerated classe, réfléchir pour son constructeur et de créer.

Défi n'est pas une tâche simple, mais il serait comment LINQ to SQL, LINQ to XML, etc tous le faire.

1voto

Spoike Points 32082

Vous pouvez utiliser une classe de paramètres au lieu de travailler avec un type anonyme. Dans votre exemple, vous pouvez créer une classe de paramètres comme celle-ci:

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

… Et mettez-le dans votre sélection comme ceci:

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

Ce que vous sortez est quelque chose du type IQueryable<ParamClass> .

1voto

Sekhat Points 2555

Cette compile, je sais pas si il fonctionne cependant...

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

En supposant que p est ce que votre transformation, et l'instruction select retourne un anon type, à l'aide de la déclaration de la fonction de lambda.

Edit: aussi, je ne sais pas comment vous permettrait de générer dynamiquement. Mais au moins, il vous montre comment utiliser la sélection lambda de retour d'un anon type avec plusieurs valeurs

Edit2:

Vous devez également avoir à garder à l'esprit, que le compilateur c# génère les classes statiques de la anon type. Si l'anon type n'ont en fait un type après le moment de la compilation. Donc, si votre production de ces requêtes au moment de l'exécution (qui je suppose que vous êtes), vous pourriez être la construction d'un type à l'aide de diverses méthodes de réflexion (je crois que vous pouvez les utiliser pour faire des types à la volée) charge de la création de types dans le contexte d'exécution et les utiliser dans la sortie générée.

1voto

Tomas Petricek Points 118959

Je pense que la plupart des choses sont déjà répondu - comme Slace dit, vous avez besoin d'un peu de classe qui serait retourné à partir de l' Select méthode. Une fois que vous avez la classe, vous pouvez utiliser l' System.Linq.Expressions.NewExpression méthode pour créer l'expression.

Si vous voulez vraiment pour ce faire, vous pouvez générer de la classe au moment de l'exécution trop. C'est un peu plus de travail, car il ne peut pas être fait à l'aide de LINQ des arbres d'Expression, mais c'est possible. Vous pouvez utiliser System.Reflection.Emit d'espace de noms pour ce faire, j'ai juste fait une recherche rapide et voici un article qui explique cela:

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X