相信Linq经过简单的培训大家都会写了,但是但你们满意的写完一套逻辑的时候有没有考虑过,你写的linq查询会导致严重的性能问题呢?那就分享下我在项目中遇到的踩过坑吧。
安装分析工具-MiniProfiler.安装教程我之前也分享过了
https://blog.csdn.net/wangwengrui40/article/details/126531322
为什么要安装呢?因为linqtosql追踪都会被编译程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次sql
public 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的,如果你的组件是BillNo或者其他,将不能进行id查询,编译器会默认为全表查询
public async Task GetXLA() {
var entitty =await _TraApplyMasterRepository.GetAll().FirstOrDefaultAsync(t=>t.BillNo== "20221226001");
var entitty2 = await _TraApplyMasterRepository.GetAsync( "20221226001");
return entitty;
}
可以看到放编译出来也不是表达式树
//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
因为很大原因都是因为表达式编译出了问题,纯纯分享踩过的坑
//写法没问题
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;
}
我们避免踩坑的目的都是提高查询性能,那么LinqToSql最终编译出来的都是sql语句。那么sql语句的优化策略,在linq里面也同样适用