C#中的IQueryable vs IEnumerable (二)

news2024/11/19 1:40:20

概要

在前面的文章中,本人曾经分析了IQueryable和 IEnumerable两个接口的异同点。但是整个分析过程,侧重于基本概念层面,本文从设计和代码应用的角度来分析它们的区别。

现象讨论

相比于IEnumerable,IQueryable多了一个Expression属性。

我们在Linq(IEnumerable)和EF(IQueryable)中,都会使用Where 方法,这两个方法从表现上看有些类似。但是它们有一处明显的区别:

IEnumable

public static System.Collections.Generic.IEnumerable Where (this System.Collections.Generic.IEnumerable source, Func<TSource,bool> predicate);

IQueryable

public static System.Linq.IQueryable Where (this System.Linq.IQueryable source, System.Linq.Expressions.Expression<Func<TSource,bool>> predicate);

从上面的定义上,可以看出,上面两个方法的参数类型,不完全相同,一个是委托类型,一个是以委托作为泛型参数的Expression类型。

基本分析

从设计和应用层面,IEnumerable的设计目的是管理内存操作,例如使用Where方法,对内存数据进行过滤。这个时候,只需要传入一个返回bool类型的委托,在迭代过程中调用该委托,根据返回值,过滤掉不需要的元素即可。

这对于IQueryable,显然是不够的。IQueryable要为ORM框架提供一个标准,EF或者NHibernate等三方框架,需要按照这个标准,将IQueryable下面的扩展方法和对应的参数,转换成SQL语句。例如将Where方法和其Predict参数转换成SQL中的where子句。

事实上,IQueryable需要做的是将委托类型的传入参数进行拆解,把它们变成一棵表达式目录树,第三方的框架在遍历这个树的过程中,逐一将内容转换成SQL。

代码举例

在代码层面,无论是IEnumerable和IQueryable,还是它们各自的Where扩展方法,主要区别都集中在Expression上。也就是说IQueryable可以通过Expression,做一些IEnumerable做不了的事情。

下面我们就使用表达式目录树,实现一个简单的SQL翻译过程,从而搞清楚到底IEnumerable为什么不能做这些。

基本案例

我们要在很多种类型的信用卡中找到币种是人民币并且一次刷卡金额在10000以下,或者是不能绑定微信的信用卡。

基本代码如下:

Expression<Func<CreditCard, bool>> whereQuery = el => el.CurrencyType == "RMB"
    && el.AmountLimitation > 10000 
    || el.BindToWeChat == false;

我们按照上述要求构造了Expression。具体CreditCard的定义请见附录。

在我们构造好Expression后,也就生成了一棵表达式目录树,如下所示:
在这里插入图片描述
该树的根节点和分支节点是操作符,叶子节点是表达式。

我们就模仿EF,遍历这课表达式目录树,生成对应SQL,关键代码如下:

我们定义TranslateExpressionToSql类,该类继承自ExpressionVisitor,ExpressionVisitor能帮助我们递归遍历整棵树,我们可以将我们的逻辑嵌入到整个遍历过程中。_sqlAccumulator用于记录生成的SQL语句。

 internal class TranslateExpressionToSql : ExpressionVisitor
    {
        private StringBuilder _sqlAccumulator  = new StringBuilder();
        public string Translate(Expression expression)
        {
            _sqlAccumulator = new StringBuilder(); ;
            this.Visit(expression);
             return " WHERE " +  _sqlAccumulator.ToString();
        }
        protected override Expression VisitBinary(BinaryExpression node)
        {
            _sqlAccumulator.Append("(");
            this.Visit(node.Left);
            switch (node.NodeType)
            {
                case ExpressionType.And:
                    _sqlAccumulator.Append(" AND ");
                    break;
                case ExpressionType.AndAlso:
                    _sqlAccumulator.Append(" AND ");
                    break;
                case ExpressionType.Or:
                    _sqlAccumulator.Append(" OR ");
                    break;
                case ExpressionType.OrElse:
                    _sqlAccumulator.Append(" OR ");
                    break;
                case ExpressionType.Equal: 
                    if (IsNullConstant(node.Right))
                    {
                        _sqlAccumulator.Append(" IS ");
                    }else
                    {
                        _sqlAccumulator.Append(" = ");
                    }
                    break;
                case ExpressionType.NotEqual:
                    if (IsNullConstant(node.Right))
                    {
                        _sqlAccumulator.Append(" IS NOT ");
                    }
                    else
                    {
                        _sqlAccumulator.Append(" <> ");
                    }
                    break;
                case ExpressionType.LessThan:
                    _sqlAccumulator.Append(" < ");
                    break;
                case ExpressionType.LessThanOrEqual:
                    _sqlAccumulator.Append(" <= ");
                    break;
                case ExpressionType.GreaterThan:
                    _sqlAccumulator.Append(" > ");
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    _sqlAccumulator.Append(" >= ");
                    break;
                default:
                    throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", node.NodeType));
            }
            this.Visit(node.Right);
            _sqlAccumulator.Append(")");
            return node;
        }

        protected bool IsNullConstant (Expression exp)
        {
            return (exp.NodeType == ExpressionType.Constant && ((ConstantExpression)exp).Value == null);
        }

        protected override Expression VisitMember(MemberExpression node)
        {
            if (node.Expression != null && node.Expression.NodeType == ExpressionType.Parameter)
            {
                _sqlAccumulator.Append(node.Member.Name);
                return node;
            }
            throw new NotSupportedException(string.Format("The member '{0}' is not supported", node.Member.Name));

        }

        protected override Expression VisitConstant(ConstantExpression node)
        {
            IQueryable q = node.Value as IQueryable;

            if (q == null && node.Value == null)
            {
                _sqlAccumulator.Append("NULL ");
            }else if (q == null)
            {
                switch (Type.GetTypeCode(node.Value.GetType())) {
                    case TypeCode.Boolean:
                        _sqlAccumulator.Append((bool)node.Value ? 1:0);
                        break;
                    case TypeCode.String:
                        _sqlAccumulator.Append("'");
                        _sqlAccumulator.Append(node.Value);
                        _sqlAccumulator.Append("'");
                        break;
                    case TypeCode.DateTime:
                        _sqlAccumulator.Append("'" );
                        _sqlAccumulator.Append(node.Value);
                        _sqlAccumulator.Append("'");
                        break;
                    default:
                        _sqlAccumulator.Append(node.Value);
                        break;
                }
            }
            return node;
        }
    }
  1. Translate作为对外提供的方法,接收一个Expression表达式,该方法调用ExpressionVisitor基类的Visit方法,开始遍历整棵表达式目录树;
  2. VisitBinary是ExpressionVisitor的一个virtual方法。我们可以利用该方法,解析出表达式中的操作符,并将其翻译成SQL的操作符。
    首先,我们覆盖该方法,加入我们自己的逻辑,即操作符的翻译过程,例如将委托中的“==”翻译成SQL的“=”;
    其次,该方法可以遍历所有的二进制表达式,包括加、减、乘、除、乘方、按位、bool等表达式;本例子我们主要用它来遍历我们的三个bool表达式;
  3. VisitMember方法是ExpressionVisitor的一个virtual方法。该方法可以帮助我们解析表达式中的成员变量;例如在本例中可以将el.CurrencyType翻译成表中列名CurrencyType;
  4. VisitConstantr方法是ExpressionVisitor的一个virtual方法。该方法可以帮助我们获取表达式中的常量。我们覆盖该方法,加入将委托中的常量转换成SQL中的常亮。例如将“RMB”转换成‘RMB’;

最后我使用上面的方法对表达式目录树进行SQL转换,代码如下:

using ExpressTree;
using System.Linq.Expressions;

var translator = new TranslateExpressionToSql();
Expression<Func<CreditCard, bool>> whereQuery = el => el.CurrencyType == "RMB"
    && el.AmountLimitation > 10000 
    || el.BindToWeChat == false;
var sql = translator.Translate(whereQuery);
Console.WriteLine(sql);

最后输出如下:

在这里插入图片描述

总结

IQueryable 的主要作用是通过其扩展方法和调用过程中使用的委托参数,将这些内容转换成表达式目录树。第三方的ORM框架可以在此基础上进行SQL语句的转换。

附录

internal class Entity
{
    public int Id { get; set; }
}
internal class CreditCard : Entity
{
    public int AmountLimitation { get; set; }
    public bool BindToWeChat { get; set; }
    public string CurrencyType { get; set; }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1001356.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

4.k8s部署私人仓库并且部署java服务案例

文章目录 前言一、搭建私人仓库1.1 拉取仓库镜像1.2 创建一个文件夹用来放用户名密码,然后在新创建一个账户1.3 Registry服务默认会将上传的镜像保存在容器的/var/lib/registry&#xff0c;我们将主机的/opt/registry目录挂载到该目录&#xff0c;即可实现将镜像保存到主机的/o…

多线程-锁的种类

1 作用 Java中的锁主要用于保障多并发线程情况下数据的一致性。在多线程编程中为了保障数据的一致性&#xff0c;我们通常需要在使用对象或者方法之前加锁&#xff0c;这时如果有其他线程也需要使用该对象或者该方法,则首先要获得锁,如果某个线程发现锁正在被其他线程使用,就会…

案例研究|农业信息化企业天演维真的堡垒机选型思路与落地实践

浙江天演维真网络科技股份有限公司&#xff08;以下简称为“天演维真”&#xff09;成立于2004年&#xff0c;是中国领先的乡村振兴数字化服务整体解决方案提供商。作为中国品牌农业信息化服务的先行者、中国农产品数字身份识别技术开创者&#xff0c;天演维真的产品已助力全国…

【Python】Python基础语法

总感慨万千&#xff0c;虽只道寻常 文章目录 前言1. python与Java的主要区别2. 数据类型3. 输入与输出3.1 输入3.2 输出 4. 注释5. 运算符6. 条件语句7. 循环8. 函数9. 列表9.1 创建9.2 根据下标访问元素9.3 列表切片9.4 遍历9.5 插入元素9.6 查找元素下标9.7 删除元素9.8 列表…

Spring Boot - Junit4 / Junit5 / Spring Boot / IDEA 关系梳理

文章目录 PreJunit4 / Junit5 / Spring Boot / IDEAIDEA版本Spring-Boot-Older-Release-NotesSpringBootTest 起源 & Spring-Boot-1.4-Release-Notes2.0.0.RELEASE ----- 2.0.9.RELEASE2.1.0.RELEASE ----- 2.1.18.RELEASE2.2.0.RELEASE ~ 2.2.13.RELEASE2.3.0.RELEASE ~ 2…

记一次线程堵塞(挂起)导致消息队列积压

1 背景 A服务作为生产者&#xff0c;每天发送上千万的mq消息&#xff0c;每一个消息包含500个用户ids数据。B服务作为消费者&#xff0c;接受MQ消息并通过http调用第三方请求进行业务处理&#xff0c;消费组启用了rabbitmq的多线程消费组&#xff0c;一个实例并发40个mq消费者…

索尼mp4变成rsv修复案例(ILME-FX3)

索尼mp4的修复案例讲过很多&#xff0c;这次是索尼的ILME-FX3也算是一个畅销的机型&#xff0c;一般索尼没有封装的文件是RSV文件&#xff0c;但是极少遇到有多个RSV文件的&#xff0c;下边我们来讲下这个特殊案例。 故障文件:4个RSV文件&#xff0c;大小在1.78G~28G多 故障现…

(测评补单)Lazada、Shopee、Zalora:探索东南亚电商市场

随着互联网的发展&#xff0c;电子商务在东南亚地区迅速崛起。在这个充满活力和潜力的市场中&#xff0c;Lazada、Shopee和Zalora成为了三大领先的电商平台。它们以其独特的商业模式和创新的服务&#xff0c;吸引了数百万用户。本文将深入探讨这三家电商巨头的发展历程、核心竞…

一文讲透机器学习超参数调优!

公众号&#xff1a;尤而小屋作者&#xff1a;Peter编辑&#xff1a;Peter 大家好&#xff0c;我是Peter~ 本文的主题&#xff1a;机器学习建模的超参数调优。开局一张图&#xff1a; 文章很长&#xff0c;建议直接收藏~ 一、什么是机器学习超参数&#xff1f; 机器学习超参数…

代码随想录训练营 打家劫舍

代码随想录训练营 dp 198. 打家劫舍&#x1f338;code 213. 打家劫舍 II&#x1f338;分析code 337.打家劫舍 III&#x1f338; 198. 打家劫舍&#x1f338; 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素…

FastChat工作原理解析

在了解FastChat如何完成大模型部署前&#xff0c;先了解下Huggingface提供的Transformer库。Hugggingface提供的Transformer库 Hugging Face 的 Transformers 库是一个用于自然语言处理&#xff08;NLP&#xff09;任务的 Python 库&#xff0c;旨在简化和加速使用预训练语言模…

Vue3 中的几个坑,你都见过吗?

Vue3 目前已经趋于稳定&#xff0c;不少代码库都已经开始使用它&#xff0c;很多项目未来也必然要迁移至 Vue3。本文记录我在使用 Vue3 时遇到的一些问题&#xff0c;希望能为其他开发者提供帮助。 1. 使用 reactive 封装基础数据类型 传统开发模式中&#xff0c;数据声明很简…

[论文阅读]Visual Attention Network原文翻译

[论文链接]https://arxiv.org/abs/2202.09741 摘要 虽然一开始是被设计用于自然语言处理任务的&#xff0c;但是自注意力机制在多个计算机视觉领域掀起了风暴。然而&#xff0c;图像的二维特性给自注意力用于计算机视觉带来了三个挑战。&#xff08;1&#xff09;将图像视作一…

怎样获取字符串数组的长度_使用sizeof(array) / sizeof(array[0])

使用sizeof() C、C中没有提供直接获取数组长度的函数&#xff0c;对于存放字符串的字符数组提供了一个strlen函数获取长度&#xff0c;那么对于其他类型的数组如何获取他们的长度呢&#xff1f; 其中一种方法是使用sizeof(array) / sizeof(array[0]), 在C语言中习惯上在使用时…

Unity 性能优化之Shader分析处理函数ShaderUtil.HasProceduralInstancing: 深入解析与实用案例

Unity 性能优化之Shader分析处理函数ShaderUtil.HasProceduralInstancing: 深入解析与实用案例 点击封面跳转到Unity国际版下载页面 简介 在Unity中&#xff0c;性能优化是游戏开发过程中非常重要的一环。其中&#xff0c;Shader的优化对于游戏的性能提升起着至关重要的作用。…

redis缓存详解

一、Redisson分布式锁存在问题 1、基于redis实现的分布式锁&#xff0c;如果redis集群出现master宕机&#xff0c;而从节点没有接收到锁对应的key&#xff0c;被选举成新的master就可能存在被其它线程加锁成功则存在加锁问题 2、 基于上面的问题&#xff0c;可以把redis分为多…

SpringBoot国际化配置组件支持本地配置和数据库配置

文章目录 0. 前言i18n-spring-boot-starter1. 使用方式0.引入依赖1.配置项2.初始化国际化配置表3.如何使用 2. 核心源码实现一个拦截器I18nInterceptorI18nMessageResource 加载国际化配置 3.源码地址 0. 前言 写个了原生的SpringBoot国际化配置组件支持本地配置和数据库配置 背…

口袋参谋:99.99%商家都学的防骗技巧!

​99%的淘宝天猫商家&#xff0c;必然都要解决一个问题&#xff01;&#xff01;&#xff01; 如何让自己不被敲诈勒索且骗钱&#xff01; 直接看真实案例 看这个骗子&#xff0c;是如何赤裸裸诈骗商家的&#xff01; 如果你不想再当冤大头&#xff0c;告诉你一个99.99%有效…

SSM - Springboot - MyBatis-Plus 全栈体系(六)

第二章 SpringFramework 四、SpringIoC 实践和应用 3. 基于 注解 方式管理 Bean 3.1 实验一&#xff1a;Bean 注解标记和扫描 (IoC) 3.1.1 注解理解 和 XML 配置文件一样&#xff0c;注解本身并不能执行&#xff0c;注解本身仅仅只是做一个标记&#xff0c;具体的功能是框…

分类预测 | MATLAB实现基于SVM-Adaboost支持向量机结合AdaBoost多输入分类预测

分类预测 | MATLAB实现基于SVM-Adaboost支持向量机结合AdaBoost多输入分类预测 目录 分类预测 | MATLAB实现基于SVM-Adaboost支持向量机结合AdaBoost多输入分类预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.MATLAB实现基于SVM-Adaboost支持向量机结合Ada…