在 LINQ 查询中使用“包含”时检查列表是否不为 null,否则选择所有记录

本文关键字:null 记录 选择 是否 列表 查询 LINQ 检查 包含 | 更新日期: 2023-09-27 18:30:43

我的应用程序中有一个搜索功能,它有 4 个条件,基本上是位置状态属性类型 [字符串列表] 和价格范围[最小和最高价格],下面是model

public class SearchFilters
{
     public SearchFilters()
     {
          MinPrice = "10000";
          MaxPrice = "8000000";
     }
     public IEnumerable<SelectListItem> Categories { get; set; }
     public string[] CategoriesId { get; set; }
     public IEnumerable<SelectListItem> Locations { get; set; }
     public string[] LocationID { get; set; }
     public IEnumerable<SelectListItem> Status { get; set; }
     public string[] StatusID { get; set; }
     public string MinPrice { get; set; }
     public string MaxPrice { get; set; }
}

当在controllerSelectList中选择的值列表中接收到数据时,将存储在CategoriesIdLocationIDStatusID中。现在,从每个列表中选择值是可选的,它可以是单个或多个。所以我需要过滤数据库,如果用户没有选择任何项目,那么这个List将被null,因为它是一个可选的搜索条件。

例如

状态值可以是"正在进行"、"即将推出"和"已完成"。所以我在下面使用下面的LINQ来提取数据。

[HttpGet]
public ActionResult Search(SearchFilters smodel)
{
     var query=db.tblProperties.Where(p => smodel.StatusID.Contains(p.PropertyLocation)).Select(x=>x).ToList();
     //.....
     //.....
}

刚刚添加了一个属性比较来演示

这将毫无问题地返回记录,但如果出现此smodel.StatusID null即用户未选择任何值,则此查询将失败并显示exception。那么如何克服这个问题呢?还有,如何在没有选择任何值时获取所有records?经历了this post但那里的解决方案对克服这个问题没有用?基本上,在这些情况下如何合并搜索查询?

在 LINQ 查询中使用“包含”时检查列表是否不为 null,否则选择所有记录

发布的答案是正确的,并为您提供了所需的解决方案,如果您在几个地方需要此行为,我将检查 null。如果此请求在许多地方重复,我将采用以下解决方案。

如果您正在执行大量此类检查,还有另一种更干净的方法是添加扩展方法来为您完成。

扩展方法使您能够将方法"添加"到现有类型 无需创建新的派生类型、重新编译或其他方式 修改原始类型。扩展方法是一种特殊的 静态方法,但它们被调用,就好像它们是实例方法一样 扩展类型。对于用 C# 和 Visual Basic 编写的客户端代码, 调用扩展方法之间没有明显区别 以及在类型中实际定义的方法。

法典:

public static class CollectionExtension
{
    public static bool CheckContainsIfHasValue<T>(this IEnumerable<T> source, T value)
    {
        return source == null || source.Contains(value);
    }
}

用法:

var query = db.tblProperties
            .Where(p => smodel.StatusID.CheckContainsIfHasValue(p.PropertyLocation))
            .ToList();

那么如果smodel.StatusID为空,你想返回所有记录吗?

var query=db.tblProperties.Where(p => smodel.StatusID == null || smodel.StatusID.Contains(p.PropertyLocation))
                          .Select(x=>x).ToList();

因此,如果你现在看Where条款,如果smodel.StatusID == null,那么每个项目都将通过Where条款。请注意,由于快捷方式,不会调用.Contains(如果OR的第一个项为真,则评估第二个项没有意义,因此不会)。

您也可以考虑执行以下操作:

.Where(p => smodel.StatusID == null || 
            !smodel.StatusID.Any() || 
            smodel.StatusID.Contains(p.PropertyLocation))

这样,您既要检查StatusID是否为空,又检查集合StatusID是否为空。

如果可以将StatusID设置为默认为空集合而不是 null(例如,在 smodel 的任何类的构造函数中设置它),则可以执行以下操作:

.Where(p => !smodel.StatusID.Any() || 
        smodel.StatusID.Contains(p.PropertyLocation))

由于您不再需要null检查,并且Any应该可以转换为 LINQ to SQL。

另一种选择是在查询之外执行null检查

var initialQuery = db.tblProperties;
if(smodel.StatusID != null)
{
    initialQuery = initialQuery.Where(p => smodel.StatusID.Contains(p.PropertyLocation));
}
var query = initialQuery.ToList();

或作为辅助方法

public static IEnumerable<T> ConditionalWhere<T>(
    this IEnumerable<T> collection, 
    Func<bool> condition, 
    Expression<Func<T, bool>> predicate)
{
    if(condition())
        return collection.Where(predicate);
    return collection;
}

然后

var query = db.tblProperties.ConditionalWhere( 
    () => smodel.StatusID != null,
    p => smodel.StatusID.Contains(p.PropertyLocation));

你可以把它们链接在一起

var query = db.tblProperties.ConditionalWhere( 
        () => smodel.StatusID != null,
        p => smodel.StatusID.Contains(p.PropertyLocation))
    .ConditionalWhere( 
        () => someOtherCollection != null,
        p => someOtherCollection.Contains(p.PropertyLocation));

这将避免多次运行 Linq-to-Objects 的condition,并允许您使用无法转换为 SQL for EF 或 Linq-to-SQL 的内容。

您可以简单地在 where 子句中添加一个空检查:

 var query=db.tblProperties.Where(p => smodel.StatusID == null || smodel.StatusID.Contains(p.PropertyLocation))
                           .Select(x=>x).ToList();

C# 使用布尔表达式的短路计算。这意味着 C# 在确定表达式结果后立即停止计算表达式。

例如在a && b中,如果a false,则已知结果是false的,因此b不会被评估。在a || b已知结果是true如果atrue,所以b不会被评估。

您可以使用它通过添加 null 测试来保护您免受异常的影响:

var query = db.tblProperties
    .Where(p => smodel.StatusID == null || 
                smodel.StatusID.Contains(p.PropertyLocation))
    .ToList();

您也可以删除.Select(x=>x)部分,因为它什么都不做。


如果使用 LINQ to EF,则上述文本不适用。不能对集合执行空检查,因为这不能转换为 SQL。而是在以下之前进行检查:

bool ignore = smodel.StatusID == null || !smodel.StatusID.Any();
var query = db.tblProperties
    .Where(p => ignore || 
                smodel.StatusID.Contains(p.PropertyLocation))
    .ToList();