繁体   English   中英

LINQ:仅当值不为空时才添加 where 子句

[英]LINQ: adding where clause only when a value is not null

我知道一个典型的方式是这样的:

IQueryable query = from staff in dataContext.Staffs;
if(name1 != null)
{
     query = from staff in query where (staff.name == name1);
}

但是,从我们从其他开发人员那里接手的程序中,我们看到了这样的代码:

IQueryable query = from staff in dataContext.Staffs;
query = from staff in query where (name1 == null || staff.name == name1);

如果这是一条普通的 SQL 语句,我肯定会说第二条是不好的做法。 因为它在 name1 为空时向查询添加了一个无意义的 where 子句。

但我是 LINQ 的新手,所以我不确定 LINQ 是否不同?

你可以这样写

IQueryable query = from staff in dataContext.Staffs;
query = from staff in query where (name1 != null && staff.name == name1);

这样,如果您的第一个条件评估为 false,则不会评估您的条件的第二部分

更新:
如果你写

IQueryable query = from staff in dataContext.Staffs;
    query = from staff in query where (name1 == null || staff.name == name1);

并且 name1 为 null 您的条件的第二部分将不会被评估,因为 or 条件只需要一个条件才能返回 true

请参阅此链接以获取更多详细信息

通常,使用流利的语法而不是查询语法来编写这种东西感觉更流畅。

例如

IQueryable query = dataContext.Staffs;
if(name1 != null)
{
     query = query.Where(x => x.name == name1);
}

因此,如果name1为空,您就无需执行任何Where()调用。 如果您有多个不同的过滤器,所有这些可能需要也可能不需要,并且可能有各种不同的排序顺序,我发现这变得更易于管理。

为 alex 编辑:好的,我正在回答有关仅在值不为空时添加 where 子句的问题。 为了回答问题的另一部分,我使用 Entity Framework 4 进行了尝试,以查看 LINQ 生成的 SQL。 为此,您可以将query转换为ObjectQuery并调用.ToTraceString() 结果是WHERE子句出来如下:

WHERE @p__linq__0 IS NULL OR [Extent1].[name] = @p__linq__1

所以,是的,这是经典的坏 SQL,如果你在name列上有索引,不要指望它会被使用。

编辑 #2:使用 LINQ to SQL 而不是 Entity Framework 再次尝试,结果完全不同。 这一次,尝试name1为 null 的查询根本不会产生WHERE子句,正如您所希望的那样; 尝试将name1 “a”会导致一个简单的WHERE [t0].[name] = @p0@p0作为“a”发送。 实体框架似乎没有因此优化。 这有点令人担忧。

最好的方法是为自己创建一个扩展方法,该方法将接受条件语句和 where 表达式。 如果条件为真,那么它将使用 where 表达式,否则将不使用它。 这可以显着清理您的代码,消除对 if 语句的需要。

public static class LinqExtensions
{
    public static IQueryable<T> WhereIf<T>(this IQueryable<T> query, bool condition, Expression<Func<T, bool>> whereClause)
    {
        if (condition)
        {
            return query.Where(whereClause);
        }
        return query;
    }
}

现在您可以像这样编写代码:

IQueryable<Staffs> query = dataContext.Staffs.AsQueryable().WhereIf(name1 != null, x => x.Name == name1);

因此,我尝试了此处列出的.Where(..., x => ...)扩展方法作为答案,但它不适用于实体框架,因为 Linq To Entities 不知道如何将其转换为 TSQL。

所以这是我的解决方案让我的 Func 开启:

Expression<Func<SomeEfPoco, bool>> columnBeingFilteredPredicate = x => true; // Default expression to just say yes
if (!string.IsNullOrWhiteSpace(someColumnBeingFilteredValue))
{
    columnBeingFilteredPredicate = x => x.someColumnBeingFiltered == someColumnBeingFilteredValue;
}

_context.SomeEfPocos.Where(x => ..... &&
            ..... &&
            ..... &&)
.Where(columnBeingFilteredPredicate);

在我的情况下, someColumnBeingFilteredValue是封装方法上的字符串参数,默认值为 NULL。

我喜欢使用表达式,例如

    Expression<Func<Persons, bool>> expresionFinal = c => c.Active == true;

    if (DateBirth.HasValue)
                {
                    Expression<Func<Persons, bool>> expresionDate = c => (EntityFunctions.TruncateTime(c.DateBirth) == DateBirth);
                    expresionFinal = PredicateBuilder.And(expresionFinal, expresionDate);
                }

IQueryable query = dataContext.Persons;
 query = query.Where(expresionFinal);

对于 EF Core,我将其分解为:

IQueryable<Partners> recs = contextApi.Partners;
if (status != -1)
{
   recs = recs.Where(i => i.Status == status);
}
recs = recs.OrderBy(i => i.Status).ThenBy(i => i.CompanyName);
foreach (var rec in recs)
{
}

我必须明确输入而不是依赖var

LINQ 在其他一些原因上有所不同(不是在这个原因中),LINQ 是一种以“更快的方式”获取数据的方式,使用尽可能少的代码和清晰的代码,LINQ 有很多好处:

  1. 更容易将数据转换为对象。 我相信您已经听说过经常使用“阻抗不匹配”这个术语,这意味着 LINQ 减少了在面向对象代码和数据范例(例如分层、平面文件、消息、关系,等等。 它并没有消除“阻抗不匹配”,因为您仍然必须以原始形式对数据进行推理,但是从这里到那里的桥梁(IMO)要短得多。

  2. 所有数据的通用语法。 一旦学习了查询语法,就可以将它与任何 LINQ 提供程序一起使用。 我认为这是一个比多年来随着数据访问技术而发展起来的巴别塔更好的开发范式。 当然,每个 LINQ 提供程序都有必要的独特细微差别,但基本方法和查询语法是相同的。

  3. 强类型代码。 C#(或 VB.NET)查询语法是语言的一部分,您使用 C# 类型进行编码,这些类型被翻译成提供者可以理解的内容。 这意味着您可以让编译器在开发生命周期中比其他地方更早地发现错误,从而提高工作效率。 当然,存储过程语法中的许多错误会在您保存时产生错误,但 LINQ 比 SQL Server 更通用。 您必须考虑生成运行时错误的所有其他类型的数据源,因为它们的查询是由字符串或其他一些松散类型的机制形成的。

  4. 提供商集成。 将数据源整合在一起非常容易。 例如,对于一些非常复杂的场景,您可以一起使用 LINQ to Objects、LINQ to SQL 和 LINQ to XML。 我认为它非常优雅。

  5. 减少工作。 在 LINQ 之前,我花了很多时间构建 DAL,但现在我的 DataContext 是 DAL。 我也使用过 OPF,但现在我有 LINQ,它附带多个提供程序和许多其他 3rd 方提供程序,让我从之前的观点中受益。 我可以在一分钟内设置一个 LINQ to SQL DataContext(我的计算机和 IDE 可以跟上的速度)。

  6. 一般情况下的性能不会成为问题。 SQL Server 现在可以很好地优化查询,就像存储过程一样。 当然,仍然存在出于性能原因需要存储过程的情况。 例如,当我在表之间进行多次交互时,我发现使用存储过程会更聪明,并且事务内部有额外的逻辑。 除了让 DTC 参与分布式事务之外,尝试在代码中执行相同任务的通信开销使存储过程的选择更具吸引力。 但是,对于在单个语句中执行的查询,LINQ 是我的首选,因为即使存储过程的性能提升很小,前面几点 (IMO) 的好处也更重要。

  7. 内置安全性。 在 LINQ 之前我更喜欢存储过程的一个原因是它们强制使用参数,有助于减少 SQL 注入攻击。 LINQ to SQL 已经参数化输入,这同样安全。

  8. LINQ 是声明性的。 使用 LINQ to XML 或 LINQ to SQL 非常关注,但 LINQ to Objects 非常强大。 LINQ to Objects 的一个典型示例是从 string[] 中读取项目。 然而,这只是一个小例子。 如果您考虑您每天使用的所有 IEnumerable 集合(您也可以查询 IEnumerable),那么机会很多。 即在 ASP.NET ListBox 控件中搜索选定项,对两个集合执行集合操作(​​例如 Union),或者遍历 List 并在每个项的 ForEach 中运行 lambda。 一旦您开始使用本质上是声明性的 LINQ 进行思考,您会发现您的许多任务比您今天使用的命令式技术更简单、更直观。

我可能可以继续,但我最好停在那里。 希望这将为您如何提高使用 LINQ 的工作效率提供更积极的观点,并可能从更广泛的角度将其视为一种有用的技术。

我在标准 SQL 中看到过这种模式,如果您有几个可能为 NULL 的参数,它似乎很有用。 例如:

SELECT * FROM People WHERE ( @FirstName IS NULL OR FirstName = @FirstName )
                       AND ( @LastName IS NULL OR LastName = @LastName )

如果您在 LINQ 中看到这一点,那么他们可能只是盲目地翻译了他们的旧 SQL 查询。

我喜欢扩展的想法

public static IQueryable<T> WhereIf<T>(this IQueryable<T> query, bool condition, Expression<Func<T, bool>> whereClause)
        => condition ? query.Where(whereClause) : query;

不,我不太同意你的观点。 在这里你只是给出了一个简单的逻辑

if(name1 != null)
// do your stuff

但是,如果您对具有空值的 name1 执行不同的操作会发生什么......!! 好的,现在考虑这种情况。 在此示例中,您将展示如何处理源集合中可能的空值。 IEnumerable<T>这样的对象集合可以包含值为 null 的元素。 如果源集合为 null 或包含值为 null 的元素,并且您的查询不处理 null 值,则执行查询时将引发NullReferenceException

可能这可能是一个问题......

我使用下面的扩展方法。 它不如其他答案中的 WhereIf 扩展灵活,但使用起来更短。

public static IQueryable<T1> FilterBy<T1, T2>(this IQueryable<T1> query, T2 expectedValue, Expression<Func<T1, T2>> propertyAccessor)
{
    if (propertyAccessor == null) throw new ArgumentNullException(nameof(propertyAccessor));
    if (expectedValue == null) return query;
    var equalExpr = Expression.Equal(propertyAccessor.Body, Expression.Constant(expectedValue, typeof(T2)));
    var lambda = Expression.Lambda<Func<T1, bool>>(equalExpr, propertyAccessor.Parameters);
    return query.Where(lambda);
}

它可以像这样使用:

var query = dataContext.Staffs.FilterBy(name, s => s.Name);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM