C # - özellik adını bir dize olarak kullanan bir özelliğe göre sıralama kodu


92

Özellik adını bir dize olarak aldığımda, C # 'da bir özelliğe karşı kodlamanın en basit yolu nedir? Örneğin, kullanıcının seçtiği bir özelliğe göre (LINQ kullanarak) bazı arama sonuçlarını sipariş etmesine izin vermek istiyorum. Elbette bir dize değeri olarak kullanıcı arayüzünde "sıralama" özelliğini seçeceklerdir. Bu dizeyi özelliklerle eşlemek için koşullu mantık (if / else, switch) kullanmak zorunda kalmadan doğrudan linq sorgusunun bir özelliği olarak kullanmanın bir yolu var mı? Yansıma?

Mantıksal olarak, yapmak istediğim şey bu:

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

Güncelleme: Başlangıçta Varlıklara Linq kullandığımı belirtmedim - yansıma (en azından GetProperty, GetValue yaklaşımı) L2E'ye çevrilmiyor gibi görünüyor.


Bence yansımayı kullanmanız gerekecek ve bir lambda ifadesinde yansımayı kullanabileceğinizden emin değilim ... Pekala, neredeyse kesinlikle Linq'den SQL'e değil ama belki Linq'i bir liste veya başka bir şeye karşı kullanırken.
CodeRedick

@Telos: Lambda'da yansıma (veya başka bir API) kullanmamanız için hiçbir neden yok. Kod bir ifade olarak değerlendirilirse ve başka bir şeye çevrilirse (önerdiğiniz gibi LINQ-to-SQL gibi) çalışıp çalışmayacağı tamamen başka bir sorudur.
Adam Robinson

Bu yüzden cevap yerine bir yorum yayınladım. ;) Çoğunlukla Linq2SQL için kullanılır ...
CodeRedick

1
Sadece aynı sorunun üstesinden gelmek zorundaydım .. aşağıdaki cevabıma bakın. stackoverflow.com/a/21936366/775114
Mark Powell

Yanıtlar:


129

Bu alternatifi, herkesin yazdıklarına sunacağım.

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

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

Bu, mülkün elde edilmesi için yansıma API'sine tekrarlanan çağrıları önler. Şimdi tekrarlanan tek çağrı değeri elde etmektir.

ancak

Bunun PropertyDescriptoryerine, özel TypeDescriptore-postaların sizin türünüze atanmasına izin vereceği için , özellikleri ve değerleri almak için hafif işlemlere sahip olmayı mümkün kıldığından, bunun yerine a kullanmayı savunurum. Özel bir tanımlayıcının olmaması durumunda, yine de yansımaya geri dönecektir.

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

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

Hızlandırmaya gelince, Marc Gravel'in HyperDescriptorCodeProject projesine göz atın. Bunu büyük bir başarıyla kullandım; yüksek performanslı veri bağlama ve iş nesneleri üzerinde dinamik özellik işlemleri için hayat kurtarıcıdır.


Yansıyan çağrının (yani GetValue), düşünmenin en maliyetli kısmı olduğunu unutmayın. Meta veri alımı (yani GetProperty) aslında daha az maliyetlidir (bir miktar sırasına göre), bu nedenle bu parçayı önbelleğe alarak gerçekten kendinizi o kadar kurtarmazsınız. Bu, her iki şekilde de hemen hemen aynı maliyete sahip olacak ve bu maliyet ağır olacak. Sadece not edilecek bir şey.
jrista

1
@jrista: Kesin olmak gerekirse, çağrı en maliyetli olanıdır. Bununla birlikte, "daha az maliyetli", "ücretsiz" veya buna yakın anlamına gelmez. Meta veri alımı önemsiz olmayan bir süre alır, bu nedenle onu önbelleğe almanın bir avantajı vardır ve hiçbir dezavantajı yoktur (burada bir şeyi kaçırmıyorsam). Gerçekte bu gerçekten kullanıyor olmalıdır PropertyDescriptor(özel tip tanımlayıcıları için hesaba zaten olabilir değer alma hafif operasyon olun).
Adam Robinson

Bir ASP.NET GridView programlı olarak sıralamayı işlemek için saatlerce arandı: PropertyDescriptor prop = TypeDescriptor.GetProperties (typeof (ScholarshipRequest)) Find (e.SortExpression, true);
Baxter

1
stackoverflow.com/questions/61635636/… Yansımayla ilgili bir sorun vardı, EfCore 3.1.3'te işe yaramadı. EfCore 2'de uyarılar için etkinleştirilmesi gereken hata veriyor gibi görünüyor. Aşağıdaki @Mark cevabını kullanın
armourshield

1
Aşağıdakileri alıyorum: InvalidOperationException: LINQ ifadesi 'DbSet <MyObject> .Where (t => t.IsMasterData) .OrderBy (t => t.GetType (). GetProperty ("Address"). GetValue (nesne: t, index: null) .GetType ()) 'çevrilemedi. Ya sorguyu çevrilebilecek bir biçimde yeniden yazın ya da AsEnumerable (), AsAsyncEnumerable (), ToList () ya da ToListAsync () 'e bir çağrı ekleyerek açıkça istemci değerlendirmesine geçin.
bbrinck

67

Partiye biraz geç kaldım, umarım bu biraz yardımcı olabilir.

Yansıma kullanmanın sorunu, ortaya çıkan İfade Ağacının neredeyse kesinlikle dahili .Net sağlayıcısı dışında herhangi bir Linq sağlayıcısı tarafından desteklenmeyeceğidir. Bu, dahili koleksiyonlar için uygundur, ancak bu, sıralamanın sayfalandırmadan önce kaynakta (SQL, MongoDb vb.) Yapılacağı yerlerde çalışmayacaktır.

Aşağıdaki kod örneği, OrderBy ve OrderByDescending için IQueryable genişletme yöntemleri sağlar ve şu şekilde kullanılabilir:

query = query.OrderBy("ProductId");

Uzatma Yöntemi:

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

Saygılarımızla, Mark.


Mükemmel çözüm - tam olarak bunu arıyordum. İfade ağaçlarını gerçekten kazmam gerekiyor. Hala bu konuda çok çaylak. @Mark, iç içe ifadeler yapmak için herhangi bir çözüm var mı? Diyelim ki, kendisi "Değer" özelliğine sahip olan TSub türünün "Alt" özelliğine sahip bir T türüne sahip olduğumu varsayalım. Şimdi "Sub.Value" dizesi için Expression <Func <T, nesne >> ifadesini almak istiyorum.
Simon Scheurer

4
Neden Expression.Convertdönüştürmemiz propertygerekiyor object? Bir Unable to cast the type 'System.String' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.hata alıyorum ve kaldırmak işe yarıyor gibi görünüyor.
ShuberFu

@ Demodave eğer doğru hatırlıyorsam. var propAsObject = Expression.Convert(property, typeof(object));ve sadece kullanmak propertyyerinepropAsObject
ShuberFu

Altın. .Net Core 2.0.5 için uyarlanmıştır.
Chris Amelinckx

2
Hata varLINQ to Entities only supports casting EDM primitive or enumeration types
Mateusz Puwałowski

35

@Mark Powell'ın cevabını beğendim ama @ShuberFu'nun dediği gibi hata veriyor LINQ to Entities only supports casting EDM primitive or enumeration types.

Çıkarma var propAsObject = Expression.Convert(property, typeof(object));, int nesnesini örtük olarak kutuya koymayacağı için, tamsayı gibi değer türleri olan özelliklerle çalışmadı.

Kristofer Andersson ve Marc Gravell'in Fikirlerini Kullanarak Sorgulanabilir işlevi özellik adını kullanarak oluşturmanın bir yolunu buldum ve hala Entity Framework ile çalışmasını sağladım. Ayrıca isteğe bağlı bir IComparer parametresi de ekledim. Dikkat: IComparer parametresi Entity Framework ile çalışmaz ve Linq to Sql kullanılıyorsa dışarıda bırakılmalıdır.

Aşağıdakiler Entity Framework ve Linq to Sql ile çalışır:

query = query.OrderBy("ProductId");

Ve @ Simon Scheurer bu da işe yarıyor:

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

Ve Entity Framework veya Linq to Sql kullanmıyorsanız, bu çalışır:

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

İşte 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)
            )
        );
}
}

Tanrım, sen Microsoft musun? :) Bu Aggregateparça harika! Join"T.Property" gibi özellikler kullandığım için EF Core modelinden oluşturulan sanal görünümlerle ilgilenir . Aksi takdirde sonradan sipariş vermek Joinya InvalidOperationExceptionda üretmek imkansız olacaktır NullReferenceException. Ve SONRA sipariş vermem gerekiyor Join, çünkü çoğu sorgu sabit, görünümlerdeki siparişler değil.
Harry

@Harry. Teşekkürler, ama Aggregatefragman için gerçekten çok fazla kredi alamam . Sanırım Marc Gravell'in kodu ile akıllıca bir önerinin bir kombinasyonu . :)
David Specht

@DavidSpecht Sadece İfade Ağaçları öğreniyorum, bu yüzden onlar hakkındaki her şey artık benim için kara büyü. Ama çabuk öğreniyorum, VS'deki C # etkileşimli pencere çok yardımcı oluyor.
Harry

bu nasıl kullanılır?
Dat Nguyen

@Dat Nguyen Bunun yerine products.OrderBy(x => x.ProductId)kullanabilirsinizproducts.OrderBy("ProductId")
David Specht

12

Evet, Düşünceden başka bir yol olduğunu sanmıyorum.

Misal:

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

Hatayı alıyorum "LINQ to Entities does not recognize the method 'System.Object GetValue(System.Object)' method, and this method cannot be translated into a store expression."Herhangi bir fikir veya tavsiye, lütfen?
Florin Vîrdol

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

Kafamın üstünden tam sözdizimi hatırlamaya çalışıyorum ama bence bu doğru.


2

Cevap, yansıma!

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

Yansıyan PropertyInfo'yu önbelleğe almak, kötü dizeleri kontrol etmek, sorgu karşılaştırma işlevinizi yazmak vb. İçin yapabileceğiniz pek çok şey vardır, ancak özünde yaptığınız şey budur.



2

Dinamik sipariş öğelerine yansıtma uzantısından daha üretken:

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

Misal:

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

Ayrıca, uyumlu lambaları önbelleğe almanız gerekebilir (örneğin Sözlük <>)


1

Ayrıca Dinamik İfadeler bu sorunu çözebilir. Çalışma zamanında dinamik olarak yapılandırılmış olabilecek LINQ ifadeleri aracılığıyla dize tabanlı sorguları kullanabilirsiniz.

var query = query
          .Where("Category.CategoryName == @0 and Orders.Count >= @1", "Book", 10)
          .OrderBy("ProductId")
          .Select("new(ProductName as Name, Price)");

0

Expression adında güçlü bir araç kullanabileceğimizi düşünüyorum ve bu durumda aşağıdaki gibi bir uzantı yöntemi olarak kullanalım:

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);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.