So erstellen Sie LINQ Expression Tree zum Auswählen eines anonymen Typs

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

Frage

Ich möchte die folgende select-Anweisung dynamisch mit Ausdrucksbäumen erzeugen:

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

Ich habe herausgefunden, wie man generiert

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

aber ich kann nicht scheinen, einen Konstruktor / eine Überladung zu finden, die mich multiple Eigenschaften in meinem vorgewählten Lambda spezifizieren lässt.

Akzeptierte Antwort

Dies kann, wie erwähnt, mit Hilfe von Reflection Emit und einer Hilfsklasse, die ich unten eingefügt habe, geschehen. Der folgende Code ist ein work in progress, also nimm es für das, was es wert ist ... "es funktioniert auf meiner Box". Die SelectDynamic-Methodenklasse sollte in einer statischen Erweiterungsmethode geworfen werden.

Wie erwartet erhalten Sie kein Intellisense, da der Typ erst zur Laufzeit erstellt wird. Funktioniert gut für spät gebundene Datensteuerelemente.

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

Beliebte Antwort

Die angenommene Antwort ist sehr nützlich, aber ich brauchte etwas näher an einem echten anonymen Typ.

Ein echter anonymer Typ hat schreibgeschützte Eigenschaften, einen Konstruktor zum Ausfüllen aller Werte, eine Implementierung von Equals / GetHashCode zum Vergleichen der Werte jeder Eigenschaft und eine ToString-Implementierung, die den Namen / Wert jeder Eigenschaft enthält. (Eine vollständige Beschreibung der anonymen Typen finden Sie unter https://msdn.microsoft.com/en-us/library/bb397696.aspx .)

Basierend auf dieser Definition von anonymen Klassen habe ich eine Klasse erstellt, die dynamische anonyme Typen auf github unter https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs generiert. Das Projekt enthält auch einige Komponententests, um sicherzustellen, dass die gefälschten anonymen Typen sich wie echte verhalten.

Hier ist ein sehr einfaches Beispiel, wie man es benutzt:

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

Noch ein Hinweis: Ich habe festgestellt, dass bei Verwendung eines dynamischen anonymen Typs mit Entity Framework der Konstruktor mit dem Parametersatz "members" aufgerufen werden muss. Beispielsweise:

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

Wenn Sie eine der Versionen von Expression.New verwendet haben, die den Parameter "members" nicht enthält, würde Entity Framework sie nicht als Konstruktor eines anonymen Typs erkennen. Ich nehme also an, dass ein Konstruktorausdruck eines echten anonymen Typs diese "Mitglieder" -Information enthält.




Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum