Linq-Tosql踩坑记
迪丽瓦拉
2025-06-01 08:28:05
0

前言

相信Linq经过简单的培训大家都会写了,但是但你们满意的写完一套逻辑的时候有没有考虑过,你写的linq查询会导致严重的性能问题呢?那就分享下我在项目中遇到的踩过坑吧。

开始准备

安装分析工具-MiniProfiler.安装教程我之前也分享过了

https://blog.csdn.net/wangwengrui40/article/details/126531322

为什么要安装呢?因为linqtosql追踪都会被编译程sql语句,所以导致性能问题的原因全都是编译出来的sql语句有问题

开始踩坑

一号坑:以下函数会导致sql语句提交

至于为什么会触发提交,上节课的底层原理探讨已经演示过了,以下函数会触发 表达式树 编译成 sql语句的。

https://blog.csdn.net/wangwengrui40/article/details/129335657

GetAllListAsync/GetAllList
FirstAsync/First
FirstOrDefaultAsync/FirstOrDefault
ToListAsync/ToList
GetAsync/Get
for循环,if判断
几乎IQueryable变其他类型的都会进行数据库提交,IOrderedQueryable等特殊的除外

代码展示

      //产生1次sql语句public async Task GetXL0(){var ce = _entityRepository.GetAll();var ce2 =  ce.Where(t=>t.Id==1);var ce3 =  ce.Where(t => t.Id == 2);//var ce =await _entityRepository.GetAll().ToListAsync();return await ce3.FirstOrDefaultAsync();}//产生2次sql语句public async Task GetXL() {var ce =  _entityRepository.GetAll();var ce2 = await ce.ToListAsync();var ce3 = await ce.CountAsync();//var ce =await _entityRepository.GetAll().ToListAsync();return ce3;}//产生2次sql语句public int GetXL2(){var ce = _entityRepository.GetAll();//var ce =await _entityRepository.GetAll().ToListAsync();int count = 0;foreach (var i in ce) {count++;}foreach (var i in ce){Console.WriteLine(i.Id);}return count;}

分享下灾难性写法

  //嵌套产生 20条参数21次sqlpublic async Task GetXL3(){//有20条数据var ce = _entityRepository.GetAll();int count = 0;foreach (var i in ce){var ii = ce.Where(t => t.Id == i.Id).FirstOrDefault();count++;}return count;}

编译出来的sql语句截图

二号坑位:Get/GetAsync不是Id做主键不要用

Get/GetAsync是默认了主键是Id的,如果你的组件是BillNo或者其他,将不能进行id查询,编译器会默认为全表查询

public async Task GetXLA() {var entitty =await  _TraApplyMasterRepository.GetAll().FirstOrDefaultAsync(t=>t.BillNo== "20221226001");var entitty2 = await _TraApplyMasterRepository.GetAsync( "20221226001");return entitty;}

可以看到放编译出来也不是表达式树

三号坑位Queryable函数部分尽可能不要带函数,不然很容易进行全表查询

        //sql语句 select * from TraApplyMasterRepository where  BillNo== "20221226001"public List GetXLB2Ok(){var entitty = _TraApplyMasterRepository.GetAll().Where(t => t.BillNo== "20221226001").ToList();return entitty;}public List GetXLB2(){//sql语句 select * from TraApplyMasterRepository var entitty =  _TraApplyMasterRepository.GetAll().Where(t =>  t.BillNo.ToString() == "20221226001").ToList();return entitty;}

那为什么编译出来的sql语句不一样呢?问题出在他们编译的表达式树上面

很明显 tostring多了一行代码。这行代码是什么,上节课也没听我讲过?

(MethodInfo)MethodBase.GetMethodFromHandle((RuntimeMethodHandle)/*OpCode not supported: LdMemberToken*/), 

其实这行代码,就是拼接 p.Name 的过程只不过全部都定义到了RuntimeMethodHandle里面了。因为linq反编译无法把 tostring编译成sql的函数,所以只能把前部分的 select * from TraApplyMasterRepository编译了

已知安全函数

Contains =sql的 like
日期类型的.Date

四号坑位连表查询尽可能不要带函数,不然很容易进行全表查询

        //join联查尽可能不要带函数public async Task> GetXLBOk(){var entitty = _TraApplyMasterRepository.GetAll().Join(_TraApplyDetailRepository.GetAll(), sa => sa.BillNo, se => se.BillNo, (sa, se) => sa).Where(t => t.BillNo == "20221226001").ToList();//.Where(t=> Convert.ToDateTime(t.CreationTime.ToString("yyyy-MM-dd"))== Convert.ToDateTime(t.CreationTime.ToString("yyyy-MM-dd")))/*var entitty2 = await _TraApplyMasterRepository.GetAll().Join(_TraApplyDetailRepository.GetAll(), sa => sa.BillNo, se => se.BillNo, (sa, se) => sa).Where(t => t.BillNo == "20221226001").OrderByDescending(t => t.BillNo).ToListAsync();*/return entitty;}public async Task> GetXLB(){var entitty = _TraApplyMasterRepository.GetAll().Join(_TraApplyDetailRepository.GetAll().OrderByDescending(t => t.BillNo), sa => sa.BillNo, se => se.BillNo, (sa, se) => sa).Where(t =>  t.BillNo == "20221226001").ToList();return entitty;}

2个方法编译出来的表达式树

在sql语句中是没有在联查中排序的写法的。个人猜测在 Linq的解析器模式中,遇到不合理的sql语句,就会把联查的2句 查询 拆开查询。这也解释了上面三号坑位为什么会拆开为2个查询

SELECT * FROM TraApplyBillMaster  
JOIN  TraApplyBillDetail ORDER BY TraApplyBillDetail.BillNo   on TraApplyBillDetail.BillNo = TraApplyBillMaster.BillNo

从这里开始我就不解释原因了

因为很大原因都是因为表达式编译出了问题,纯纯分享踩过的坑

五号坑位join嵌套要注意让linq懂你,不然很容易进行全表查询

       //写法没问题public async Task GetXLC(){var entitty = _TraApplyMasterRepository.GetAll().Join(_TraApplyDetailRepository.GetAll(), sa => sa.BillNo, se => se.BillNo, (sa, se) => new  { sa, se });var entitty2 = await _TraApplyPatientRepository.GetAll().Join(entitty, sc => sc.BillNo, sd => sd.sa.BillNo, (sc, sd) => sd.sa).FirstOrDefaultAsync();return entitty2;}
//性能问题写法public async Task GetXLC1(){var entitty = _TraApplyMasterRepository.GetAll().Join(_TraApplyDetailRepository.GetAll(), sa => sa.BillNo, se => se.BillNo, (sa, se) => new XLCDto { BillNo=sa.BillNo, BillNoDetail=se.BillNo });var entitty2 = await _TraApplyPatientRepository.GetAll().Join(entitty, sc => sc.BillNo, sd => sd.BillNo, (sc, sd) => sd).FirstOrDefaultAsync();return entitty2;}//写法没问题public async Task GetXLC2(){var entitty =await _TraApplyMasterRepository.GetAll().Join(_TraApplyDetailRepository.GetAll(), sa => sa.BillNo, se => se.BillNo, (sa, se) => new {sa,se }).Join(_TraApplyPatientRepository.GetAll(), sc => sc.sa.BillNo, sd => sd.BillNo, (sc, sd) => sc.sa).FirstOrDefaultAsync();return entitty;}

六号坑位导航属性容易出问题,不然很容易进行全表查询

尽量不要用导航属性,用join。

        //导航属性容易出事//没问题public async Task GetXLD(){var product =await _productRepository.GetAll().Include(t => t.BasBloodVariety).Join(_TraApplyDetailRepository.GetAll(), sa => sa.BasBloodVarietyId, se => se.BasBloodVarietyId, (sa, se) => sa).FirstOrDefaultAsync();return product;}
//没问题public async Task GetXLD1(){var product = await _productRepository.GetAll().Include(t => t.BasBloodVariety).Where(t => t.BasBloodVariety.Name != "1").Join(_TraApplyDetailRepository.GetAll(), sa => sa.BasBloodVarietyId, se => se.BasBloodVarietyId, (sa, se) => sa).FirstOrDefaultAsync();return product;}public async Task GetXLD2(){
//出问题了,进行了全表查询var product = await _TraApplyDetailRepository.GetAll().Join(_productRepository.GetAll().Include(t => t.BasBloodVariety).Where(t => t.BasBloodVariety.Name !="1"), sa => sa.BasBloodVarietyId, se => se.BasBloodVariety.Id, (sa, se) => se.BasBloodVariety.Name).FirstOrDefaultAsync();return product;}
//没问题public async Task GetXLD3(){var product = await _TraApplyDetailRepository.GetAll().Join(_productRepository.GetAll().Include(t => t.BasBloodVariety), sa => sa.BasBloodVarietyId, se => se.BasBloodVariety.Id, (sa, se) => se.BasBloodVariety.Name).FirstOrDefaultAsync();return product;}

最后关于sql语句的优化

我们避免踩坑的目的都是提高查询性能,那么LinqToSql最终编译出来的都是sql语句。那么sql语句的优化策略,在linq里面也同样适用

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...