仅当condition不为null时才通过Where子句进行过滤

时间:2012-02-09 04:01:07

标签: c# asp.net linq

我有这种控制器方法:

public ActionResult Index(string searchError)
{
    // get all errors
    var viewModel = _errorsRepository.Errors.OrderByDescending(e => e.TimeUtc).
                            Select(e => new ErrorViewModel
                                            {
                                                ErrorId = e.ErrorId,
                                                Message = e.Message,
                                                TimeUtc = e.TimeUtc
                                            });

    if (!String.IsNullOrEmpty(searchError))
            viewModel = viewModel.Where(e => e.Message.ToLower().Contains(searchError.Trim().ToLower()));

    return View(viewModel);
}

我认为执行额外的过滤器会减慢所有内容,我想知道是否可以将Where子句添加到Select语句并检查searchError是否为null内联。

这可能吗?

3 个答案:

答案 0 :(得分:7)

由于Linq是懒惰的,只要你没有执行你的查询(例如通过迭代结果或使用ToList()强制执行急切执行),你是否有“一个大的语句”或多个并不重要因为你只是链接扩展方法,所以没有惩罚。在这方面,我将重点放在可读性上。

但有些事情需要考虑,例如排序不能是懒惰的(你必须先查看所有项目然后才能按顺序吐出项目) - 这就是为什么你应该总是将Where过滤器放在OrderBy之前,这样你就可以减少排序的项目。这说我会重构你的代码:

// get all errors
var viewModel = _errorsRepository.Errors;

// optionally filter            
if (!String.IsNullOrEmpty(searchError))
{
    string searchErrorMatch = searchError.Trim().ToLower();
    viewModel = viewModel.Where(e => e.Message.ToLower().Contains(searchErrorMatch));
}

//order and project to ErrorViewModel
viewModel = viewModel.OrderByDescending(e => e.TimeUtc)
                     .Select(e => new ErrorViewModel
                      {
                          ErrorId = e.ErrorId,
                          Message = e.Message,
                          TimeUtc = e.TimeUtc
                      }).ToList();

另请注意,我从lambda中取出searchError.Trim().ToLower()并将其分配给变量一次 - 否则每次执行 评估lambda时,这是非常不必要的工作。

最终修改:我还在投影后添加了ToList()来执行查询 - 否则您的查询确实将从您的视图中执行,这通常是由于多种原因导致的,例如:您必须保持数据库上下文的活动时间可能更长,并且您违反了关注点的分离 - 视图应仅关注视图模型,而与获取数据无关。

答案 1 :(得分:1)

如果确实想要在没有if语句的情况下一次性构建查询,您可以像这样写出来......

public ActionResult Index(string searchError)
{
    // get all errors
    var viewModel = _errorsRepository.Errors.OrderByDescending(e => e.TimeUtc)
        .Where(e => String.IsNullOrEmpty(searchError)
                    || e.Message.ToLower().Contains(searchError.Trim().ToLower())
        ).Select(
            e => new ErrorViewModel {
                ErrorId = e.ErrorId,
                Message = e.Message,
                TimeUtc = e.TimeUtc
            }
        );

    return View(viewModel);
}

...但是在searchErrornull或为空的情况下,您已经引入了必须为结果集中的每个项调用的额外委托。最好让代码保持原样,或者像BrokenGlass建议的那样,首先进行过滤,然后对剩余的项目进行排序,投影等。这实际上是关于LINQ的一个非常酷的事情是能够动态地插入不同的方法并仅使用你真正需要的部分组成查询,并且它都是懒惰的评估(尽可能最好)!

答案 2 :(得分:0)

如果searchError为null,则表示FALSE,我们不需要传递第二个条件(快捷方式)。 如果searchError不为null,则表示TRUE,但第二个条件必须为TRUE才能将此WHERE条件设为TRUE。

viewModel = viewModel.Where(e => 
                   !String.IsNullOrEmpty(searchError) 
                   &&
                   e.Message.ToLower().Contains(searchError.Trim().ToLower()));