Frage

Ich möchte die folgende select-Anweisung dynamisch mit Ausdruck Bäume generieren:

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

Ich habe gearbeitet, wie zu generieren

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

, aber ich kann nicht scheinen, einen Konstruktor / Überlast zu finden, die ich mehrere Eigenschaften in meinem wählt Lambda angeben lassen.

War es hilfreich?

Lösung

Dies kann getan werden, wie erwähnt, mit Hilfe von Reflection Emit und einer Hilfsklasse I unten aufgeführt habe. Der folgende Code ist ein work in progress, es so nehmen für das, was es wert ist ... ‚es funktioniert auf meiner Box‘. Die SelectDynamic Methode Klasse sollte in einer statischen Erweiterungsmethode Klasse geworfen werden.

Wie erwartet, werden Sie keine Intellisense bekommen, da der Typ nicht erst zur Laufzeit erstellt wird. Funktioniert gut auf spät gebundenen Datenkontrollen.

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

Andere Tipps

Die akzeptierte Antwort ist sehr nützlich, aber ich brauchte etwas, ein wenig näher an einen echten anonymen Typ.

Ein echter anonymer Typ hat schreibgeschützte Eigenschaften, einen Konstruktor für in allen Werten füllen, eine Implementierung von Equals / GetHashCode für die Werte der Eigenschaften zu vergleichen und eine Implementierung ToString, die den Namen / Wert jeder Eigenschaft enthält . (Siehe https://msdn.microsoft.com/en-us/library/bb397696 aspx für eine vollständige Beschreibung der anonymen Typen.)

Auf der Grundlage dieser Definition von anonymen Klassen, habe ich eine Klasse, die unter https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs . Das Projekt hat auch einige Unit-Tests enthält sicher, dass die gefälschten anonymen Typen wie echte verhalten zu machen.

Hier ist ein sehr einfaches Beispiel dafür, wie es zu benutzen:

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

Auch eine andere Anmerkung: Ich fand, dass, wenn einen dynamischen anonymen Typen mit Entity Framework verwendet, muss der Konstrukteur mit dem „Mitgliedern“ Parametersatz aufgerufen werden. Zum Beispiel:

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

Wenn Sie eine der Versionen von Expression.New verwendet, die nicht die „Mitglieder“ Parameter enthält, würde Entity Framework erkennen sie nicht als Konstruktor eines anonymen Typs. So nehme ich an, dass bedeutet einen echter Konstruktor Ausdruck über den anonymen Typs würde, dass „Mitglieder“ Informationen.

Sie könnten die IQueryable-Extensions hier verwendet werden, die eine UMSETZEN der Lösung von "Ethan J. Brown" beschrieben ist:

https://github.com/thiscode/DynamicSelectExtensions

Die Erweiterung baut dynamisch einen anonymen Typ.

Dann können Sie dies tun:

var YourDynamicListOfFields = new List<string>(

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

)
var query = query.SelectPartially(YourDynamicListOfFields);

Vielleicht ein bisschen spät, aber kann jemand helfen.

Sie können durch Aufruf DynamicSelectGenerator in ausgewählten von einer Entität dynamische Auswahl erzeugen.

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

Und Verwendung von diesem Code:

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

Ich glaube nicht, dass Sie in der Lage sein werden, dies zu erreichen. Obwohl, wenn Sie select new { c.Name, c.Population } tun wie es scheint, dass Sie nicht eine Klasse erstellen Sie tatsächlich sind. Wenn Sie einen Blick auf die kompilierte Ausgabe in Reflector oder rohen IL haben Sie in der Lage sein, dies zu sehen.

Sie werden eine Klasse, die in etwa so aussehen:

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

(Ok, ich gereinigt es einen Hauch auf, da eine Eigenschaft ist wirklich nur ein get_Name() und set_Name(name) Verfahren eingestellt sowieso)

Was Sie versuchen, ist die richtige dynamische Klasse Schöpfung zu tun, etwas, das wird nicht verfügbar sein, bis .NET 4.0 kommt (und selbst dann sicher, dass ich nicht wirklich, wenn es in der Lage sein zu erreichen, was Sie wollen).

Sie sind beste Lösung wäre, die verschiedenen anonym Klassen zu definieren und dann eine Art logischer Kontrolle hat, um zu bestimmen, welche zu erstellen, und es erstellen Sie das Objekt System.Linq.Expressions.NewExpression verwenden können.

Aber kann es (zumindest theoretisch) möglich sein, es zu tun, wenn Sie wirklich harten Kern über den zugrunde liegenden LINQ-Anbieter zu bekommen. Wenn Sie sind Ihre eigenen LINQ-Anbieter schreiben können Sie erkennen, ob die gegenwärtig analysiert Ausdruck ein Select ist, dann bestimmen Sie die CompilerGenerated Klasse, für den Konstruktor reflektieren und erstellen.

Trotzig keine einfache Aufgabe, aber es wäre, wie LINQ to SQL, LINQ to XML, etc alle tun es.

Sie können eine Parameter-Klasse verwenden, anstatt mit einer anonymen Art des Arbeitens. In Ihrem Beispiel können Sie einen Parameter Klasse wie folgt erstellen:

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

... und legen Sie sie in Ihr wählen wie folgt aus:

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

Was man raus ist etwas von der Art IQueryable<ParamClass>.

Dies kompiliert, ich weiß nicht, ob es funktioniert aber ...

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

Unter der Annahme, p ist, was Ihre Transformation, und die select-Anweisung einen anon Typen zurückgibt, die Funktionsdeklaration von LAMBDA verwendet wird.

Edit: Ich weiß auch nicht, wie Sie diese dynamisch erzeugen würden. Aber zumindest zeigt es Ihnen, wie Sie die Auswahl Lambda verwenden, um ein Anon Typ mit mehreren Werten zurückzukehren

Edit2:

Sie müßten auch daran tragen, dass die c # Compiler generiert tatsächlich statische Klassen des anon Typs. So ist der anon Typ hat tatsächlich eine Art nach der Kompilierung. Also, wenn Sie diese Abfragen zur Laufzeit zu erzeugen (was ich nehme an, Sie sind) müssen Sie eine Art mit den verschiedenen Reflexionsmethoden konstruieren können (ich glaube, sie nutzen zu können Arten on the fly zu machen) laden die erstellten Typen in Ausführungskontext und verwenden sie sie in Ihrer erzeugten Ausgabe.

Ich denke, die meisten Dinge schon beantwortet sind - wie Slace gesagt, Sie brauchen eine Klasse, die von der Select Methode zurückgegeben werden würde. Nachdem Sie die Klasse haben, können Sie die System.Linq.Expressions.NewExpression Methode verwenden, um den Ausdruck zu erstellen.

Wenn Sie das wirklich tun möchten, können Sie Klasse zur Laufzeit erzeugen. Es ist ein bisschen mehr Arbeit, weil es nicht getan werden kann, LINQ Expression Bäume verwenden, aber es ist möglich. Sie können System.Reflection.Emit Namespace zu tun verwenden - ich habe gerade eine schnelle Suche und hier ist ein Artikel, der dies erklärt:

Sie können das Dynamic Expression API verwenden, die Sie dynamisch bauen Ihre select-Anweisung wie folgt ermöglicht:

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

Sie müssen die Dynamics.cs von den LINQ und Sprachbeispiele für Visual Studio-Datei für diese zu arbeiten, sowohl im unteren Bereich verbunden sind, diese Seite . Sie können auch ein funktionsfähiges Beispiel zeigt dies in Aktion zur gleichen URL sehen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top