Comment créer un arbre d'expression LINQ pour sélectionner un type anonyme

c# entity-framework expression-trees linq linq-to-entities

Question

J'aimerais générer l'instruction select suivante de manière dynamique en utilisant des arbres d'expression:

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

J'ai travaillé comment générer

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

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

Réponse acceptée

Cela peut être fait, comme mentionné, à l'aide de Reflection Emit et d'un cours d'aide que j'ai inclus 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));
    }
}

Réponse populaire

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 qui inclut 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, j'ai créé une classe générant des types anonymes dynamiques sur github à l' adresse 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 }
});

Autre remarque: j'ai constaté que lors de l'utilisation d'un type anonyme dynamique avec Entity Framework, le constructeur doit être appelé avec le jeu de paramètres "membres". Par exemple:

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

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




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi