【Linq】表达式中And、Or和AndAlso、OrElse的区别

前言

最近在EFCore中,做一个动态拼接日期的操作,在查看EFCore生成的sql语句时,发现写的判断都被转换为了bit位,然后才去比较结果,感觉很别扭,而且担心这种bit位判断会影响速度,随后开始百度Linq的表达式区别

微软文档

下面几个链接是微软对And、Or和AndAlso、OrElse的解释,感觉看着有些干硬,没法接地气的理解意思

https://docs.microsoft.com/zh-cn/dotnet/api/system.linq.expressions.expression.and?view=net-6.0

https://docs.microsoft.com/zh-cn/dotnet/api/system.linq.expressions.expression.andalso?view=net-6.0

https://docs.microsoft.com/zh-cn/dotnet/api/system.linq.expressions.expression.or?view=net-6.0

https://docs.microsoft.com/zh-cn/dotnet/api/system.linq.expressions.expression.orelse?view=net-6.0

总结

简单描述

AndAlso,OrElse相对于And,Or最大的特性是会自动实现“最短路径”。
所谓“最短路径”就是指:当第一个被比较的运算式的结果已经能决定运算的最终结果时,就不会再去比较其他运算式,因此可以避免掉额外且不需要的比较运算式。

Linq生成bit位的解释

原因在于:微软文档描述,创建的是一个按位运算的AND

所以Linq中写的每一个lambda表达式,最后转换sql时都有个case when 转换bit的操作

而使用AndAlsoOrElse这2个方法替代AndOr方法,会因为实现"最短路径",把一些不需要计算的比较运算式剔除掉,最后才会生成我们熟悉的sql语句,没有那些多余的case when 转换bit操作

例子

And和AndAlso

            int a = 1;
            int b = 1;
            (1)对于And,会对所有的表达式都进行判断。1==2、a==1、b==2,这3个表达式都会计算一遍,最后再取逻辑真
            if(1 == 2 And a == 1 And b == 2)
                return true;
            else
                return false;
            (2)对于AndAlso,会从第一个表达式开始计算。也就是先计算1==2,这里已经是逻辑假,所以后面的a==1、b==2就不会再计算了
            if (1 == 2 AndAlso a == 1 AndAlso b == 2)
                return true;
            else
                return false;

Or和OrElse

            int a = 1;
            int b = 1;
            (1)对于Or,会对所有的表达式都进行判断。1==1、a==1、b==2,这3个表达式都会计算一遍,最后再取逻辑假
            if(1 == 1 Or a == 1 Or b == 2)
                return true;
            else
                return false;
            (2)对于OrElse,会从第一个表达式开始计算。也就是先计算1==1,这里已经是逻辑真,所以后面的a==1、b==2就不会再计算了
            if (1 == 1 OrElse a == 1 OrElse b == 2)
                return true;
            else
                return false;

对比EFCore生成的sql语句

And

(1)代码

                var exp = PredicateBuilder.True<Users>();
                exp = exp.And(x => x.Age > 0);
                exp = exp.And(x => x.Age < 20);
                exp = exp.And(x => true);

                var data = db.Users
                    .Where(exp)
                    .ToList();

 

(2)生成的sql

SELECT [u].[Id], [u].[Age], [u].[CreateTime], [u].[Gender], [u].[Name]
FROM [Users] AS [u]
WHERE (((CAST(1 AS bit) & CASE
    WHEN [u].[Age] > 0 THEN CAST(1 AS bit)
    ELSE CAST(0 AS bit)
END) & CASE
    WHEN [u].[Age] < 20 THEN CAST(1 AS bit)
    ELSE CAST(0 AS bit)
END) & CAST(1 AS bit)) = CAST(1 AS bit)

 

AndAlso

(1)代码

                var exp = PredicateBuilder.True<Users>();
                exp = exp.AndAlso(x => x.Age > 0);
                exp = exp.AndAlso(x => x.Age < 20);
                exp = exp.AndAlso(x => true);

                var data = db.Users
                    .Where(exp)
                    .ToList();

 

(2)生成的sql

SELECT [u].[Id], [u].[Age], [u].[CreateTime], [u].[Gender], [u].[Name]
FROM [Users] AS [u]
WHERE ([u].[Age] > 0) AND ([u].[Age] < 20)

 

Or

(1)代码

                var exp = PredicateBuilder.False<Users>();
                Dictionary<DateTime, DateTime> lstDate = new Dictionary<DateTime, DateTime>
                {
                    { new DateTime(2022, 6, 12), new DateTime(2022, 6, 13) },
                    { new DateTime(2022, 6, 20), new DateTime(2022, 6, 21) }
                };

                foreach (var item in lstDate)
                {
                    DateTime beginDate_Many = item.Key;
                    DateTime endDate_Many = item.Value.AddDays(1);

                    exp = exp.Or(x => x.CreateTime >= beginDate_Many && x.CreateTime < endDate_Many);
                }

                var data = db.Users
                    .Where(exp)
                    .ToList();

 

(2)生成的sql

exec sp_executesql N'SELECT [u].[Id], [u].[Age], [u].[CreateTime], [u].[Gender], [u].[Name]
FROM [Users] AS [u]
WHERE ((CAST(0 AS bit) | CASE
    WHEN ([u].[CreateTime] >= @__beginDate_Many_0) AND ([u].[CreateTime] < @__endDate_Many_1) THEN CAST(1 AS bit)
    ELSE CAST(0 AS bit)
END) | CASE
    WHEN ([u].[CreateTime] >= @__beginDate_Many_2) AND ([u].[CreateTime] < @__endDate_Many_3) THEN CAST(1 AS bit)
    ELSE CAST(0 AS bit)
END) = CAST(1 AS bit)',N'@__beginDate_Many_0 datetime2(7),@__endDate_Many_1 datetime2(7),@__beginDate_Many_2 datetime2(7),@__endDate_Many_3 datetime2(7)',@__beginDate_Many_0='2022-06-12 00:00:00',@__endDate_Many_1='2022-06-14 00:00:00',@__beginDate_Many_2='2022-06-20 00:00:00',@__endDate_Many_3='2022-06-22 00:00:00'

 

OrElse

(1)代码

                var exp = PredicateBuilder.False<Users>();
                Dictionary<DateTime, DateTime> lstDate = new Dictionary<DateTime, DateTime>
                {
                    { new DateTime(2022, 6, 12), new DateTime(2022, 6, 13) },
                    { new DateTime(2022, 6, 20), new DateTime(2022, 6, 21) }
                };

                foreach (var item in lstDate)
                {
                    DateTime beginDate_Many = item.Key;
                    DateTime endDate_Many = item.Value.AddDays(1);

                    exp = exp.OrElse(x => x.CreateTime >= beginDate_Many && x.CreateTime < endDate_Many);
                }

                var data = db.Users
                    .Where(exp)
                    .ToList();

 

(2)生成的sql

exec sp_executesql N'SELECT [u].[Id], [u].[Age], [u].[CreateTime], [u].[Gender], [u].[Name]
FROM [Users] AS [u]
WHERE (([u].[CreateTime] >= @__beginDate_Many_0) AND ([u].[CreateTime] < @__endDate_Many_1)) OR (([u].[CreateTime] >= @__beginDate_Many_2) AND ([u].[CreateTime] < @__endDate_Many_3))',N'@__beginDate_Many_0 datetime2(7),@__endDate_Many_1 datetime2(7),@__beginDate_Many_2 datetime2(7),@__endDate_Many_3 datetime2(7)',@__beginDate_Many_0='2022-06-12 00:00:00',@__endDate_Many_1='2022-06-14 00:00:00',@__beginDate_Many_2='2022-06-20 00:00:00',@__endDate_Many_3='2022-06-22 00:00:00'

 

 测试使用的类

Linq扩展类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace Test.EFCoreLinq
{
    public class ParameterRebinder : ExpressionVisitor
    {
        private readonly Dictionary<ParameterExpression, ParameterExpression> map;

        public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
        {
            this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
        }

        public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
        {
            return new ParameterRebinder(map).Visit(exp);
        }

        protected override Expression VisitParameter(ParameterExpression p)
        {
            if (map.TryGetValue(p, out ParameterExpression replacement))
            {
                p = replacement;
            }
            return base.VisitParameter(p);
        }
    }

    public static class PredicateBuilder
    {

        public static Expression<Func<T, bool>> True<T>() { return f => true; }
        public static Expression<Func<T, bool>> False<T>() { return f => false; }
        public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
        {
            // build parameter map (from parameters of second to parameters of first)  
            var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);

            // replace parameters in the second lambda expression with parameters from the first  
            var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);

            // apply composition of lambda expression bodies to parameters from the first expression   
            return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
        }

        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.And);
        }

        public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.AndAlso);
        }

        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.Or);
        }

        public static Expression<Func<T, bool>> OrElse<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.OrElse);
        }
    }
}

Users类

using System;

namespace Test.EFCoreLinq.Entity
{
    public class Users
    {
        /// <summary>
        /// 主键
        /// </summary>
        public string Id { get; set; }
        /// <summary>
        /// 姓名
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 年龄
        /// </summary>
        public int Age { get; set; }
        /// <summary>
        /// 性别
        /// </summary>
        public int Gender { get; set; }
        /// <summary>
        /// 创建时间
        /// </summary>
        public DateTime CreateTime { get; set; }
    }
}

 

posted @ 2022-06-25 10:25  我有我奥妙  阅读(1449)  评论(0编辑  收藏  举报