Linq-Tosql踩坑记

前言

相信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次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语句截图

Linq-Tosql踩坑记_第1张图片

二号坑位: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;
        }
Linq-Tosql踩坑记_第2张图片

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

Linq-Tosql踩坑记_第3张图片

三号坑位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语句不一样呢?问题出在他们编译的表达式树上面

Linq-Tosql踩坑记_第4张图片

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


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

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

Linq-Tosql踩坑记_第5张图片

已知安全函数

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个方法编译出来的表达式树

Linq-Tosql踩坑记_第6张图片

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

Linq-Tosql踩坑记_第7张图片

你可能感兴趣的:(linq,c#,数据库)