C # - mã đặt hàng theo thuộc tính sử dụng tên thuộc tính dưới dạng chuỗi


92

Cách đơn giản nhất để viết mã chống lại một thuộc tính trong C # khi tôi có tên thuộc tính là một chuỗi là gì? Ví dụ: tôi muốn cho phép người dùng sắp xếp một số kết quả tìm kiếm theo thuộc tính họ chọn (sử dụng LINQ). Họ sẽ chọn thuộc tính "order by" trong giao diện người dùng - tất nhiên là một giá trị chuỗi. Có cách nào để sử dụng chuỗi đó trực tiếp làm thuộc tính của truy vấn linq mà không cần phải sử dụng logic có điều kiện (if / else, switch) để ánh xạ các chuỗi thành thuộc tính không. Phản ánh?

Về mặt logic, đây là những gì tôi muốn làm:

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

Cập nhật: Ban đầu tôi không chỉ định rằng tôi đang sử dụng Linq to Entities - có vẻ như phản ánh (ít nhất là phương pháp GetProperty, GetValue) không dịch sang L2E.


Tôi nghĩ bạn phải sử dụng phản xạ và tôi không chắc bạn có thể sử dụng phản chiếu trong biểu thức lambda ... tốt, gần như chắc chắn không phải trong Linq to SQL nhưng có thể khi sử dụng Linq đối với một danh sách hoặc thứ gì đó.
CodeRedick

@Telos: Không có lý do gì mà bạn không thể sử dụng phản chiếu (hoặc bất kỳ API nào khác) trong lambda. Liệu nó có hoạt động hay không nếu mã được đánh giá như một biểu thức và được dịch sang một thứ khác (như LINQ-to-SQL, như bạn đề xuất) hoàn toàn là một câu hỏi khác.
Adam Robinson

Đây là lý do tại sao tôi đăng một bình luận thay vì một câu trả lời. ;) Chủ yếu được sử dụng để Linq2SQL ...
CodeRedick

1
Chỉ cần phải khắc phục cùng một vấn đề .. xem câu trả lời của tôi dưới đây. stackoverflow.com/a/21936366/775114
Mark Powell

Câu trả lời:


129

Tôi sẽ cung cấp thay thế này cho những gì mọi người khác đã đăng.

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

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

Điều này tránh các cuộc gọi lặp lại tới API phản ánh để lấy thuộc tính. Bây giờ cuộc gọi lặp lại duy nhất đang nhận được giá trị.

Tuy nhiên

Tôi sẽ ủng hộ việc sử dụng PropertyDescriptorthay thế, vì điều này sẽ cho phép TypeDescriptorgán các tùy chỉnh cho kiểu của bạn, giúp bạn có thể thực hiện các thao tác nhẹ để truy xuất thuộc tính và giá trị. Trong trường hợp không có bộ mô tả tùy chỉnh, nó sẽ trở lại trạng thái phản chiếu.

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

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

Để tăng tốc độ, hãy xem HyperDescriptordự án của Marc Gravel trên CodeProject. Tôi đã sử dụng điều này với thành công lớn; đó là một trình bảo vệ cuộc sống cho các hoạt động liên kết dữ liệu hiệu suất cao và tài sản động trên các đối tượng kinh doanh.


Lưu ý rằng lời gọi phản ánh (tức là GetValue) là phần tốn kém nhất của phản ánh. Việc truy xuất siêu dữ liệu (tức là GetProperty) thực sự ít tốn kém hơn (theo thứ tự độ lớn), vì vậy bằng cách lưu phần đó vào bộ nhớ đệm, bạn không thực sự tiết kiệm cho mình nhiều như vậy. Điều này sẽ có giá khá giống nhau, và chi phí đó sẽ rất nặng. Chỉ cần một số điều cần lưu ý.
jrista

1
@jrista: chắc chắn là tốn kém nhất. Tuy nhiên, "ít tốn kém hơn" không có nghĩa là "miễn phí", hoặc thậm chí gần với nó. Việc truy xuất siêu dữ liệu mất một khoảng thời gian không nhỏ, vì vậy có một lợi thế khi lưu vào bộ nhớ đệm và không có bất lợi nào (trừ khi tôi thiếu thứ gì đó ở đây). Trên thực tế, điều này thực sự nên được sử dụng PropertyDescriptor(để giải thích cho các bộ mô tả kiểu tùy chỉnh, có thể làm cho việc truy xuất giá trị trở thành một hoạt động nhẹ).
Adam Robinson

Đã tìm kiếm hàng giờ cho một thứ như thế này để xử lý việc sắp xếp một ASP.NET GridView theo lập trình: PropertyDescriptor prop = TypeDescriptor.GetProperties (typeof (ScholarshipRequest)). Find (e.SortExpression, true);
Baxter

1
stackoverflow.com/questions/61635636/… Đã gặp sự cố với phản ánh, nó không hoạt động trong EfCore 3.1.3. Có vẻ như lỗi trong EfCore 2 cần được kích hoạt để nhận cảnh báo. Sử dụng câu trả lời của
@Mark

1
Tôi nhận được thông tin sau: InvalidOperationException: Biểu thức LINQ 'DbSet <MyObject> .Where (t => t.IsMasterData) .OrderBy (t => t.GetType (). GetProperty ("Địa chỉ"). GetValue (obj: t, index: null) .GetType ()) 'không thể dịch được. Viết lại truy vấn trong một biểu mẫu có thể được dịch hoặc chuyển sang đánh giá ứng dụng khách một cách rõ ràng bằng cách chèn một lệnh gọi tới AsEnumerable (), AsAsyncEnumerable (), ToList () hoặc ToListAsync ().
bbrinck

67

Tuy nhiên, tôi đến bữa tiệc hơi muộn, tôi hy vọng điều này có thể giúp ích được phần nào.

Vấn đề với việc sử dụng phản chiếu là Cây biểu thức kết quả gần như chắc chắn sẽ không được hỗ trợ bởi bất kỳ nhà cung cấp Linq nào ngoài nhà cung cấp .Net nội bộ. Điều này là tốt cho các bộ sưu tập nội bộ, tuy nhiên điều này sẽ không hoạt động khi việc sắp xếp được thực hiện tại nguồn (là SQL, MongoDb, v.v.) trước khi phân trang.

Mẫu mã dưới đây cung cấp các phương thức mở rộng IQueryable cho OrderBy và OrderByDescending, và có thể được sử dụng như vậy:

query = query.OrderBy("ProductId");

Phương pháp mở rộng:

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

Trân trọng, Mark.


Giải pháp tuyệt vời - tôi đang tìm kiếm chính xác điều đó. Tôi thực sự cần tìm hiểu về cây Biểu thức. Vẫn còn rất tân binh ở đó. @Mark, bất kỳ giải pháp nào để thực hiện các biểu thức lồng nhau? Giả sử tôi có loại T với thuộc tính "Sub" của loại TSub mà bản thân nó có thuộc tính "Giá trị". Bây giờ tôi muốn lấy biểu thức Expression <Func <T, object >> cho chuỗi "Sub.Value".
Simon Scheurer,

4
Tại sao chúng ta cần Expression.Convertchuyển đổi propertysang object? Tôi đang gặp Unable to cast the type 'System.String' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.lỗi và việc xóa nó có vẻ hoạt động.
ShuberFu

@Demodave nếu tôi nhớ không lầm. var propAsObject = Expression.Convert(property, typeof(object));và chỉ sử dụng propertyở vị trí củapropAsObject
ShuberFu

Vàng. Được điều chỉnh cho .Net Core 2.0.5.
Chris Amelinckx

2
Lỗi GotLINQ to Entities only supports casting EDM primitive or enumeration types
Mateusz Puwałowski

35

Tôi thích câu trả lời từ @Mark Powell , nhưng như @ShuberFu đã nói, nó đưa ra lỗi LINQ to Entities only supports casting EDM primitive or enumeration types.

Việc xóa var propAsObject = Expression.Convert(property, typeof(object));không hoạt động với các thuộc tính có kiểu giá trị, chẳng hạn như số nguyên, vì nó sẽ không hoàn toàn đóng hộp đối tượng int to.

Sử dụng Ý tưởng từ Kristofer AnderssonMarc Gravell, tôi đã tìm ra cách để xây dựng hàm Queryable bằng cách sử dụng tên thuộc tính và nó vẫn hoạt động với Entity Framework. Tôi cũng bao gồm một tham số IComparer tùy chọn. Thận trọng: Tham số IComparer không hoạt động với Entity Framework và nên bị loại bỏ nếu sử dụng Linq to Sql.

Phần sau hoạt động với Entity Framework và Linq to Sql:

query = query.OrderBy("ProductId");

@Simon Scheurer điều này cũng hoạt động:

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

Và nếu bạn không sử dụng Entity Framework hoặc Linq to Sql, điều này sẽ hoạt động:

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

Đây là mã:

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, bạn có phải là Microsoft không? :) Đoạn đó Aggregatethật tuyệt vời! Nó xử lý các khung nhìn ảo được tạo từ mô hình EF Core với Join, vì tôi sử dụng các thuộc tính như "T.Property". Nếu không đặt hàng sau Joinsẽ không thể sản xuất hoặc InvalidOperationExceptionhoặc NullReferenceException. Và tôi cần đặt hàng SAU KHI Join, bởi vì hầu hết các truy vấn là không đổi, thứ tự trong các lượt xem thì không.
Harry

@Harry. Cảm ơn, nhưng tôi thực sự không thể mất quá nhiều tín dụng cho Aggregatephần này. Tôi tin rằng đó là sự kết hợp giữa mã của Marc Gravell và một đề xuất intellisense. :)
David Specht

@DavidSpecht Tôi chỉ đang học Cây biểu hiện, vì vậy mọi thứ về chúng bây giờ là ma thuật đen đối với tôi. Nhưng tôi học nhanh, cửa sổ tương tác C # trong VS giúp ích rất nhiều.
Harry

làm thế nào để sử dụng cái này?
Dat Nguyen

@Dat Nguyễn Thay vì products.OrderBy(x => x.ProductId), bạn có thể sử dụngproducts.OrderBy("ProductId")
David Specht

12

Vâng, tôi không nghĩ có cách nào khác ngoài Reflection.

Thí dụ:

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

Tôi nhận được lỗi "LINQ to Entities does not recognize the method 'System.Object GetValue(System.Object)' method, and this method cannot be translated into a store expression."Bất kỳ suy nghĩ hoặc lời khuyên, xin vui lòng?
Florin Vîrdol

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

Cố gắng nhớ lại cú pháp chính xác trong đầu nhưng tôi nghĩ đó là chính xác.


2

Suy ngẫm là câu trả lời!

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

Có rất nhiều việc bạn có thể làm để lưu vào bộ nhớ cache của PropertyInfo được phản ánh, kiểm tra các chuỗi không hợp lệ, viết hàm so sánh truy vấn của bạn, v.v., nhưng về cơ bản, đây là những gì bạn làm.



2

Hiệu quả hơn phần mở rộng phản ánh cho các mục đặt hàng động:

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

Thí dụ:

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

Ngoài ra, bạn có thể cần phải lưu vào bộ nhớ cache lambas tuân thủ (ví dụ: trong Từ điển <>)


1

Ngoài ra Biểu thức động có thể giải quyết vấn đề này. Bạn có thể sử dụng các truy vấn dựa trên chuỗi thông qua các biểu thức LINQ có thể được tạo động tại thời điểm chạy.

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

0

Tôi nghĩ chúng ta có thể sử dụng một tên công cụ mạnh mẽ Biểu thức an trong trường hợp này, hãy sử dụng nó như một phương thức mở rộng như sau:

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);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.