Question

Je souhaite générer l'instruction select suivante de manière dynamique à l'aide d'arbres d'expression:

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

J'ai découvert 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 permette de spécifier plusieurs propriétés dans mon lambda sélectionné.

Était-ce utile?

La solution

Ceci peut être réalisé, comme indiqué, à l’aide de Reflection Emit et d’une classe d’aide 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". La classe de méthode SelectDynamic doit être jetée dans une classe de méthode d’extension statique.

Comme prévu, vous n’obtiendrez pas d’Intellisense car le type n’a pas été 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));
    }
}

Autres conseils

La réponse acceptée est très utile, mais il me fallait quelque chose d'un peu plus proche d'un vrai type anonyme.

Un type anonyme réel a des propriétés en lecture seule, un constructeur pour renseigner toutes les valeurs, une implémentation de Equals / GetHashCode pour comparer les valeurs de chaque propriété et une implémentation ToString incluant le nom / la valeur de chaque propriété. . (Voir https://msdn.microsoft.com/en-us/library/bb397696 .aspx pour une description complète des types anonymes.)

Sur la base de cette définition des classes anonymes, je mets une classe qui génère des types anonymes dynamiques sur github à https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs . Le projet contient également des tests unitaires pour vérifier que les faux types anonymes se comportent comme des vrais.

Voici un exemple très basique d'utilisation:

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

En outre, une autre remarque: j'ai constaté que, lorsque vous utilisez un type anonyme dynamique avec Entity Framework, le constructeur doit être appelé avec les "membres". jeu de paramètres. Par exemple:

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

Si vous avez utilisé l'une des versions d'Expression.New n'incluant pas les "membres" paramètre, Entity Framework ne le reconnaîtrait pas comme le constructeur d’un type anonyme. Donc, je suppose que cela signifie que l'expression du constructeur d'un type de type anonyme réel inclurait que "membres" informations.

Vous pouvez utiliser ici les extensions IQueryable, qui sont une implémentation de la solution décrite par "Ethan J. Brown":

https://github.com/thiscode/DynamicSelectExtensions

L’extension construit dynamiquement un type anonyme.

Ensuite, vous pouvez faire ceci:

var YourDynamicListOfFields = new List<string>(

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

)
var query = query.SelectPartially(YourDynamicListOfFields);

Peut-être un peu tard mais peut aider quelqu'un.

Vous pouvez générer une sélection dynamique par appel DynamicSelectGenerator dans select à partir d'une entité.

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

Et utiliser par ce code:

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

Je ne crois pas que vous puissiez y parvenir. Bien que lorsque vous sélectionniez new {c.Name, c.Population} , il semble que vous ne créez pas une classe que vous êtes réellement. Si vous regardez la sortie compilée dans Reflector ou le IL brut, vous pourrez le voir.

Vous aurez une classe qui ressemblerait à ceci:

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

(Ok, j'ai nettoyé une touche, puisqu'un propriété est vraiment juste une méthode get_Name () et set_Name (nom) de toute façon)

Ce que vous essayez de faire est de créer une classe dynamique appropriée, ce qui ne sera pas disponible avant la sortie de .NET 4.0 (et même dans ce cas, je ne suis pas vraiment sûr de pouvoir réaliser ce que vous voulez).

La meilleure solution serait de définir les différentes classes anonymes , puis de procéder à une vérification logique pour déterminer celle à créer et de le créer, vous pouvez utiliser l'objet . System.Linq.Expressions.NewExpression .

Mais, il peut être (en théorie du moins) possible de le faire, si vous devenez très dur avec le fournisseur LINQ sous-jacent. Si vous écrivez votre propre fournisseur LINQ, vous pouvez détecter si l'expression actuellement analysée est une sélection. Vous déterminez ensuite la classe CompilerGenerated , vous réfléchissez pour son constructeur et vous créez. / p>

Ce n’est certes pas une tâche simple, mais c’est comme cela que LINQ to SQL, LINQ to XML, etc. le fera.

Vous pouvez utiliser une classe de paramètre 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 obtenez est du type IQueryable < ParamClass > .

Ceci compile, je ne sais pas si cela fonctionne cependant ...

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

En supposant que p correspond à votre transformation et que l'instruction select renvoie un type anonyme, à l'aide de la déclaration de fonction de lambda.

Edit: Je ne sais pas non plus comment vous généreriez ceci dynamiquement. Mais au moins, il vous montre comment utiliser la sélection lambda pour renvoyer un type anonyme avec plusieurs valeurs

Modifier2:

Vous devez également vous rappeler que le compilateur c # génère en fait des classes statiques du type anon. Donc, le type anon a effectivement un type après la compilation. Donc, si vous générez ces requêtes au moment de l’exécution (ce que je suppose vous êtes), vous devrez peut-être construire un type en utilisant les différentes méthodes de réflexion (vous pouvez les utiliser pour créer des types à la volée), chargez les types créés dans le contexte d’exécution et utilisez-les dans votre sortie générée.

Je pense que la plupart des problèmes ont déjà été résolus - comme l'a dit Slace, vous avez besoin d'une classe qui serait renvoyée par la méthode Select . Une fois que vous avez la classe, vous pouvez utiliser la méthode System.Linq.Expressions.NewExpression pour créer l'expression.

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

Vous pouvez utiliser l'API d'expressions dynamiques qui vous permet de construire dynamiquement votre instruction select comme suit:

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

Pour que cela fonctionne, vous avez besoin du fichier Dynamics.cs du LINQ et des exemples de langage pour Visual Studio. Les deux liens sont liés au bas de cette page . Vous pouvez également voir un exemple de travail montrant cela en action sur la même URL.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top