Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
228 views
in Technique[技术] by (71.8m points)

c# - Store multi-type OrderBy expression as a property

In a generic abstract base class I'm storing a couple of expressions used for ordering:

public Expression<Func<T, string>> OrderByString { get; set; }
public Expression<Func<T, int>> OrderByInt { get; set; }

The get used later on in the generic base class:

if (OrderByString != null)
{
    results = results.OrderBy(OrderByString);
}
else if (OrderByInt != null)
{
    results = results.OrderBy(OrderByInt);
}

Finally one of them will get set in the deriving concrete class's constructer:

this.OrderByString = c => c.CustomerID;

I don't like the fact that I need to have separate expressions based on the property type I want to OrderBy. ToString won't work on the property's because LINQ to Entities doesn't support it. What I'm after is a way of storing an expression that picks any of the properties to order on regardless of type.

If I try something a little more generic such as:

public Expression<Func<T, object>> Order { get; set; }

Unable to cast the type 'System.Int32' to type 'System.Object'. LINQ to Entities only supports casting Entity Data Model primitive types.

Additionally if I try a slight hack this also doesn't work:

public Expression<Func<T, string>> Order { get; set; }
this.Order = c => c.OrderID.ToString();

LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Sounds like you want a way to pile up a bunch of Ordering in a list somewhere and apply it. But you can't because each Expression has its own type, which is checked by the compiler when calling OrderBy. You must have those two types when calling OrderBy, but you must have one type to put in the same list.

Hide that second type behind an interface.

public interface IOrderer<T>
{
    IOrderedQueryable<T> ApplyOrderBy(IQueryable<T> source);
    IOrderedQueryable<T> ApplyOrderByDescending(IQueryable<T> source);
    IOrderedQueryable<T> ApplyThenBy(IOrderedQueryable<T> source);
    IOrderedQueryable<T> ApplyThenByDescending(IOrderedQueryable<T> source);
}

public class Orderer<T, U> : IOrderer<T>
{
    private Expression<Func<T, U>> _orderExpr;
    public Orderer(Expression<Func<T, U>> orderExpr) { _orderExpr = orderExpr; }

    public IOrderedQueryable<T> ApplyOrderBy(IQueryable<T> source)
    { return source.OrderBy(_orderExpr); }
    public IOrderedQueryable<T> ApplyOrderByDescending(IQueryable<T> source)
    { return source.OrderByDescending(_orderExpr); }
    public IOrderedQueryable<T> ApplyThenBy(IOrderedQueryable<T> source)
    { return source.ThenBy(_orderExpr); }
    public IOrderedQueryable<T> ApplyThenByDescending(IOrderedQueryable<T> source)
    { return source.ThenByDescending(_orderExpr); }
}

public class OrderCoordinator<T>
{
    public List<IOrderer<T>> Orders { get; set; }

    public OrderCoordinator() { Orders = new List<IOrderer<T>>(); }

    //note, did not return IOrderedQueryable to support ability to return with empty Orders
    public IQueryable<T> ApplyOrders(IQueryable<T> source)
    {
        foreach (IOrderer<T> orderer in Orders)
        {
            source = orderer.ApplyOrderBy(source);
        }
        return source;
    }
}

public class Customer
{
    public string Name { get; set; }
    public int FavNumber { get; set; }
}

public class Tester
{
    public void Test()
    {
        OrderCoordinator<Customer> coord = new OrderCoordinator<Customer>();
        coord.Orders.Add(new Orderer<Customer, string>(c => c.Name));
        coord.Orders.Add(new Orderer<Customer, int>(c => c.FavNumber));

        IQueryable<Customer> query = Enumerable.Empty<Customer>().AsQueryable();

        query = coord.ApplyOrders(query);

        string result = query.Expression.ToString();
    }
}

In the debugger:

result = "OrderingDemo.Customer[].OrderBy(c => c.Name).OrderBy(c => c.FavNumber)"

So in your case, instead of this property:

 public Expression<Func<T, U>> Order { get; set; } 

use this property

 public IOrderer<T> Order { get; set; } 

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...