Include OrderBy delegate in method parameters

I have a method;

public List<Task> GetTasksByAssignedTo(Guid contactId) { List<Task> tasks = dc.Tasks.Where(x => x.ContactId == contactId).ToList(); return tasks; } 

which returns a list of items. Let's say now I want to specify the sort order in which I want to return the list.

So, I can sort by name, DueDate, Completed, etc. etc.

How can I include this in a method as a parameter? I do not want to use the switch statement, but I would like to use lambda, if possible.

So,

 List<Task> list = GetTasksByAssignedTo("guid", ??????); 

Or is this the wrong approach.

+7
source share
4 answers

I think your approach is the wrong way to use LINQ .

LINQ uses a deferred execution model for a specific reason. It allows you to combine a series of operations that are performed only when you tell it to calculate the result - often with .ToList() , .ToArray() , .First() - but you can also force the calculation by filtering with OrderBy , whose parameter is Func<T, ?> .

Now you are returning a List<Task> , which means that you forced the execution - what to do when you are ready to use the results - but if you then continue with further operations, they potentially load many more records into memory than you need.

You could do this:

 public List<Task> GetTasksByAssignedTo<P>(Guid contactId, Func<Task, P> orderBy) { return dc.Tasks .Where(x => x.ContactId == contactId) .OrderBy(orderBy) // this forces evaluation - sort happens in memory .ToList(); } 

To execute the execution in the database, you need to modify it as follows:

 public List<Task> GetTasksByAssignedTo<P>( Guid contactId, Expression<Func<Task, P>> orderBy) { return dc.Tasks .Where(x => x.ContactId == contactId) .OrderBy(orderBy) .ToList(); // Now execution happens here } 

But the problem is that if you did this:

 var query = from t1 in GetTasksByAssignedTo(contactId, t => t.Name) join t2 in GetTasksByAssignedTo(contactId, t => t.Name) on t1.Name equals t2.Name select new { t1, t2 }; 

Since your GetTasksByAssignedTo brings writes to memory, you are making a connection in memory. (Yes, the request is a little inventive, but the principle is solid, though.)

It is often much better to do this in a database.

Here's how to fix it:

 public IQueryable<Task> GetTasksByAssignedTo<P>( Guid contactId, Expression<Func<Task, P>> orderBy) { return dc.Tasks .Where(x => x.ContactId == contactId) .OrderBy(orderBy); } 

Now the above query will not be executed until you execute query.ToList() and everything will happen in the database.

But I have a bigger problem.

You hide a lot of information in GetTasksByAssignedTo . Someone using the code doesn't know that they really get the list when they read the code, and they really don't know if the real implementation is doing the right thing. I think for these types of queries it is often better to leave it as normal LINQ.

Compare these:

 var tasks1 = GetTasksByAssignedTo(contactId); var tasks2 = GetTasksByAssignedTo(contactId, t => t.Name); var tasks3 = GetTasksByAssignedToDescending(contactId, t => t.Name); var tasks4 = ( from t in dc.Tasks where t.ContactId == contactId orderby t.Name descending select t ).ToList(); 

The first tasks1 request tasks1 not so bad, but it does not tell you what the return type is;

The second tasks2 request does something with some t and the Name property, but does not tell you that.

The third tasks3 query gives you a hint that it sorts in descending order, but does not tell you if this is a mysterious property of Name or something else.

The fourth tasks4 request tells you everything you need to know - it filters tasks on ContactId , the reverse order of the results on Name and finally returns a list.

Now take a look at this query:

 var query2 = from t1 in dc.Tasks where t1.ContactId == contactId join t2 in dc.Tasks on t1.Name equals t2.Name where t2.ContactId != contactId orderby t2.Name descending select t2; 

I can read it quite easily and see what it does. Imagine what the name of the helper method will be for this! Or what a crazy investment of helper methods would be required.

As a result, LINQ is an API for queries.

If you desperately want to create helper methods, use extension methods .

 public static class TaskEx { public static IQueryable<Task> WhereAssignedTo(this IQueryable<Task> tasks, Guid contactId) { return tasks.Where(t => t.ContactId == contactId); } public static IQueryable<Task> OrderByName(this IQueryable<Task> tasks) { return tasks.OrderBy(t => t.Name); } } 

This allows you to write the following:

 var tasks = dc.Tasks .WhereAssignedTo(contactId) .OrderByName() .ToList(); 

And it is clear, concise, extensible, compound, reusable, and you control it during execution .

+10
source

You can pass Func<Task, object> your order method:

 public List<Task> GetTasksByAssignedTo(Guid contactId, Func<Task, object> someOrder) { List<Task> tasks = dc.Tasks.Where(x => x.ContactId == contactId) .OrderBy(someOrder) .ToList(); return tasks; } 

Now you can call your method, for example

 Func<Task, object> someOrder = (Task t) => t.DueDate; List<Task> list = GetTasksByAssignedTo(someGuid, someOrder); 

In general, I agree with the comments, though - it doesn't seem like ordering is needed for a method named GetTasksByAssignedTo .

+6
source

@BrokenGlass beat me to a beat.

Another option is to use the extension method that the switch hides and present the various order parameters as an enumeration.

 public static IEnumerable<Task> WithOrdering(this IEnumerable<Task> source, TaskOrdering order) { switch (order) { case TaskOrdering.Name: return source.OrderBy(task => task.Name); case TaskOrdering.DueDate: return source.OrderByDescending(task => task.DueDate); } } 

And then:

 public List<Task> GetTasksByAssignedTo(Guid contactId, TaskOrdering order) { List<Task> tasks = dc.Tasks.Where(x => x.ContactId == contactId) .WithOrdering(order) .ToList(); return tasks; } 

I do this all the time. Allowing a predicate as a parameter to a method can be tricky, because what happens if you want to do up / down? To do this, you will need another parameter (bool), then do an if / else check to make OrderByDescending or OrderByDescending .

Hide the logic in the filter, then you can reuse it anywhere in your application.

+2
source

Try this ... the input parameters are on the line. This corresponds to a modified solution from StackOverflow

  /// <summary> /// Sort List<typeparam name="T"></typeparam> objects base on string options /// </summary> /// <param name="SortDirection">Ascending or Descending</param> /// <param name="ColumnName">Column name in complex object (object.ColumnName)</param> public static class ListExtension { public static List<T> SortList<T>(this List<T> data, string sortDirection, string sortExpression) { try { switch (sortDirection) { case "Ascending": data = (from n in data orderby GetDynamicSortProperty(n, sortExpression) ascending select n).ToList(); break; case "Descending": data = (from n in data orderby GetDynamicSortProperty(n, sortExpression) descending select n).ToList(); break; default: data = null; //NUL IF IS NO OPTION FOUND (Ascending or Descending) break; } return data; } catch(Exception ex){ throw new Exception("Unable to sort data", ex); } } private static object GetDynamicSortProperty(object item, string propName) { //Use reflection to get order type return item.GetType().GetProperty(propName).GetValue(item, null); } } 
-one
source

All Articles