在Method参数中包含OrderBy委托

时间:2011-09-20 03:48:57

标签: c# lambda

我有一个方法;

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

返回项目列表。假设我现在想要指定我想要返回列表的排序顺序。

所以我可能会根据Name,DueDate,Completed等等进行排序。

我如何在方法中包含它作为参数?我不想使用switch语句而是想尽可能使用lambda。

因此;

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

或者这是错误的方法。

4 个答案:

答案 0 :(得分:10)

我认为你的方法是使用LINQ的错误方法

LINQ使用延迟执行模型是有原因的。它允许您将一系列操作链接在一起,只有当您告诉它计算结果时才会执行 - 通常使用.ToList().ToArray().First() - 但您也可以强制执行通过使用OrderBy作为参数的Func<T, ?>子句进行过滤。

现在你要返回一个List<Task>,这意味着你已经强制执行 - 当你准备好使用结果时这是正确的事情 - 但是如果你继续做进一步的话操作可能会将更多记录加载到内存中而不是您需要的。

你当然可以这样做:

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

要在数据库中执行,您需要更改它:

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
}

但问题是,如果你这样做了:

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

因为你的GetTasksByAssignedTo将记录带入内存,所以你在内存中进行连接。 (是的,查询有点人为,但原则很稳固。)

在数据库中执行此操作通常要好得多。

以下是修复方法:

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

现在上述查询将不会执行,直到您执行query.ToList()并且所有内容都将在数据库中发生。

但我有一个更大的问题。

您在GetTasksByAssignedTo隐藏了大量信息。使用代码的人不知道他们在读取代码时实际上是在获取列表,而他们实际上并不知道实际的实现是否做得对。我认为,对于这些类型的查询,通常最好将其保留为简单的LINQ。

比较这些:

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

第一个查询tasks1并不算太差,但它没有告诉你返回类型是什么;

第二个查询tasks2对某些t和属性Name执行某项操作,但不会告诉您什么。

第三个查询tasks3给你一个提示,它正在排序降序,但不会告诉你它是否是由神秘的Name属性或其他东西。

第四个查询tasks4告诉您需要知道的所有内容 - 它是ContactId过滤任务,按Name反向排序结果,最后返回列表。

现在,看看这个查询:

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;

我可以很容易地阅读它,看看它在做什么。试想一下这个帮助方法的名称是什么!或者需要帮助方法的疯狂嵌套。

底线是LINQ是用于查询的API。

如果您迫切想要创建辅助方法,请使用扩展方法

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

这允许你写这个:

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

清晰,简洁,可扩展,可组合,可重复使用,并且可以控制执行时间

答案 1 :(得分:6)

您可以将Func<Task, object>传递给您的方法进行订购:

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

现在您可以像

一样调用您的方法
Func<Task, object> someOrder = (Task t) => t.DueDate;
List<Task> list = GetTasksByAssignedTo(someGuid, someOrder);

一般来说,我同意这些意见 - 对于名为GetTasksByAssignedTo的方法,似乎不需要

答案 2 :(得分:2)

@BrokenGlass打败了我。

另一种选择是使用隐藏开关的扩展方法,并将不同的排序选项表示为枚举。

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

然后:

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

我一直这样做。允许谓词作为方法参数可能很棘手,因为如果你想进行升序/降序会发生什么?您需要另一个参数(bool),然后执行if / else检查以执行OrderByOrderByDescending

隐藏过滤器中的逻辑,然后您可以在应用中的任何位置重复使用它。

答案 3 :(得分:-1)

试试这个...输入参数是字符串。这是根据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);
    }

}