2013-05-13 12 views
37

Betrachten wir diese Klasse haben:LINQ: Dynamisch wählen

public class Data 
{ 
    public string Field1 { get; set; } 
    public string Field2 { get; set; } 
    public string Field3 { get; set; } 
    public string Field4 { get; set; } 
    public string Field5 { get; set; } 

} 

Wie wähle ich dynamisch für Spalten angeben? so etwas wie dieses:

var list = new List<Data>(); 

    var result= list.Select("Field1,Field2"); // How ? 

Ist dies die einzige Lösung =>Dynamic LINQ?
Ausgewählte Felder sind zur Kompilierzeit nicht bekannt. Sie würden zur Laufzeit

+0

Ja ist es, wenn Ihre Filtereingabe Zeichenfolge ist –

+0

@CuongLe Wie? Jedes Codeprobe oder Link wäre großartig. – Unforgiven

+1

Was soll der Typ von 'result' sein? –

Antwort

47

Sie können dies tun, indem dynamisch die Lambda Erstellen Sie passieren zu Select:

Func<Data,Data> CreateNewStatement(string fields) 
{ 
    // input parameter "o" 
    var xParameter = Expression.Parameter(typeof(Data), "o"); 

    // new statement "new Data()" 
    var xNew = Expression.New(typeof(Data)); 

    // create initializers 
    var bindings = fields.Split(',').Select(o => o.Trim()) 
     .Select(o => { 

      // property "Field1" 
      var mi = typeof(Data).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<Data,Data>>(xInit, xParameter); 

    // compile to Func<Data, Data> 
    return lambda.Compile(); 
} 

Dann können Sie es wie folgt verwenden:

var result = list.Select(CreateNewStatement("Field1, Field2")); 
+1

Während dies für direkte Mitglieder funktioniert, funktioniert es nicht für geschachtelte Werte. Zum Beispiel: Ich habe einen Elternteil, der ein Kindobjekt hat, und ich möchte child.name von Eltern auswählen. Ich spiele mit dem Parsen der Eingabezeichenfolge für Perioden und das Verschachteln von Aufrufen von getproperty. – Dave

+2

Welcher Namespace ist Daten in von (Func )? –

+7

Ich empfehle, es als 'Func CreateNewStatement (Zeichenfolge Felder)' zu definieren, ersetzen Sie innerhalb der Funktion jedes Vorkommen von 'Data' durch' T' und verwenden Sie als 'var result = list.Select (CreateNewStatement (" Field1, Field2 "));'. Dies erlaubt es für jede Entität zu verwenden, nicht nur für "Daten". – Matt

-7
var result = from g in list.AsEnumerable() 
       select new {F1 = g.Field1,F2 = g.Field2}; 
+6

Dies ist nicht dynamisch –

+0

@ThomasLevesque was dynamische bedeutet hier –

+3

Es bedeutet, dass die ausgewählten Felder zur Kompilierzeit nicht bekannt sind. Sie würden zur Laufzeit spezifiziert werden, z.B. Verwenden einer Zeichenfolge –

4

Mit Reflexion und Ausdruck bulid kann tun, was Sie sagen. Beispiel:

var list = new List<Data>(); 
//bulid a expression tree to create a paramter 
ParameterExpression param = Expression.Parameter(typeof(Data), "d"); 
//bulid expression tree:data.Field1 
Expression selector = Expression.Property(param,typeof(Data).GetProperty("Field1")); 
Expression pred = Expression.Lambda(selector, param); 
//bulid expression tree:Select(d=>d.Field1) 
Expression expr = Expression.Call(typeof(Queryable), "Select", 
    new Type[] { typeof(Data), typeof(string) }, 
    Expression.Constant(list.AsQueryable()), pred); 
//create dynamic query 
IQueryable<string> query = list.AsQueryable().Provider.CreateQuery<string>(expr); 
var result=query.ToList(); 
4

Sie Reflexion verwenden müssen, um Erhalte und setze den Eigenschaftswert mit seinem Namen.

var result = new List<Data>(); 
    var data = new Data(); 
    var type = data.GetType(); 
    var fieldName = "Something"; 

    for (var i = 0; i < list.Count; i++) 
    { 
     foreach (var property in data.GetType().GetProperties()) 
     { 
     if (property.Name == fieldName) 
     { 
      type.GetProperties().FirstOrDefault(n => n.Name == property.Name).SetValue(data, GetPropValue(list[i], property.Name), null); 
      result.Add(data); 
     } 
     } 
    } 

Und hier ist GetPropValue() -Methode

public static object GetPropValue(object src, string propName) 
{ 
    return src.GetType().GetProperty(propName).GetValue(src, null); 
} 
1

Neben Nicholas Butler und den Hinweis in Kommentar von Matt (diese Verwendung T für Art der Eingangsklasse), ich Nicholas eine Verbesserung setzen antworten Sie, dass die Eigenschaft der Entität dynamisch generiert wird und dass die Funktion duos field nicht als Parameter senden muss.

Zur Verwendung hinzufügen Klasse als Schlag:

public static class Helpers 
{ 
    public static Func<T, T> DynamicSelectGenerator<T>(string Fields = "") 
    { 
     string[] EntityFields; 
     if (Fields == "") 
      // get Properties of the T 
      EntityFields = typeof(T).GetProperties().Select(propertyInfo => propertyInfo.Name).ToArray(); 
     else 
      EntityFields = Fields.Split(','); 

     // input parameter "o" 
     var xParameter = Expression.Parameter(typeof(T), "o"); 

     // new statement "new Data()" 
     var xNew = Expression.New(typeof(T)); 

     // create initializers 
     var bindings = EntityFields.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(); 
    } 
} 

Die DynamicSelectGenerator Methode Einheit mit Typ T diese Methode hat optionale Eingabeparameter erhalten Fields, dass, wenn Sie von Unternehmen Spezialgebiet slect wollen als String senden wie "Field1, Field2" und wenn Sie etwas zu methid nicht senden, es alle Felder der Entität zurückgeben, können Sie diese Methode als unten verwenden:

using (AppDbContext db = new AppDbContext()) 
      { 
       //select "Field1, Field2" from entity 
       var result = db.SampleEntity.Select(Helpers.DynamicSelectGenerator<SampleEntity>("Field1, Field2")).ToList(); 

       //select all field from entity 
       var result1 = db.SampleEntity.Select(Helpers.DynamicSelectGenerator<SampleEntity>()).ToList(); 
      } 

(Angenommen, Sie einehabenmit Namen AppDbContext und Rahmen hat eine Einheit mit dem Namen SampleEntity)

+0

Bitte fügen Sie Ihrer Antwort ein kleines Anwendungsbeispiel hinzu. – Matt

+0

@Matt Ich bearbeite die Antwort und verbessere das durch Beispiel –

+0

@Matt und es ist besser, wenn jemand eine Überschreibung der 'Select' Methode durch diesen Code implementieren könnte –

0

Ein weiterer Ansatz, den ich verwendet habe, ein verschachtelter ternärer Operator ist:

string col = "Column3"; 
var query = table.Select(i => col == "Column1" ? i.Column1 : 
           col == "Column2" ? i.Column2 : 
           col == "Column3" ? i.Column3 : 
           col == "Column4" ? i.Column4 : 
           null); 

Der ternäre Operator, dass jedes Feld die gleiche Art, sein erfordert so Sie müssen .ToString() für alle Nicht-String-Spalten aufrufen.