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 uporządkowanie niektórych wyników wyszukiwania według wybranej właściwości (za pomocą LINQ). W interfejsie użytkownika wybiorą właściwość „order by” - oczywiście jako ciąg znaków. Czy istnieje sposób na użycie tego ciągu bezpośrednio jako właściwości zapytania linq, bez konieczności używania logiki warunkowej (if / else, switch) do mapowania ciągów na właściwości? Odbicie?
Logicznie rzecz biorąc, chciałbym to zrobić:
query = query.OrderBy(x => x."ProductId");
Aktualizacja: początkowo nie określiłem, że używam Linq do Entities - wydaje się, że odbicie (przynajmniej podejście GetProperty, GetValue) nie przekłada się na L2E.
c#
.net
linq
linq-to-entities
Jeremy
źródło
źródło
Odpowiedzi:
Oferowałbym tę alternatywę dla tego, co opublikowali wszyscy inni.
System.Reflection.PropertyInfo prop = typeof(YourType).GetProperty("PropertyName"); query = query.OrderBy(x => prop.GetValue(x, null));
Pozwala to uniknąć powtarzających się wywołań interfejsu API odbicia w celu uzyskania właściwości. Teraz jedynym powtarzanym wezwaniem jest uzyskanie wartości.
jednak
Zalecałbym użycie
PropertyDescriptor
zamiast tego, ponieważ pozwoliTypeDescriptor
to na przypisanie niestandardowych s do twojego typu, umożliwiając lekkie operacje pobierania właściwości i wartości. W przypadku braku niestandardowego deskryptora i tak powróci do refleksji.PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(YourType)).Find("PropertyName"); query = query.OrderBy(x => prop.GetValue(x));
Jeśli chodzi o przyspieszenie, sprawdź
HyperDescriptor
projekt Marca Gravela w CodeProject. Użyłem tego z wielkim sukcesem; to ratunek dla wysokowydajnych powiązań danych i dynamicznych operacji właściwości na obiektach biznesowych.źródło
PropertyDescriptor
(w celu uwzględnienia niestandardowych deskryptorów, które mogą sprawić, że pobieranie wartości będzie lekką operacją).Trochę się spóźniłem na przyjęcie, mam jednak nadzieję, że to może być pomocne.
Problem z używaniem odbicia polega na tym, że wynikowe drzewo wyrażeń prawie na pewno nie będzie obsługiwane przez innych dostawców Linq niż wewnętrzny dostawca .Net. Jest to dobre w przypadku kolekcji wewnętrznych, jednak nie zadziała, jeśli sortowanie ma być wykonywane u źródła (np. SQL, MongoDb itp.) Przed paginacją.
Poniższy przykład kodu zawiera metody rozszerzenia IQueryable dla OrderBy i OrderByDescending i może być używany w następujący sposób:
query = query.OrderBy("ProductId");
Metoda rozszerzenia:
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); } }
Pozdrawiam, Mark.
źródło
Expression.Convert
konwersjiproperty
naobject
? OtrzymujęUnable to cast the type 'System.String' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.
błąd i jego usunięcie wydaje się działać.var propAsObject = Expression.Convert(property, typeof(object));
i po prostu użyjproperty
zamiastpropAsObject
LINQ to Entities only supports casting EDM primitive or enumeration types
Podobała mi się odpowiedź od @Mark Powell , ale jak powiedział @ShuberFu , podaje błąd
LINQ to Entities only supports casting EDM primitive or enumeration types
.Usuwanie
var propAsObject = Expression.Convert(property, typeof(object));
nie działało z właściwościami, które były typami wartości, takimi jak liczba całkowita, ponieważ nie spowoduje to niejawnego zapakowania int do obiektu.Korzystając z Pomysłów Kristofera Anderssona i Marca Gravella , znalazłem sposób na skonstruowanie funkcji Queryable przy użyciu nazwy właściwości i sprawienie, by nadal działała z Entity Framework. Dołączyłem również opcjonalny parametr IComparer. Przestroga: parametr IComparer nie działa z Entity Framework i należy go pominąć, jeśli używasz Linq do Sql.
Poniższe działa z Entity Framework i Linq to Sql:
query = query.OrderBy("ProductId");
I @Simon Scheurer to również działa:
query = query.OrderBy("ProductCategory.CategoryId");
A jeśli nie używasz Entity Framework lub Linq to Sql, działa to:
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) ) ); } }
źródło
Aggregate
fragment jest niesamowity!Join
Dba o wirtualne widoki utworzone z modelu EF Core , ponieważ używam właściwości takich jak „T.Property”. W przeciwnym razie zamówienie poJoin
byłoby niemożliwe, aby wyprodukować alboInvalidOperationException
alboNullReferenceException
. I muszę zamawiać POJoin
, ponieważ większość zapytań jest stała, a zamówienia w widokach nie.Aggregate
fragment. Myślę, że było to połączenie kodu Marca Gravella i zalecenia Intellisense. :)products.OrderBy(x => x.ProductId)
możesz użyćproducts.OrderBy("ProductId")
Tak, myślę, że nie ma innego sposobu niż Reflection.
Przykład:
query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));
źródło
"LINQ to Entities does not recognize the method 'System.Object GetValue(System.Object)' method, and this method cannot be translated into a store expression."
Jakieś przemyślenia lub rady, proszę?query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));
Próbuję przypomnieć sobie dokładną składnię z całej mojej głowy, ale myślę, że to prawda.
źródło
Refleksja jest odpowiedzią!
typeof(YourType).GetProperty("ProductId").GetValue(theInstance);
Istnieje wiele rzeczy, które możesz zrobić, aby buforować odzwierciedlone PropertyInfo, sprawdzić złe ciągi, napisać funkcję porównywania zapytań itp., Ale w gruncie rzeczy to właśnie robisz.
źródło
Możesz użyć dynamicznego Linq - sprawdź ten blog.
Sprawdź również ten post StackOverFlow ...
źródło
Bardziej produktywne niż rozszerzenie refleksji na dynamiczne elementy 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"));
Być może będziesz musiał buforować zgodne lambka (np. W Słowniku <>)
źródło
Również wyrażenia dynamiczne mogą rozwiązać ten problem. Możesz używać zapytań opartych na ciągach za pośrednictwem wyrażeń LINQ, które mogły zostać dynamicznie utworzone w czasie wykonywania.
var query = query .Where("Category.CategoryName == @0 and Orders.Count >= @1", "Book", 10) .OrderBy("ProductId") .Select("new(ProductName as Name, Price)");
źródło
Myślę, że możemy użyć potężnego narzędzia o nazwie Expression iw tym przypadku użyć go jako metody rozszerzenia w następujący sposób:
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, bool descending) { var type = typeof(T); var property = type.GetProperty(ordering); var parameter = Expression.Parameter(type, "p"); var propertyAccess = Expression.MakeMemberAccess(parameter, property); var orderByExp = Expression.Lambda(propertyAccess, parameter); MethodCallExpression resultExp = Expression.Call(typeof(Queryable), (descending ? "OrderByDescending" : "OrderBy"), new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp)); return (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(resultExp); }
źródło