2009-11-06 11 views
53

Jaki jest najprostszy sposób kodowania właściwości w języku C#, gdy mam nazwę właściwości jako ciąg? Na przykład chcę zezwolić użytkownikowi na zamówienie niektórych wyników wyszukiwania według wybranej przez siebie właściwości (przy użyciu LINQ). Wybiorą właściwość "order by" w interfejsie użytkownika - oczywiście jako wartość ciągu. Czy istnieje sposób użycia tego ciągu bezpośrednio jako właściwość zapytania linq, bez konieczności użycia logiki warunkowej (jeśli/else, switch) do odwzorowania łańcuchów na właściwości. Odbicie?C# - kod do zamówienia przez właściwość używającą nazwy właściwości w postaci ciągu znaków

Logicznie rzecz biorąc, jest to, co chciałbym zrobić:

query = query.OrderBy(x => x."ProductId"); 

Aktualizacja: Nie pierwotnie określić, że używam Linq do podmiotów - wydaje się, że odbicie (przynajmniej GetProperty, Metoda GetValue) nie przekłada się na L2E.

+0

Myślę, że musiałbyś użyć odbicia, i nie jestem pewien, czy możesz użyć odbicia w wyrażeniu lambda ... no, prawie na pewno nie w Linq na SQL, ale może podczas używania Linq na liście lub coś. – CodeRedick

+0

@Telos: Nie ma powodu, dla którego nie można używać odbicia (ani żadnego innego API) w lambda. To, czy zadziała, czy kod zostanie oceniony jako wyrażenie i przetłumaczone na coś innego (jak LINQ-SQL, jak sugerujesz), jest zupełnie inne. –

+0

Dlatego wysłałem komentarz zamiast odpowiedzi. ;) Najczęściej używane do Linq2SQL ... – CodeRedick

Odpowiedz

77

Oferuję tę alternatywę dla tego, co napisali inni.

System.Reflection.PropertyInfo prop = typeof(YourType).GetProperty("PropertyName"); 

query = query.OrderBy(x => prop.GetValue(x, null)); 

Pozwala to uniknąć powtarzających się połączeń z interfejsem API do refleksji w celu uzyskania właściwości. Teraz jedynym powtarzającym się wezwaniem jest uzyskanie wartości.

Jednak

bym opowiadają za pomocą PropertyDescriptor zamiast, jak to pozwoli na zwyczaju TypeDescriptor e być przypisany do danego typu, dzięki czemu można mieć lekkie operacje pobierania właściwości i wartości. W przypadku braku niestandardowego deskryptora i tak wróci do refleksji.

PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(YourType)).Find("PropertyName"); 

query = query.OrderBy(x => prop.GetValue(x)); 

chodzi o przyspieszenie go, sprawdź Marc żwiru za HyperDescriptor projekt na CodeProject. Używałem tego z wielkim sukcesem; to oszczędność czasu dla wydajnego powiązania danych i dynamicznych operacji na obiektach biznesowych.

+0

dobry punkt. musicie być ostrożni z tymi lambdami. – dkackman

+0

Należy zauważyć, że odbite odwołanie (tj. GetValue) jest najbardziej kosztowną częścią refleksji. Pobieranie metadanych (np.GetProperty) jest faktycznie mniej kosztowne (o rząd wielkości), więc przez buforowanie tej części, tak naprawdę nie oszczędzasz sobie tak bardzo. To wszystko będzie kosztować mniej więcej tyle samo, a koszt będzie ciężki. Po prostu coś do zapamiętania. – jrista

+1

@jrista: inwokacja jest najbardziej kosztowna, aby mieć pewność. Jednak "mniej kosztowne" nie oznacza "darmowy", a nawet bliski. Odtworzenie metadanych zajmuje nietrywialną ilość czasu, więc zaletą jest buforowanie go i brak * wady * (chyba że czegoś tu brakuje). W rzeczywistości tak naprawdę powinno się używać 'PropertyDescriptor' tak czy inaczej (w celu uwzględnienia niestandardowych deskryptorów typów, które * mogą * przywracać wartości do lekkiej operacji). –

11

Tak, nie sądzę, że jest inny sposób niż odbicie.

Przykład:

query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null)); 
2

Odbicie jest odpowiedź!

typeof(YourType).GetProperty("ProductId").GetValue(theInstance); 

Jest wiele rzeczy, które można zrobić, aby buforować odbitego PropertyInfo, sprawdź złych strun, wyraź swoją funkcję porównawczą zapytań, itp, ale w jego sercu, to co robisz.

5
query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null)); 

Próbuję przywołać dokładną składnię z mojej głowy, ale myślę, że to prawda.

3

Możesz użyć dynamicznego Linq - sprawdź blog this.

Również sprawdzić this StackOverflow post ...

+0

To jest najlepsza odpowiedź dla mnie – Demodave

37

Jestem trochę późno na imprezę, jednak mam nadzieję, że może to być pomocne.

Problem z użyciem refleksji jest taki, że wynikowe drzewo ekspresji prawie na pewno nie będzie obsługiwane przez żadnych dostawców Linq innych niż wewnętrzny dostawca .Net.Jest to dobre dla wewnętrznych kolekcji, ale nie będzie działać, gdy sortowanie ma być wykonywane u źródła (czy to SQL, MongoDb itp.) Przed paginacją. poniżej

Próbka kodu zapewnia IQueryable metod EXTENTION dla OrderBy i OrderByDescending, i mogą być stosowane w taki sposób:

query = query.OrderBy("ProductId"); 

Extension Metoda:

public static class IQueryableExtensions 
{ 
    public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName) 
    { 
     return source.OrderBy(ToLambda<T>(propertyName)); 
    } 

    public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string propertyName) 
    { 
     return source.OrderByDescending(ToLambda<T>(propertyName)); 
    } 

    private static Expression<Func<T, object>> ToLambda<T>(string propertyName) 
    { 
     var parameter = Expression.Parameter(typeof(T)); 
     var property = Expression.Property(parameter, propertyName); 
     var propAsObject = Expression.Convert(property, typeof(object)); 

     return Expression.Lambda<Func<T, object>>(propAsObject, parameter);    
    } 
} 

Regards, Mark.

+6

głosuj za rozwiązaniem, które działa dla EF – icesar

+0

Doskonałe rozwiązanie - właśnie tego szukałem. Naprawdę muszę zagłębić się w drzewa Wyrażeń. Wciąż bardzo nowicjusz. @Mark, jakiekolwiek rozwiązanie do wyrażeń zagnieżdżonych? Powiedzmy, że mam typ T z właściwością "Sub" typu TSub, który sam ma właściwość "Wartość". Teraz chciałbym uzyskać wyrażenie Expression > dla ciągu "Sub.Value". –

+4

Dlaczego potrzebujemy 'Expression.Convert' do zamiany' property' na 'object'? Otrzymuję "Nie można rzucić typu" System.String ", aby wpisać" System.Object ". LINQ to Entities obsługuje tylko rzucanie prymitywów EDM lub typów wyliczeniowych EDM, a usunięcie go wydaje się działać. – ShuberFu

9

Podobała mi się odpowiedź od @Mark Powell, ale jako @ShuberFu powiedział, daje błąd LINQ to Entities only supports casting EDM primitive or enumeration types.

Usunięcie var propAsObject = Expression.Convert(property, typeof(object)); nie działało z właściwościami, które były typami wartości, na przykład liczbą całkowitą, ponieważ nie wstawiałby niejawnie int do obiektu.

Korzystanie z pomysłów od Kristofer Andersson i Marc Gravell Znalazłem sposób na skonstruowanie funkcji Queryable za pomocą nazwy właściwości i nadal działa z Entity Framework. Dodałem również opcjonalny parametr IComparer. Ostrzeżenie: Parametr IComparer nie działa z Entity Framework i powinien zostać pominięty, jeśli używa się Linq do Sql.

następujące prace z Entity Framework i LINQ to SQL:

query = query.OrderBy("ProductId"); 

I @Simon Scheurer Działa to również:

query = query.OrderBy("ProductCategory.CategoryId"); 

A jeśli nie używają Entity Framework lub LINQ do SQL to działa :

query = query.OrderBy("ProductCategory", comparer); 

Oto kod:

public static class IQueryableExtensions 
{  
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> query, string propertyName, IComparer<object> comparer = null) 
{ 
    return CallOrderedQueryable(query, "OrderBy", propertyName, comparer); 
} 

public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> query, string propertyName, IComparer<object> comparer = null) 
{ 
    return CallOrderedQueryable(query, "OrderByDescending", propertyName, comparer); 
} 

public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> query, string propertyName, IComparer<object> comparer = null) 
{ 
    return CallOrderedQueryable(query, "ThenBy", propertyName, comparer); 
} 

public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> query, string propertyName, IComparer<object> comparer = null) 
{ 
    return CallOrderedQueryable(query, "ThenByDescending", propertyName, comparer); 
} 

/// <summary> 
/// Builds the Queryable functions using a TSource property name. 
/// </summary> 
public static IOrderedQueryable<T> CallOrderedQueryable<T>(this IQueryable<T> query, string methodName, string propertyName, 
     IComparer<object> comparer = null) 
{ 
    var param = Expression.Parameter(typeof(T), "x"); 

    var body = propertyName.Split('.').Aggregate<string, Expression>(param, Expression.PropertyOrField); 

    return comparer != null 
     ? (IOrderedQueryable<T>)query.Provider.CreateQuery(
      Expression.Call(
       typeof(Queryable), 
       methodName, 
       new[] { typeof(T), body.Type }, 
       query.Expression, 
       Expression.Lambda(body, param), 
       Expression.Constant(comparer) 
      ) 
     ) 
     : (IOrderedQueryable<T>)query.Provider.CreateQuery(
      Expression.Call(
       typeof(Queryable), 
       methodName, 
       new[] { typeof(T), body.Type }, 
       query.Expression, 
       Expression.Lambda(body, param) 
      ) 
     ); 
} 
} 
+0

Doskonała odpowiedź, żałuję, że nie mogłem cię dwa razy wygrać – Anduril

1

bardziej produktywne niż refleksji rozszerzenie dynamicznych elementów zamówienia:

public static class DynamicExtentions 
{ 
    public static object GetPropertyDynamic<Tobj>(this Tobj self, string propertyName) where Tobj : class 
    { 
     var param = Expression.Parameter(typeof(Tobj), "value"); 
     var getter = Expression.Property(param, propertyName); 
     var boxer = Expression.TypeAs(getter, typeof(object)); 
     var getPropValue = Expression.Lambda<Func<Tobj, object>>(boxer, param).Compile();    
     return getPropValue(self); 
    } 
} 

Przykład:

var ordered = items.OrderBy(x => x.GetPropertyDynamic("ProductId")); 
0

także Dynamic Expressions może rozwiązać ten problem. Można używać zapytań opartych na łańcuchach za pośrednictwem wyrażeń LINQ, które mogły być dynamicznie konstruowane w czasie wykonywania.

Powiązane problemy