C # - รหัสเพื่อเรียงลำดับตามคุณสมบัติโดยใช้ชื่อคุณสมบัติเป็นสตริง


92

อะไรคือวิธีที่ง่ายที่สุดในการโค้ดเทียบคุณสมบัติใน C # เมื่อฉันมีชื่อคุณสมบัติเป็นสตริง ตัวอย่างเช่นฉันต้องการอนุญาตให้ผู้ใช้จัดลำดับผลการค้นหาตามคุณสมบัติที่ต้องการ (โดยใช้ LINQ) พวกเขาจะเลือกคุณสมบัติ "เรียงลำดับตาม" ใน UI - เป็นค่าสตริงแน่นอน มีวิธีใช้สตริงนั้นโดยตรงเป็นคุณสมบัติของคิวรี linq โดยไม่ต้องใช้ตรรกะเงื่อนไข (if / else, switch) เพื่อแม็พสตริงกับคุณสมบัติ การสะท้อนกลับ?

ตามหลักเหตุผลนี่คือสิ่งที่ฉันต้องการทำ:

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

อัปเดต: เดิมทีฉันไม่ได้ระบุว่าฉันใช้ Linq เป็นเอนทิตี - ดูเหมือนว่าการสะท้อน (อย่างน้อยก็คือวิธี GetProperty, GetValue) ไม่ได้แปลเป็น L2E


ฉันคิดว่าคุณต้องใช้การสะท้อนและฉันไม่แน่ใจว่าคุณสามารถใช้การสะท้อนในนิพจน์แลมบ์ดาได้ ... ดีเกือบจะไม่ได้อยู่ใน Linq เป็น SQL แต่บางทีเมื่อใช้ Linq กับรายการหรือบางอย่าง
CodeRedick

@Telos: ไม่มีเหตุผลที่คุณไม่สามารถใช้การสะท้อนแสง (หรือ API อื่น ๆ ) ในแลมบ์ดา จะใช้งานได้หรือไม่หากโค้ดได้รับการประเมินเป็นนิพจน์และแปลเป็นอย่างอื่น (เช่น LINQ-to-SQL ตามที่คุณแนะนำ) เป็นอีกคำถามหนึ่งโดยสิ้นเชิง
Adam Robinson

นี่คือเหตุผลที่ฉันโพสต์ความคิดเห็นแทนคำตอบ ;) ส่วนใหญ่ใช้กับ Linq2SQL ...
CodeRedick

1
เพียงแค่ต้องเอาชนะปัญหาเดียวกัน .. ดูคำตอบของฉันด้านล่าง stackoverflow.com/a/21936366/775114
Mark Powell

คำตอบ:


130

ฉันขอเสนอทางเลือกนี้ให้กับสิ่งที่คนอื่นโพสต์

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

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

วิธีนี้จะหลีกเลี่ยงการเรียกซ้ำไปยัง API การสะท้อนเพื่อรับคุณสมบัติ ตอนนี้การเรียกซ้ำเพียงครั้งเดียวคือการได้รับค่า

อย่างไรก็ตาม

ฉันขอแนะนำให้ใช้ a PropertyDescriptorแทนเนื่องจากจะอนุญาตให้กำหนดTypeDescriptors แบบกำหนดเองให้กับประเภทของคุณทำให้มีการดำเนินการที่มีน้ำหนักเบาสำหรับการดึงคุณสมบัติและค่าต่างๆ ในกรณีที่ไม่มีตัวบ่งชี้ที่กำหนดเองมันจะย้อนกลับไปสู่การสะท้อนอย่างไรก็ตาม

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

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

สำหรับการเร่งความเร็วให้ดูHyperDescriptorโครงการของ Marc Gravel ใน CodeProject ฉันใช้สิ่งนี้ประสบความสำเร็จอย่างมาก มันช่วยประหยัดชีวิตสำหรับการเชื่อมโยงข้อมูลประสิทธิภาพสูงและการดำเนินการคุณสมบัติแบบไดนามิกบนออบเจ็กต์ทางธุรกิจ


โปรดทราบว่าการเรียกร้องที่สะท้อน (เช่น GetValue) เป็นส่วนที่มีค่าใช้จ่ายสูงที่สุดของการสะท้อน การดึงข้อมูลเมตา (เช่น GetProperty) นั้นมีค่าใช้จ่ายน้อยกว่า (ตามลำดับขนาด) ดังนั้นการแคชส่วนนั้นจะทำให้คุณไม่ได้ช่วยตัวเองมากขนาดนั้น สิ่งนี้จะมีค่าใช้จ่ายค่อนข้างมากเหมือนกันและค่าใช้จ่ายนั้นจะหนักมาก สิ่งที่ควรทราบ
jrista

1
@jrista: การร้องขอเป็นค่าใช้จ่ายมากที่สุดเพื่อให้แน่ใจ อย่างไรก็ตาม "เสียค่าใช้จ่ายน้อย" ไม่ได้หมายความว่า "ฟรี" หรือใกล้เคียงด้วยซ้ำ การดึงข้อมูลเมตาใช้เวลาไม่สำคัญดังนั้นจึงมีข้อได้เปรียบในการแคชและไม่มีข้อเสียใด ๆ(เว้นแต่ฉันจะพลาดบางอย่างที่นี่) ในความเป็นจริงสิ่งนี้ควรใช้PropertyDescriptorอยู่แล้ว (เพื่อพิจารณาตัวบอกประเภทที่กำหนดเองซึ่งอาจทำให้การดึงค่าเป็นการดำเนินการที่มีน้ำหนักเบา)
Adam Robinson

ค้นหาหลายชั่วโมงสำหรับสิ่งนี้เพื่อจัดการการเรียงลำดับ ASP.NET GridView โดยทางโปรแกรม: PropertyDescriptor prop = TypeDescriptor.GetProperties (typeof (ScholarshipRequest)) ค้นหา (e.SortExpression, true);
Baxter

1
stackoverflow.com/questions/61635636/… มีปัญหากับการสะท้อนกลับไม่ได้ผลใน EfCore 3.1.3 ดูเหมือนว่าจะเกิดข้อผิดพลาดใน EfCore 2 ซึ่งจำเป็นต้องเปิดใช้งานสำหรับคำเตือน ใช้คำตอบของ
@Mark

1
ฉันได้รับสิ่งต่อไปนี้: InvalidOperationException: นิพจน์ LINQ 'DbSet <MyObject> .Where (t => t.IsMasterData) .OrderBy (t => t.GetType (). GetProperty ("Address") GetValue (obj: t, ดัชนี: null) .GetType ()) 'ไม่สามารถแปลได้ เขียนแบบสอบถามใหม่ในรูปแบบที่สามารถแปลได้หรือเปลี่ยนไปใช้การประเมินผลไคลเอ็นต์อย่างชัดเจนโดยการแทรกการเรียกไปยัง AsEnumerable (), AsAsyncEnumerable (), ToList () หรือ ToListAsync ()
bbrinck

68

ฉันไปงานปาร์ตี้ช้าไปหน่อย แต่หวังว่านี่จะช่วยได้บ้าง

ปัญหาในการใช้การสะท้อนกลับคือ Expression Tree ที่เป็นผลลัพธ์แทบจะไม่ได้รับการสนับสนุนจากผู้ให้บริการ Linq ใด ๆ นอกเหนือจากผู้ให้บริการ. Net ภายใน สิ่งนี้ใช้ได้ดีสำหรับคอลเลกชันภายในอย่างไรก็ตามจะใช้ไม่ได้ในกรณีที่ต้องทำการเรียงลำดับที่ต้นทาง (เช่น SQL, MongoDb เป็นต้น) ก่อนการแบ่งหน้า

ตัวอย่างโค้ดด้านล่างแสดงวิธีการขยาย IQueryable สำหรับ OrderBy และ OrderByDescending และสามารถใช้ได้ดังนี้:

query = query.OrderBy("ProductId");

วิธีการขยาย:

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

ขอแสดงความนับถือมาร์ค


ทางออกที่ยอดเยี่ยม - ฉันกำลังมองหาสิ่งนั้น ฉันต้องขุดเข้าไปในต้นไม้ Expression จริงๆ ยังคงเป็นมือใหม่มากที่ @ มาร์ควิธีแก้ปัญหาใด ๆ ในการทำนิพจน์ที่ซ้อนกัน สมมติว่าฉันมีประเภท T ที่มีพร็อพเพอร์ตี้ "Sub" ประเภท TSub ที่ตัวมันเองมีคุณสมบัติ "Value" ตอนนี้ฉันต้องการรับนิพจน์ Expression <Func <T, object >> สำหรับสตริง "Sub.Value"
Simon Scheurer

4
ทำไมเราต้องExpression.Convertแปลงpropertyเป็นobject? ฉันได้รับUnable to cast the type 'System.String' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.ข้อผิดพลาดและดูเหมือนว่าจะนำออกได้
ShuberFu

@Demodave ถ้าจำไม่ผิด. var propAsObject = Expression.Convert(property, typeof(object));และใช้propertyแทนpropAsObject
ShuberFu

ทอง. ดัดแปลงสำหรับ. Net Core 2.0.5
Chris Amelinckx

2
มีข้อผิดพลาดLINQ to Entities only supports casting EDM primitive or enumeration types
Mateusz Puwałowski

36

ผมชอบคำตอบจาก@ Mark Powellแต่เป็น@ShuberFuLINQ to Entities only supports casting EDM primitive or enumeration typesกล่าวว่ามันจะช่วยให้ข้อผิดพลาด

การนำออกใช้var propAsObject = Expression.Convert(property, typeof(object));ไม่ได้กับคุณสมบัติที่เป็นประเภทค่าเช่นจำนวนเต็มเนื่องจากจะไม่ใส่กล่อง int to object โดยปริยาย

การใช้ไอเดียจากKristofer AnderssonและMarc Gravellฉันพบวิธีสร้างฟังก์ชัน Queryable โดยใช้ชื่อคุณสมบัติและยังใช้งานได้กับ Entity Framework ฉันยังรวมพารามิเตอร์ IComparer ที่เป็นทางเลือก ข้อควรระวัง:พารามิเตอร์ IComparer ใช้ไม่ได้กับ Entity Framework และควรละไว้หากใช้ Linq เป็น Sql

สิ่งต่อไปนี้ใช้ได้กับ Entity Framework และ Linq ถึง Sql:

query = query.OrderBy("ProductId");

และ@Simon Scheurerสิ่งนี้ยังใช้งานได้:

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

และถ้าคุณไม่ได้ใช้ Entity Framework หรือ Linq to Sql สิ่งนี้จะใช้ได้:

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

นี่คือรหัส:

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

Geez คุณเป็น Microsoft หรือไม่? :) Aggregateชิ้นส่วนนั้นยอดเยี่ยมมาก! ดูแลมุมมองเสมือนจริงที่สร้างจากโมเดล EF Core ด้วยJoinเนื่องจากฉันใช้คุณสมบัติเช่น "T.Property" มิฉะนั้นการสั่งซื้อหลังจากที่Joinจะเป็นไปไม่ได้การผลิตอย่างใดอย่างหนึ่งหรือInvalidOperationException NullReferenceExceptionและฉันจำเป็นต้องสั่งซื้อ AFTER Joinเนื่องจากข้อความค้นหาส่วนใหญ่คงที่คำสั่งซื้อในมุมมองจึงไม่ใช่
Harry

@DavidSpecht ฉันเพิ่งเรียนรู้ Expression Trees ดังนั้นตอนนี้ทุกอย่างเกี่ยวกับพวกเขาจึงเป็นมนต์ดำสำหรับฉัน แต่ฉันเรียนรู้ได้เร็วหน้าต่างโต้ตอบ C # ใน VS ช่วยได้มาก
Harry

ใช้ยังไง?
Dat Nguyen

@Dat Nguyen แทนที่จะproducts.OrderBy(x => x.ProductId)ใช้products.OrderBy("ProductId")
David Specht

12

ใช่ฉันไม่คิดว่าจะมีวิธีอื่นนอกจาก Reflection

ตัวอย่าง:

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

ฉันได้รับข้อผิดพลาดมี"LINQ to Entities does not recognize the method 'System.Object GetValue(System.Object)' method, and this method cannot be translated into a store expression."ความคิดเห็นหรือคำแนะนำใด ๆ โปรด?
Florin Vîrdol

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

พยายามจำไวยากรณ์ที่แน่นอนจากด้านบนของหัว แต่ฉันคิดว่าถูกต้อง


2

Reflection คือคำตอบ!

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

มีหลายสิ่งที่คุณสามารถทำได้เพื่อแคช PropertyInfo ที่สะท้อนตรวจสอบสตริงที่ไม่ถูกต้องเขียนฟังก์ชันเปรียบเทียบการสืบค้นของคุณ ฯลฯ แต่หัวใจสำคัญของมันนี่คือสิ่งที่คุณทำ



2

มีประสิทธิผลมากกว่าการขยายการสะท้อนไปยังรายการสั่งซื้อแบบไดนามิก:

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

ตัวอย่าง:

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

นอกจากนี้คุณอาจต้องแคช lambas ที่ปฏิบัติตาม (เช่นในพจนานุกรม <>)


1

นอกจากนี้นิพจน์แบบไดนามิกสามารถแก้ปัญหานี้ได้ คุณสามารถใช้คิวรีแบบสตริงผ่านนิพจน์ LINQ ที่สามารถสร้างแบบไดนามิกในขณะรันไทม์

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

0

ฉันคิดว่าเราสามารถใช้ชื่อเครื่องมือที่มีประสิทธิภาพ Expression an ในกรณีนี้ใช้เป็นวิธีการขยายดังนี้:

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);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.