2

In my "Invoice" class it has 20+ fields, I need to support sorting to all fields.

I have a method accepting a string of the property (e.g. "AmountDue"). Now I need to OrderBy AmoundDue field.

My specific requirement is, passing a property name string value I want to return a function like,

Func<Invoice, decimal> keySelector = a=> a.AmountDue;

Where whole expression is dynamically built.

where I can use the keySelector for for sorting

_api.Invoices.Find().Where(c => c.Contact.Id == contactId).OrderBy(keySelector).ToList();

So far I was able to identify the type with the string value. Need some help to return a function.

  public static Type VariableType(string prop)
    {
        Type type = typeof(TResource);
        PropertyInfo pi = type.GetProperty(prop, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
        type = pi.PropertyType;
        return type;
    }

I referred to this post before I post this question. Inputs being considered are the same both questions, but outputs are different. I tried to use that solution and played around, but could not solve my specific problem. In my case I am calling a third party API (Xero api) and I want to build this output specifically.

Community
  • 1
  • 1
Dhanuka777
  • 7,389
  • 5
  • 61
  • 112
  • what provider you use? linq to objects or something else? – Grundy Nov 10 '15 at 07:05
  • Try using Dynamic LINQ. – Aliasgar Rajpiplawala Nov 10 '15 at 07:06
  • 2
    Possible duplicate of [Dynamic LINQ OrderBy on IEnumerable](http://stackoverflow.com/questions/41244/dynamic-linq-orderby-on-ienumerablet) – Grundy Nov 10 '15 at 07:06
  • This method is inside an API that I am writing. User will pass any property (string) and I need to convert into objects and do the sorting. Well I can use bunch of if conditions (if propname.ToLower() == "amountdue" then a=>a.AmountDue). But I am trying to avoid this nested if conditions if I can use generics or some other approach to convert the string to nessasary objects. – Dhanuka777 Nov 10 '15 at 07:08
  • I cannnot use Dynamic Linq as I am calling a third party API which does not accept dynamic linq input. – Dhanuka777 Nov 10 '15 at 21:36

4 Answers4

2

When sorting you have to return property value, not type or property itself

  String propertyName = "AmountDue";

  var result = _context
    .Invoices
    .Find()
    .Where(c => c.Contact.Id == contactId)
    .OrderBy(item => typeof(Invoice).GetProperty(
         propertyName, 
         BindingFlags.Public | BindingFlags.Instance)
       .GetValue(item))
    .ToList();
Dmitry Bychenko
  • 149,892
  • 16
  • 136
  • 186
1

You can pass the object to the reflection function and return the appropriate property, something like this:

.OrderBy(o => keySelector(o, propertyName))

public static Type VariableType(object o, string prop)
{
    Type type = typeof(TResource);
    PropertyInfo pi = type.GetProperty(prop, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
    return pi.GetValue(o);
}

But I don't think this would work on IQueryable though, because it cannot translate the function into SQL. So you'd have to first execute the query (e.g. with ToList())

_context.Invoices.Find().Where(c => c.Contact.Id == contactId).ToList().OrderBy(o => keySelector(o, propertyName)).ToList();

Using dynamic LINQ as Grundy suggested in a comment would probably be the better solution.

Domysee
  • 11,862
  • 10
  • 48
  • 75
0

You probably would also want to be able to switch between the Sorting Directions (Ascending, Descending). This would bring you to another problem. With some little changes, you should be able to adapt your solution from this answer.

The usage is then pretty simple, just call the Extension method OrderBy<T> on your collection. This is also where the piece of code is which you were looking for.

Community
  • 1
  • 1
thmshd
  • 5,473
  • 2
  • 34
  • 61
  • Yes, you are correct. Next issue is the order by method. But I can live with one if-else condition since I can dynamically build the hard part. – Dhanuka777 Nov 10 '15 at 21:28
0

Instead of reflection you could do a simple lookup in a dictionary.

static readonly Dictionary<string, Func<Invoice, decimal>> keySelectors = ...;

On startup (or using Lazy) you initialize all the keySelectors:

keySelectors.Add("AmountDue", a=> a.AmountDue);
// add all 20 options here

And now each time you need one it's just a fast dictionary lookup.

Advantages:

  • Decouples the string used to connect the UI and the business logic from the field names allowing them to be safely refactored.
  • Is easy for other developers on team to understand and maintain.
Ian Mercer
  • 35,804
  • 6
  • 87
  • 121