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里面也同样适用

相关内容

热门资讯

一场帐篷音乐节,如何让井研从“... 帐篷音乐节现场封面新闻记者 周洪攀10月的研溪湿地,晚风里裹着柑橘的清甜与民谣的回响。夜幕降临,10...
白鹤翩翩落星愿,上海迪士尼度假... 10月9日,世界候鸟日前夕,上海迪士尼度假区星愿公园的湖风里多了一分白鹤的灵动。三座白鹤雕塑正式揭幕...
成都锦江边赏初秋枫叶飘飘 目前,成都锦江边国华街枫叶树红了。近年来,国华街附近的几条街都栽种了红枫树,是城区较集中看枫叶的点位...
GBTA:商务旅行正在复苏,但... 亚太正在成为全球商务旅行增长的重要引擎。在经历疫情后的快速反弹之后,全球商务旅行正在进入一个新的增长...
从景区焕新到节赛引流 邹城双节... 齐鲁网·闪电新闻10月9日讯 中秋、国庆“双节”假期,邹城市文化和旅游局提前谋划、周密部署,立足自身...
江山如此多娇 且看石楼今朝 金秋十月,黄河岸畔秋意盎然,丰收盛会喜迎国庆。本次石楼国庆文旅活动以“江山如此多娇,且看石楼今朝”为...
海南国庆假期接待游客475.4... 2026年国庆假期,海南接待游客475.40万人次,较2025年增长13.1%,较2024年增长15...
从三阳村出发,到双阳村接山泉水... 这天我从银桥镇三阳村出来后,下一站便是它旁边的双阳村路边取水点,按照之前所做的功课,其实只要导航到某...
国庆假期宁德文旅综合消费增长6... 东南网10月9日讯(本网记者 叶伏国 通讯员 周俊因)记者从宁德市文旅局获悉,今年国庆假期,全市文旅...
会网球懂摄影,一名合格的民宿管... 刚刚结束的国庆假期,各地特色文旅热度高涨。有这样一群民宿管家,他们无暇出门度假,却以贴心细致的守候为...