• 回顾工作中常用的Linq小技巧


    前言

    在C#语言发展的历史长河中,Linq是一个极其重要的里程碑!

    Linq的语法吸取了SQL语法的特性,同时配合Lambda表达式又可以使代码更加优雅!
    可以这么说,用好了Linq可以大大提高程序猿的工作效率,毕竟我们的日常工作本质就是对数据的处理。经历了十多年的发展,现在微软自带的内库包含的Linq函数已经非常多了,几乎满足我们日常工作。

    下面根据一个对科室数据操作的例子,就个人觉得日常高频使用的Linq小技巧贴出来,权当是做个笔记了。

    初始化数据

    定义模型

    这里定义一个科室对象,模拟我们日常工作的科室信息。科室存在层级关系,还有一个员工数量的属性。模型如下:

        public class DepartmentDto
        {
            public int Id { get; set; }         
    
            public int? ParentId { get; set; }
    
            public string Name { get; set; }
    
            public string TelPhone { get; set; }
     
            public string Address { get; set; }
    
            public string Remark { get; set; }
             
            public int EmployeeNumber { get; set; }
        }
    

    初始化数据

            public List<DepartmentDto> InitDepartmentData()
            {
                List<DepartmentDto> lst = new List<DepartmentDto>();
                lst.AddRange(new DepartmentDto[] {
                    new DepartmentDto() {
                         Address ="一马路XX号",
                         Id=1,
                         Name="一级一号科室",
                         Remark="",
                         TelPhone="0731-6111111",
                         EmployeeNumber=3,
                    },
                    new DepartmentDto() {
                         Address ="二马路XX号",
                         Id=2,
                         Name="一级二号科室",
                         Remark="",
                         TelPhone="0731-6111111",
                         EmployeeNumber=4,
                    },
                    new DepartmentDto() {
                         Address ="三马路XX号",
                         Id=3,
                         Name="一级三号科室",
                         Remark="",
                         TelPhone="0731-6222222",
                         EmployeeNumber=6,
                    },
                    new DepartmentDto() {
                         Address ="一马路XX号",
                         ParentId=1,
                         Id=4,
                         Name="二级一号科室",
                         Remark="",
                         TelPhone="0731-6222222",
                         EmployeeNumber=7,
                    },
                    new DepartmentDto() {
                         Address ="二马路XX号",
                         ParentId=2,
                         Id=5,
                         Name="二级二号科室",
                         Remark="",
                         TelPhone="0731-6222222",
                         EmployeeNumber=5,
                    },
                });
                return lst;
            }
    

    获取未存在父级科室的科室集合

      List<DepartmentDto> lstDepartItems = InitDepartmentData();
                //1、获取未存在父级科室的科室集合 1、2、3            
                List<DepartmentDto> notExistsParentDepartmentIdLst = lstDepartItems
                    .Where(p => !p.ParentId.HasValue)
                    .ToList();
    

    这里比较简单,Where内校验下ParentId的值为空即可,不使用Linq则需要自己手写一个循环搞定,如下:

       List<DepartmentDto> notExistsParentDepartmentIdLst_1 = new List<DepartmentDto>();
       foreach (DepartmentDto department in lstDepartItems)
       {
             if (!department.ParentId.HasValue)
                   notExistsParentDepartmentIdLst_1.Add(department);
       }
    

    这么看感觉便捷性不太明显是吧~~ 没事,万丈高楼平地起,咋们循行渐进~

    获取存在子科室的科室集合

     //2、获取存在子科室的科室集合 1、2
                List<DepartmentDto> existsParentDepartmentIdLst1 = lstDepartItems
                    .Where(p => lstDepartItems.Select(k => k.ParentId).Contains(p.Id))
                    .ToList();
    

    这里通过引用了外部的集合对象进行关联,在Where内对子科室的ParentId字段与当前集合的科室Id校验,从而得到已存在子科室的科室集合。如果不使用Linq则自己需要写两个循环才能搞定。
    如下:

      List<DepartmentDto> existsParentDepartmentIdLst1_1 = new List<DepartmentDto>();
      foreach (DepartmentDto parentDepart in lstDepartItems)
      {
           foreach (DepartmentDto childDepart in lstDepartItems) 
           {
                if (parentDepart.Id == childDepart.ParentId)
                {
                     existsParentDepartmentIdLst1_1.Add(parentDepart);
                     continue;
                }
           }
      }
    

    这么看,Linq带来的便捷性是否足够明显了,代码优雅了太多了~

    科室根据地址分组,获取科室总数、科室平均数

     var groupDto = lstDepartItems
                    .GroupBy(p => p.Address)
                    .Select(p => new
                    {
                        Address = p.Key,
                        SumEmployeeCount = p.Sum(p => p.EmployeeNumber),
                        AvgEmployeeCount = p.Average(p => p.EmployeeNumber),
                    }).ToList();
    

    获取两个集合不相等的元素

    引用类型的比较处理

    这里需要留意我们的科室对象是class,class在C#里属于引用类型。引用类型的比较和值类型的比较大不同相同!引用类型的比较是比较对象在内存堆里指向的地址,并非对象包含的属性值 但是我们预期的比较就是单纯的对值进行比较,所以这里需要通过实现IEqualityComparer<T>接口来对两个引用类型集合对象进行比较。

    创建IEqualityComparer<DepartmentDto>对象

      public class DepartmentEqualityComparer : IEqualityComparer<DepartmentDto>
        {
            public bool Equals([AllowNull] DepartmentDto x, [AllowNull] DepartmentDto y)
            {
                // 这里可以写比较的关键代码~
                return x.Id == y.Id;
            }
    
            public int GetHashCode([DisallowNull] DepartmentDto obj)
            {
                return obj.ToString().GetHashCode();
            }
        }
    

    接下来,定义一个新的集合,再手动向这个集合追加元素。

        List<DepartmentDto> lstDepartItemsCPs = InitDepartmentData();
                lstDepartItemsCPs.Add(new DepartmentDto()
                {
                    Address = "三马路XX号",
                    Id = 6,
                    Name = "二级三号科室",
                    Remark = "",
                    TelPhone = "0731-6222222",
                    EmployeeNumber = 7
                });
    

    集合比较代码:

      // 这里如果DepartmentDto为引用类型(class)则需要使用比较器DepartmentEqualityComparer才能返回我们的预期值(根据ID值判断是否相等)
                List<DepartmentDto> diffList = lstDepartItemsCPs.Except(lstDepartItems, new DepartmentEqualityComparer()).ToList();
                // 获取相等元素
                List<DepartmentDto> diffList1 = lstDepartItemsCPs.Intersect(lstDepartItems.Select(p => p), new DepartmentEqualityComparer()).ToList();
            // 需要添加IEqualityComparer,因为集合内的内容为引用类型!所以两个集合的“值”是不同的,引用类型的值在这里还包含了指向的内存堆的引用地址
                bool isEqual = lstDepartItems.SequenceEqual(InitDepartmentData(), new DepartmentEqualityComparer());
    

    值类型的比较处理

    可能你觉得需要去创建IEqualityComparer<DepartmentDto>对象过于麻烦,那么想下是否一定需要将科室对象的类型设定为class,是否有更合适的类型可以替换? 答案是有的,微软推荐如果对具体模型不需要多次执行装箱、拆箱操作最好将模型设置为结构struct而非class 现在我们回过头将科室对象的类型更新为struct

    然后发现上面集合比较的代码可以简化成这样:

        // 这里如果DepartmentDto为值类型(struct)则不需要使用比较器DepartmentEqualityComparer即可返回我们的预期值(根据ID值判断是否相等)
                List<DepartmentDto> diffList3 = lstDepartItemsCPs.Except(lstDepartItems).ToList();
                // 获取相等元素
                List<DepartmentDto> diffList4 = lstDepartItemsCPs.Intersect(lstDepartItems.Select(p => p)).ToList();
                     // 如果把DepartmentDto的类型改为值类型则可以不需要IEqualityComparer进行判断的结果也会为true
                isEqual = lstDepartItems.SequenceEqual(InitDepartmentData());
    

    OfType和Cast的不同之处

    OfType允许对集合的项进行隐性转换(非强转Convert)且在转换前会进行判断,当类型不允许转换则continue到下一个集合项。而Cast则是子项不进行判断,直接隐性转换,所以理论上效率更高,当然相对的出错率也更高~

            public void ConvertListTest()
            {
                try
                {
                    object[] ss = { 1, "2", 3, "四", "五", "7" };
                    // 1、3 OfType的本质是循环集合,对每个集合项进行类型验证(不是强转,所以此处的结果是1、3 而不是1、2、3、7)
                    var lst = ss.ToList().OfType<int>().ToList();
                    // 3  
                    int max = ss.OfType<int>().Max();
                    // 这句代码会提示“System.InvalidCastException:“Unable to cast object of type 'System.String' to type 'System.Int32'.”异常,原因:Cast的执行效率会略高与OfType,因为在对集合项进行类型转换前不会对其进行类型校验,当你确保集合的类型是安全的则可以用Cast,但是能用到Cast和OfType的时候基本上都是用OfType了..
                    int maxCast = ss.Cast<int>().Max();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }
            }
    

    上述代码已上传到github,地址: https://github.com/QQ897878763/LinqStudySample.git

  • 相关阅读:
    MSSQLSERVER数据库 C#里调用存储过程,多参数查询,个人记录
    ASP.NET GridView和Repeater合并单元格
    C# XPath教程
    MSSQLSERVER数据库 导入文本文件
    MSSQLSERVER数据库 递归查询例子
    C# TreeView右键弹出菜单
    tomcat 下War包部署方法
    JAVA自定义标签教程及实例代码
    JAVA tag学习
    Java Quartz 自动调度
  • 原文地址:https://www.cnblogs.com/hunanzp/p/Linq.html
Copyright © 2020-2023  润新知