C#之LINQ

文章目录

  • 前言
    • LINQ
  • 一、LINQ1
  • 一、LINQ2
  • 一、LINQ3
  • Where方法:每一项数据都会进过predicate的测试,如果针对一个元素,predicate执行的返回值为true,那么这个元素就会放到返回值中。
  • 获取一条数据(是否带参数的两种写法):
    • C# LINQ 查询方法详解:Single, SingleOrDefault, First, FirstOrDefault
    • 方法对比表
    • 详细解释与示例
      • 1. Single
      • 2. SingleOrDefault
      • 3. First
      • 4. FirstOrDefault
    • 性能考虑
    • 默认值说明
    • 最佳实践建议
    • 总结对比图
  • 排序:
    • C# LINQ 排序方法详解:OrderBy 与 OrderByDescending
    • 基本概念
    • 基本语法
    • 示例解释:`list.OrderBy(e => e.Age)`
      • 执行过程:
      • 排序结果:
    • 完整排序示例
      • 1. 单属性排序
      • 2. 多级排序(ThenBy/ThenByDescending)
      • 3. 自定义排序逻辑
    • C# 特殊排序场景详解:简单类型、末位字符与随机排序
    • 一、简单类型排序(不使用 Lambda 表达式)
      • 1. 基本排序方法
      • 2. C# 11+ 的简化语法
      • 3. 字符串集合排序
    • 二、特殊案例:按最后一个字符排序
      • 1. 基本实现
      • 2. 处理空字符串和单字符
      • 3. 多级排序(先按长度,再按末字符)
    • 三、随机排序(使用 Guid 或随机数)
      • 1. 使用 Guid 随机排序
  • 限制结果集,获取部分数据:
    • C# LINQ 分页操作详解:Skip 与 Take
    • 基本概念
    • 基本语法
    • 方法详解
      • 1. Skip(n)
      • 2. Take(n)
    • 组合使用:分页实现
      • 基本分页公式
      • 完整分页示例
      • 分页辅助方法
  • 集合函数:
    • C# LINQ 聚合方法与链式调用详解
    • LINQ 聚合方法概述
    • 链式调用原理
      • 链式调用示例
    • 代码解析:`list.Where(e=>e.Age>30).Min(e=>e.Age);`
      • 执行步骤
      • 等效传统代码
      • 注意事项
    • 其他聚合方法链式调用示例
      • 1. 计算平均值
      • 2. 求和统计
      • 3. 计数统计
      • 4. 多级聚合
    • 链式调用的高级应用
      • 1. 条件聚合
      • 2. 组合使用
      • 3. 空值处理技巧
  • 分组:
    • C# LINQ GroupBy 分组方法详解
    • GroupBy 方法核心概念
      • 方法签名
      • 关键特性
    • IGrouping 接口解析
      • 核心特性
    • 基本用法示例
      • 1. 简单分组
      • 2. 分组后聚合计算
    • 高级分组技巧
      • 1. 复合键分组
      • 2. 分组后元素转换
      • 3. 自定义结果选择器
    • IGrouping 的实际应用
      • 1. 直接访问分组键
      • 2. 分组嵌套处理
      • 3. 转换为字典
    • 性能注意事项
    • 投影:
  • C# LINQ 投影操作详解
    • 投影的本质
  • C# LINQ 投影操作详解
    • 投影的本质
    • 核心方法:Select()
      • 方法签名
    • 投影的基本用法
      • 1. 提取属性值
      • 2. 创建新对象
      • 3. 转换类型
    • 高级投影技巧
      • 1. 带索引的投影
      • 2. 嵌套投影
      • 3. 条件投影
      • 4. 计算字段投影
    • 实际应用场景
      • 1. 数据转换(Entity → DTO)
      • 2. 数据简化
      • 3. 计算字段
      • 4. 组合数据
    • 性能考虑
    • 与 SelectMany() 的区别
    • 最佳实践
    • 集合转换:


前言

LINQ

一、LINQ1

委托->lambda->LINQ
1、委托是可以指向方法的类型,调用委托变量时执行的就是变量指向方法。

在 C# 中,​​委托(Delegate)​​ 是一种类型安全的函数指针,它允许将方法作为参数传递、存储或动态调用。委托是事件(Event)和回调机制的基础,实现了​​松耦合​​的设计模式。
核心概念​​
1.类型安全的方法引用​​

委托定义了方法的签名(参数类型和返回类型),只能绑定匹配签名的方法。

2.类似接口​​

委托类似于只包含一个方法的接口,但更轻量且直接。

3.多播能力​​

一个委托实例可绑定多个方法(+= 添加),调用时按顺序执行所有方法。

委托的声明与使用​

  1. 定义委托类型
// 声明一个委托类型,指定方法签名
public delegate void MyDelegate(string message);
  1. 绑定方法
// 目标方法(签名必须匹配)
public void ShowMessage(string msg)
{Console.WriteLine($"Message: {msg}");
}// 实例化委托并绑定方法
MyDelegate del = new MyDelegate(ShowMessage);
  1. 调用委托
del("Hello, Delegate!"); 
// 输出:Message: Hello, Delegate!

实例

class Program
{static void Main(string[] args){D1 d = F1;d();d = F2;d();}static void F1(){Console.WriteLine("我是F1");}static void F2(){Console.WriteLine("我是F2");}
}
delegate void D1();

结果
在这里插入图片描述

2、.NET中定义了泛型委托Action(无返回值)和Func(有返回值),所以一般不用自定义委托类型

内置泛型委托​​
C# 提供两种常用泛型委托,无需自定义:

1.​​Action​​

无返回值的方法(支持 0~16 个参数)。

public delegate void Action();                     // 无参数
public delegate void Action<in T>(T obj);          // 1个参数
public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2); // 2个参数
// ... 最多支持16个参数 (Action<T1,...,T16>)
Action<string> actionDel = ShowMessage; // void 方法

2.​​Func​​
有返回值的方法(最后一个泛型参数是返回类型)。

public delegate TResult Func<out TResult>();                     // 无参数,有返回值
public delegate TResult Func<in T, out TResult>(T arg);          // 1输入+1输出
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2); // 2输入+1输出
// ... 最多16输入+1输出 (Func<T1,...,T16,TResult>)
Func<int, int, int> add = (a, b) => a + b; 
int result = add(3, 5); // 返回 8

委托变量不仅可以指向普通方法,还可以指向匿名方法。

Func<int, int, string> f1 = delegate (int i1, int i2)
{return $"{i1}+{i2}={i1 + i2}";
};

匿名方法可以写成lambda表达式,可以省略参数数据类型,因为编译根据委托类型推断出参数类型,用=>引出方法体

        Func<int, int, string> f2 = (i1, i2) =>{return $"{i1}+{i2}={i1 + i2}";};

lambda表达式

(输入参数) => 表达式或语句块
场景Lambda 表达式等效传统写法
无参数() => Console.WriteLine("Hi")void F() { Console.WriteLine("Hi"); }
单参数x => x * xint F(int x) { return x * x; }
多参数(a, b) => a + bint F(int a, int b) { return a + b; }
语句块s => { Console.WriteLine(s); return s.Length; }int F(string s) { Console.WriteLine(s); return s.Length; }

一、LINQ2

揭秘LINQ方法的背后
LINQ中提供了很多集合扩展方法,配合lambda能简化数据处理。

int[] nums = { 11, 1, 24, 5, 6, 98, 60 };
// Where方法会遍历集合中的每一个元素,对于每一个元素
// 都调用a=> a>10这个表达式判断下一个是否为true
// 如果为true,则把这个放到返回的集合中
IEnumerable<int> result = nums.Where(a => a > 10);
foreach (int i in result)
{Console.WriteLine(i);
}

下面手动实现Where功能

int[] nums = { 11, 1, 24, 5, 6, 98, 60 };
//// Where方法会遍历集合中的每一个元素,对于每一个元素
//// 都调用a=> a>10这个表达式判断下一个是否为true
//// 如果为true,则把这个放到返回的集合中
//IEnumerable<int> result = nums.Where(a => a > 10);
IEnumerable<int> result = MyWhere(nums,a=>a>10);
foreach (int i in result)
{Console.WriteLine(i);
}IEnumerable<int> MyWhere(IEnumerable<int> items,Func<int,bool> f)
{List<int> result = new List<int>();foreach (int item in items){if (f(item))result.Add(item);}return result;
}

使用yield实现Where功能

int[] nums = { 11, 1, 24, 5, 6, 98, 60 };
//// Where方法会遍历集合中的每一个元素,对于每一个元素
//// 都调用a=> a>10这个表达式判断下一个是否为true
//// 如果为true,则把这个放到返回的集合中
//IEnumerable<int> result = nums.Where(a => a > 10);
IEnumerable<int> result = MyWhere1(nums,a=>a>10);
foreach (int i in result)
{Console.WriteLine(i);
}IEnumerable<int> MyWhere1(IEnumerable<int> items, Func<int, bool> f)
{List<int> result = new List<int>();foreach (int item in items){if (f(item))yield return item;}
}

一、LINQ3

LINQ常用扩展方法

(补充)扩展方法
#C# 扩展方法深度解析

一、本质与原理

  1. 核心概念
    扩展方法是一种编译时语法糖,它允许开发者在不修改原始类型、不创建派生类的情况下,为现有类型"添加"新方法。其本质是静态方法,但通过编译器魔法实现了实例方法调用语法。

  2. 实现机制

// 定义扩展方法
public static class StringExtensions {public static bool IsValidEmail(this string input) => Regex.IsMatch(input, @"^[^@\s]+@[^@\s]+\.[^@\s]+$");
}// 调用代码
var isValid = "test@example.com".IsValidEmail();// 编译器转换后的实际代码
var isValid = StringExtensions.IsValidEmail("test@example.com");
  1. 关键特性
  • 静态伪装:静态方法伪装成实例方法
  • 非侵入性:不修改原始类型代码
  • 编译时解析:在编译阶段确定方法绑定
  • 命名空间控制:需导入扩展方法所在命名空间

二、技术实现详解

  1. 三大必要条件
public static class Extensions // 条件1:静态类
{// 条件2:静态方法 + 条件3:this修饰首参数public static string Reverse(this string value){char[] chars = value.ToCharArray();Array.Reverse(chars);return new string(chars);}
}
  1. 参数规则
  • 首个参数:必须使用 this 修饰,指定目标类型
  • 附加参数:可添加多个常规参数
public static string Wrap(this string text, string wrapper)=> $"{wrapper}{text}{wrapper}";// 使用
"Hello".Wrap("**"); // 输出:**Hello**
  1. 方法重载
// 重载1:默认包装符
public static string Wrap(this string text) => Wrap(text, "[]");// 重载2:自定义包装符
public static string Wrap(this string text, string wrapper) => $"{wrapper}{text}{wrapper}";

三、高级应用场景

  1. 接口扩展
public static void Log<T>(this IEnumerable<T> collection)
{foreach (var item in collection)Console.WriteLine(item);
}// 所有集合类型通用
new List<int>{1,2,3}.Log();
new int[]{4,5,6}.Log();
  1. 链式调用 (Fluent API)
public static StringBuilder AppendFormattedLine(this StringBuilder sb,string format,params object[] args)
{sb.AppendFormat(format, args).AppendLine();return sb; // 返回自身实现链式调用
}// 使用
var sb = new StringBuilder().AppendFormattedLine("Date: {0}", DateTime.Now).AppendFormattedLine("User: {0}", "Alice");
  1. 空值处理模式
public static TResult SafeGet<T, TResult>(this T obj, Func<T, TResult> selector,TResult defaultValue = default)
{return obj != null ? selector(obj) : defaultValue;
}// 安全访问嵌套属性
var city = person?.Address?.City; // 传统方式
var city = person.SafeGet(p => p.Address.City); // 扩展方法方式

四、LINQ风格通用扩展方法

  1. 完整实现示例
public static class EnumerableExtensions
{// 通用过滤 (支持所有IEnumerable<T>)public static IEnumerable<T> WhereEx<T>(this IEnumerable<T> source,Func<T, bool> predicate){foreach (var item in source)if (predicate(item)) yield return item;}// 通用转换public static IEnumerable<TResult> SelectEx<TSource, TResult>(this IEnumerable<TSource> source,Func<TSource, TResult> selector){foreach (var item in source)yield return selector(item);}// 字典键过滤专用public static IEnumerable<TKey> KeysWhere<TKey, TValue>(this IDictionary<TKey, TValue> source,Func<TKey, bool> predicate){foreach (var key in source.Keys)if (predicate(key))yield return key;}
}
  1. 多类型兼容使用
// List使用
var numbers = new List<int> {1, 2, 3, 4};
var evens = numbers.WhereEx(n => n % 2 == 0);// 数组使用
string[] fruits = {"Apple", "Banana"};
var aFruits = fruits.WhereEx(f => f.StartsWith("A"));// 字典使用
var dict = new Dictionary<int, string> {{1, "A"}, {2, "B"}};
var keys = dict.KeysWhere(k => k > 1); // [2]

Where方法:每一项数据都会进过predicate的测试,如果针对一个元素,predicate执行的返回值为true,那么这个元素就会放到返回值中。

Where参数是一个lambda表达式格式的匿名方法,方法的参数e表示当前判断的元素对象。参数的名字不一定非要叫e,不过一般lambda表达式中的变量名长度都不长。
Count方法:获取数据条数
Any方法:是否至少有一条数据

List<Employee> list = new List<Employee>();
list.Add(new Employee { Id = 1, Name = "jerry", Age = 28, Gender = true, Salary = 5000 });
list.Add(new Employee { Id = 2, Name = "jim", Age = 33, Gender = true, Salary = 3000 });
list.Add(new Employee { Id = 3, Name = "lily", Age = 35, Gender = false, Salary = 9000 });
list.Add(new Employee { Id = 4, Name = "lucy", Age = 16, Gender = false, Salary = 2000 });
list.Add(new Employee { Id = 5, Name = "kimi", Age = 25, Gender = true, Salary = 1000 });
list.Add(new Employee { Id = 6, Name = "nancy", Age = 35, Gender = false, Salary = 8000 });
list.Add(new Employee { Id = 7, Name = "zack", Age = 35, Gender = true, Salary = 8500 });
list.Add(new Employee { Id = 8, Name = "jack", Age = 33, Gender = true, Salary = 8000 });
IEnumerable<Employee> items1 = list.Where(e => e.Age > 30);// 返回符合条件的IEnumerable集合
int items2 = list.Count();// 无条件返回总条数
int items3 = list.Count(e => e.Age > 30);// 返回符合条件的,数量
int items4 = list.Count(e => e.Age > 30 && e.Salary>500);// 返回符合条件的,数量
bool items5 = list.Any();// 有一条数据就返回true否则返回false
bool items6 = list.Any(e => e.Age > 30);// 有一条数据就返回true否则返回false,找到一条符合条件的数据就返回true,且不会继续寻找后面的数据,否则返回falseforeach (Employee item in items1)
{Console.WriteLine(item.Name);
}

获取一条数据(是否带参数的两种写法):

Single:有且只有一条满足要求的数据;
SingleOrDefault:最多只有一条满足要求的数据;
First:至少有一条,返回第一条;
FirstOrDefault:返回第一条或者默认值;

C# LINQ 查询方法详解:Single, SingleOrDefault, First, FirstOrDefault

在 C# 的 LINQ 查询中,Single, SingleOrDefault, FirstFirstOrDefault 是常用的元素检索方法,它们有不同的行为和使用场景。下面我将详细解释它们的区别和使用方法。

方法对比表

方法返回值条件无匹配时行为多个匹配时行为使用场景
Single有且只有一条满足要求的数据抛出 InvalidOperationException抛出 InvalidOperationException确保只有唯一匹配项时
SingleOrDefault最多只有一条满足要求的数据返回默认值(如 null 或 0)抛出 InvalidOperationException期望0或1个匹配项时
First至少有一条,返回第一条抛出 InvalidOperationException返回第一个匹配项需要第一个匹配项且确保存在时
FirstOrDefault返回第一条或者默认值返回默认值(如 null 或 0)返回第一个匹配项需要第一个匹配项或处理空结果时

详细解释与示例

1. Single

  • 行为:要求序列中有且只有一个元素满足条件
  • 异常情况
    • 如果没有匹配项 → 抛出 InvalidOperationException
    • 如果有多个匹配项 → 抛出 InvalidOperationException
  • 使用场景:当你确定只有一个匹配项时
  • 示例
// 查找唯一ID为3的员工
var employee = list.Single(e => e.Id == 3);
Console.WriteLine(employee.Name); // 输出: lily// 以下情况会抛出异常:
// var invalid1 = list.Single(e => e.Age > 40); // 无匹配项
// var invalid2 = list.Single(e => e.Age == 35); // 多个匹配项

2. SingleOrDefault

  • 行为:要求序列中最多只有一个元素满足条件
  • 异常情况
    • 如果没有匹配项 → 返回类型的默认值(如 null, 0 等)
    • 如果有多个匹配项 → 抛出 InvalidOperationException
  • 使用场景:当你期望0或1个匹配项时
  • 示例
// 查找唯一ID为10的员工(不存在)
var employee1 = list.SingleOrDefault(e => e.Id == 10);
Console.WriteLine(employee1?.Name ?? "未找到"); // 输出: 未找到// 查找唯一ID为3的员工(存在)
var employee2 = list.SingleOrDefault(e => e.Id == 3);
Console.WriteLine(employee2.Name); // 输出: lily// 以下情况会抛出异常:
// var invalid = list.SingleOrDefault(e => e.Age == 35); // 多个匹配项

3. First

  • 行为:返回序列中第一个满足条件的元素
  • 异常情况
    • 如果没有匹配项 → 抛出 InvalidOperationException
    • 如果有多个匹配项 → 返回第一个匹配项
  • 使用场景:当你需要第一个匹配项确保存在
  • 示例
// 查找第一个年龄大于30的员工
var employee = list.First(e => e.Age > 30);
Console.WriteLine(employee.Name); // 输出: jim// 以下情况会抛出异常:
// var invalid = list.First(e => e.Age > 40); // 无匹配项

4. FirstOrDefault

  • 行为:返回序列中第一个满足条件的元素,或默认值
  • 异常情况
    • 如果没有匹配项 → 返回类型的默认值(如 null, 0 等)
    • 如果有多个匹配项 → 返回第一个匹配项
  • 使用场景:当你需要第一个匹配项或处理空结果
  • 示例
// 查找第一个年龄大于40的员工(不存在)
var employee1 = list.FirstOrDefault(e => e.Age > 40);
Console.WriteLine(employee1?.Name ?? "未找到符合条件的员工"); // 输出: 未找到符合条件的员工// 查找第一个年龄为35的员工
var employee2 = list.FirstOrDefault(e => e.Age == 35);
Console.WriteLine(employee2.Name); // 输出: lily

性能考虑

  • First/FirstOrDefault 通常比 Single/SingleOrDefault 性能更好,因为它们找到第一个匹配项就返回
  • Single/SingleOrDefault 需要遍历整个序列以确保只有一个匹配项

默认值说明

对于引用类型:

  • 默认值为 null
  • 使用前应检查是否为 null

对于值类型:

  • 默认值为该类型的默认值(如 int 为 0,bool 为 false)
  • 使用前应检查是否等于默认值

最佳实践建议

  1. 优先使用 FirstOrDefault

    • 大多数情况下是最安全的选择
    • 避免异常处理,代码更简洁
  2. 谨慎使用 Single

    • 仅在确保只有一个匹配项时使用
    • 数据库主键查询是典型场景
  3. 避免在可能多个匹配项时使用 SingleOrDefault

    • 多个匹配项会抛出异常
    • 使用前应确认数据唯一性
  4. 处理默认值

var result = list.FirstOrDefault();
if (result != null) // 对于引用类型
{// 处理结果
}var valueResult = intList.FirstOrDefault();
if (valueResult != default) // 对于值类型
{// 处理结果
}

总结对比图

       唯一性要求         存在性要求↓               ↓
Single:     有且只有一条满足要求的数据
SingleOrDefault: 最多只有一条满足要求的数据
First:              至少有一条,返回第一条
FirstOrDefault:         返回第一条或者默认值

根据你的具体需求选择合适的方法:

  • 需要唯一结果 → SingleSingleOrDefault
  • 需要第一个结果 → FirstFirstOrDefault
  • 不确定是否存在结果 → ...OrDefault 版本
  • 确保结果存在 → 不带 OrDefault 的版本

排序:

OrderBy() 对数据正序排序;
OrderByDescending()倒序排序;
list.OrderBy(e=>e.Age);
对于简单类型排序,也许不用lambda表达式。特殊案例:按照最后一个字符排序,用Guid或者随机数进行随机排序。

C# LINQ 排序方法详解:OrderBy 与 OrderByDescending

在 C# 的 LINQ 查询中,OrderBy()OrderByDescending() 是用于对数据进行排序的核心方法。下面我将详细解释它们的用法和区别。

基本概念

方法描述排序方向
OrderBy()对序列元素进行升序排序从小到大 (A→Z, 1→9)
OrderByDescending()对序列元素进行降序排序从大到小 (Z→A, 9→1)

基本语法

// 正序排序
IEnumerable<TSource> sortedAsc = source.OrderBy(e => e.Property);// 倒序排序
IEnumerable<TSource> sortedDesc = source.OrderByDescending(e => e.Property);

示例解释:list.OrderBy(e => e.Age)

// 使用 OrderBy 按年龄正序排序
var sortedByAge = list.OrderBy(e => e.Age);

执行过程:

  1. 遍历 list 中的所有员工
  2. 提取每个员工的 Age 属性值作为排序键
  3. 按照年龄从小到大排序
  4. 返回排序后的新序列(原始列表不会被修改)

排序结果:

假设原始列表年龄为:[28, 33, 35, 16, 25, 35, 35, 33]
排序后变为:[16, 25, 28, 33, 33, 35, 35, 35]

完整排序示例

1. 单属性排序

// 按年龄正序排序
var byAgeAsc = list.OrderBy(e => e.Age);// 按工资倒序排序
var bySalaryDesc = list.OrderByDescending(e => e.Salary);

2. 多级排序(ThenBy/ThenByDescending)

// 先按性别正序,再按年龄倒序
var multiSort = list.OrderBy(e => e.Gender)        // 先按性别排序(false在前,true在后).ThenByDescending(e => e.Age);  // 再按年龄降序// 先按年龄倒序,再按工资正序
var multiSort2 = list.OrderByDescending(e => e.Age).ThenBy(e => e.Salary);

3. 自定义排序逻辑

// 按姓名长度排序
var byNameLength = list.OrderBy(e => e.Name.Length);// 按工资范围分组排序
var bySalaryRange = list.OrderBy(e => 
{if (e.Salary < 3000) return 1;    // 低薪组if (e.Salary < 6000) return 2;    // 中薪组return 3;                         // 高薪组
});

C# 特殊排序场景详解:简单类型、末位字符与随机排序

在 C# 中,虽然 Lambda 表达式是 LINQ 排序的常见方式,但在某些特殊场景下,我们可以使用更简洁或更灵活的方法进行排序。下面我将详细解释这些特殊排序场景的实现方式。

一、简单类型排序(不使用 Lambda 表达式)

1. 基本排序方法

List<int> numbers = new List<int> { 5, 2, 8, 1, 9 };// 升序排序(不使用 Lambda)
var sortedAsc = numbers.OrderBy(n => n); // 传统方式
var simpleAsc = numbers.Order();         // C# 11+ 简化方式// 降序排序(不使用 Lambda)
var sortedDesc = numbers.OrderByDescending(n => n); // 传统方式
var simpleDesc = numbers.OrderDescending();         // C# 11+ 简化方式

2. C# 11+ 的简化语法

在 C# 11 及以上版本中,对于简单类型集合,可以直接使用:

// 升序排序
var sorted = numbers.Order();// 降序排序
var sortedDesc = numbers.OrderDescending();

3. 字符串集合排序

List<string> fruits = new List<string> { "Apple", "Banana", "Cherry", "Date" };// 按字母顺序排序
var alphabetical = fruits.Order(); // ["Apple", "Banana", "Cherry", "Date"]// 按长度排序(仍需使用 Lambda)
var byLength = fruits.OrderBy(f => f.Length); // ["Date", "Apple", "Banana", "Cherry"]

二、特殊案例:按最后一个字符排序

1. 基本实现

List<string> words = new List<string> { "apple", "banana", "cherry", "date" };// 按最后一个字符升序排序
var byLastChar = words.OrderBy(w => w[1](@ref)); // ^1 表示最后一个字符// 结果: ["banana"(a), "apple"(e), "date"(e), "cherry"(y)]

2. 处理空字符串和单字符

public static char SafeLastChar(string s)
{return string.IsNullOrEmpty(s) ? '\0' : s[1](@ref);
}// 安全获取最后一个字符并排序
var safeSorted = words.Where(w => !string.IsNullOrEmpty(w)).OrderBy(w => SafeLastChar(w));

3. 多级排序(先按长度,再按末字符)

var multiSort = words.OrderBy(w => w.Length).ThenBy(w => w[1](@ref));

三、随机排序(使用 Guid 或随机数)

1. 使用 Guid 随机排序

// 使用 Guid 生成随机排序键
var randomOrder = list.OrderBy(e => Guid.NewGuid()).ToList();// 原理:为每个元素分配唯一的随机 Guid,然后排序

优点

  • 实现简单,一行代码
  • 分布均匀,随机性好

缺点

  • 性能较差(生成 Guid 开销大)
  • 不适用于大数据集

限制结果集,获取部分数据:

Ship(n)跳过n条数据,Take(n) 获取n条数据。
案例:获取从第2条开始获取3条数据 var orderedItems1 = list.Skip(2).Take(3);
Skip()、Take()也可以单独使用。

C# LINQ 分页操作详解:Skip 与 Take

在 C# 的 LINQ 查询中,Skip()Take() 是两个用于数据分页和子集选择的核心方法。它们通常结合使用来实现高效的分页功能。

基本概念

方法描述行为
Skip(n)跳过序列中的前 n 个元素返回剩余元素的序列
Take(n)从序列开头获取前 n 个元素返回包含前 n 个元素的序列

基本语法

// 跳过前 n 个元素
IEnumerable<T> skipped = source.Skip(n);// 获取前 n 个元素
IEnumerable<T> taken = source.Take(n);// 组合使用(分页)
IEnumerable<T> page = source.Skip(pageIndex * pageSize).Take(pageSize);

方法详解

1. Skip(n)

  • 功能:跳过序列中的前 n 个元素
  • 参数:要跳过的元素数量
  • 返回值:包含源序列中跳过指定数量元素后的剩余元素
  • 边界情况
    • 如果 n ≤ 0:返回整个序列
    • 如果 n ≥ 序列长度:返回空序列
List<int> numbers = new List<int> {1, 2, 3, 4, 5};// 跳过前 2 个元素
var skipped = numbers.Skip(2); // [3, 4, 5]// 跳过 0 个元素
var skipZero = numbers.Skip(0); // [1, 2, 3, 4, 5]// 跳过超过序列长度
var skipLarge = numbers.Skip(10); // 空序列

2. Take(n)

  • 功能:从序列开头获取指定数量的元素
  • 参数:要获取的元素数量
  • 返回值:包含源序列前 n 个元素的序列
  • 边界情况
    • 如果 n ≤ 0:返回空序列
    • 如果 n ≥ 序列长度:返回整个序列
List<int> numbers = new List<int> {1, 2, 3, 4, 5};// 获取前 3 个元素
var taken = numbers.Take(3); // [1, 2, 3]// 获取 0 个元素
var takeZero = numbers.Take(0); // 空序列// 获取超过序列长度
var takeLarge = numbers.Take(10); // [1, 2, 3, 4, 5]

组合使用:分页实现

基本分页公式

int pageIndex = 2; // 第3页(从0开始计数)
int pageSize = 3;  // 每页3条var page = source.Skip(pageIndex * pageSize).Take(pageSize);

完整分页示例

List<Employee> employees = GetEmployees(); // 假设有100名员工int pageSize = 10; // 每页10条// 获取第3页数据(索引从0开始)
var page3 = employees.OrderBy(e => e.LastName) // 先排序.Skip(2 * pageSize)       // 跳过前20条.Take(pageSize);          // 取10条Console.WriteLine($"第3页数据(共{page3.Count()}条):");
foreach (var emp in page3)
{Console.WriteLine($"{emp.LastName}, {emp.FirstName}");
}

分页辅助方法

public static class PagingExtensions
{public static IEnumerable<T> Page<T>(this IEnumerable<T> source, int pageIndex, int pageSize){return source.Skip(pageIndex * pageSize).Take(pageSize);}public static IQueryable<T> Page<T>(this IQueryable<T> source, int pageIndex, int pageSize){return source.Skip(pageIndex * pageSize).Take(pageSize);}
}// 使用
var page = employees.Page(2, 10); // 获取第3页,每页10条

集合函数:

Max()、Min()、Average()、Sum()、Count()。
LINQ中所有的扩展方法几乎都是针对IEnumerable接口的,而几乎所有能返回集合的都返回IEnumerable,所以是可以把几乎所有方法“链式使用”的。list.Where(e=>e.Age>30).Min(e=>e.Age);

C# LINQ 聚合方法与链式调用详解

LINQ 聚合方法概述

方法描述返回值类型空集合行为
Max()返回序列中的最大值数值类型抛出异常
Min()返回序列中的最小值数值类型抛出异常
Average()返回序列的平均值数值类型抛出异常
Sum()返回序列的总和数值类型返回0
Count()返回序列的元素数量int返回0

链式调用原理

LINQ 的核心设计理念是链式调用(Method Chaining),这得益于:

  1. 几乎所有 LINQ 方法都是针对 IEnumerable<T> 接口的扩展方法
  2. 大多数方法返回 IEnumerable<T>IOrderedEnumerable<T>
  3. 每个方法操作前一个方法返回的结果集

链式调用示例

var result = employees.Where(e => e.Department == "IT")  // 返回 IEnumerable<Employee>.OrderBy(e => e.LastName)          // 返回 IOrderedEnumerable<Employee>.Select(e => new { e.Name, e.Salary }) // 返回 IEnumerable<匿名类型>.Take(10);                         // 返回 IEnumerable<匿名类型>

代码解析:list.Where(e=>e.Age>30).Min(e=>e.Age);

执行步骤

  1. 过滤阶段

    var filtered = list.Where(e => e.Age > 30);
    
    • 遍历原始集合 list
    • 筛选出年龄大于30的元素
    • 返回 IEnumerable<Employee> 类型的结果集
  2. 聚合阶段

    var minAge = filtered.Min(e => e.Age);
    
    • 遍历过滤后的结果集 filtered
    • 提取每个元素的 Age 属性
    • 找出这些年龄值中的最小值
    • 返回 int 类型的最小年龄值

等效传统代码

int minAge = int.MaxValue;
bool found = false;foreach (var employee in list)
{if (employee.Age > 30){found = true;if (employee.Age < minAge){minAge = employee.Age;}}
}if (!found)
{throw new InvalidOperationException("序列不包含任何元素");
}

注意事项

  1. 空集合处理

    • 如果 Where 过滤后没有元素,Min() 会抛出 InvalidOperationException
    • 安全处理方式:
      var minAge = list.Where(e => e.Age > 30).Select(e => e.Age).DefaultIfEmpty(0).Min();
      
  2. 性能优化

    • 对于大型集合,考虑使用更高效的算法:
      int? minAge = null;
      foreach (var e in list)
      {if (e.Age > 30 && (minAge == null || e.Age < minAge)){minAge = e.Age;}
      }
      

其他聚合方法链式调用示例

1. 计算平均值

double avgSalary = employees.Where(e => e.Department == "Sales").Average(e => e.Salary);

2. 求和统计

decimal totalSales = salesRecords.Where(s => s.Year == 2023).Sum(s => s.Amount);

3. 计数统计

int highEarners = employees.Where(e => e.Salary > 100000).Count();

4. 多级聚合

var stats = employees.GroupBy(e => e.Department).Select(g => new {Department = g.Key,MinSalary = g.Min(e => e.Salary),MaxSalary = g.Max(e => e.Salary),AvgSalary = g.Average(e => e.Salary)});

链式调用的高级应用

1. 条件聚合

var result = products.Where(p => p.Category == "Electronics").Select(p => p.Price).DefaultIfEmpty(0) // 处理空集合.Average();

2. 组合使用

var analysis = orders.Where(o => o.Date.Year == 2023).GroupBy(o => o.CustomerId).Select(g => new {CustomerId = g.Key,TotalOrders = g.Count(),TotalAmount = g.Sum(o => o.Amount),AvgOrderValue = g.Average(o => o.Amount)}).OrderByDescending(x => x.TotalAmount).Take(10);

3. 空值处理技巧

decimal? maxDiscount = customers.Where(c => c.IsPremium).Select(c => c.DiscountPercentage).Where(d => d.HasValue).DefaultIfEmpty(0).Max();

分组:

GroupBy()方法参数是分组条件表达式,返回值为IGrouping<TKey,TSource>类型的泛型IEnumerable,也就是每一组以一个IGrouping对象的形式返回。IGrouping是一个继承自IEnumerable的接口,IGrouping中Key属性表示这一组的分数的值。例子:根据年龄分组,获取每组人数、最高工资、平均工资。

C# LINQ GroupBy 分组方法详解

GroupBy 方法核心概念

GroupBy() 是 LINQ 中最强大的数据分组方法,它允许您根据指定的键将数据集合划分为多个逻辑组。

方法签名

IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IEnumerable<TSource> source,Func<TSource, TKey> keySelector
)

关键特性

特性说明
分组条件通过 keySelector 函数指定分组依据
返回值IEnumerable<IGrouping<TKey, TSource>>
分组对象每个分组是一个 IGrouping<TKey, TSource> 对象
分组访问可以通过 Key 属性访问分组键
元素访问分组本身是可枚举的,包含该组的所有元素

IGrouping 接口解析

IGrouping<TKey, TSource> 接口定义如下:

public interface IGrouping<out TKey, out TElement> : IEnumerable<TElement>
{TKey Key { get; }
}

核心特性

  1. 继承自 IEnumerable

    • 每个分组本身是一个可枚举集合
    • 可以遍历分组内的所有元素
  2. Key 属性

    • 表示该分组的键值
    • 类型为 TKey,由分组条件决定

基本用法示例

1. 简单分组

List<Employee> employees = new List<Employee>
{new Employee { Name = "Alice", Department = "HR", Salary = 50000 },new Employee { Name = "Bob", Department = "IT", Salary = 60000 },new Employee { Name = "Charlie", Department = "HR", Salary = 55000 },new Employee { Name = "David", Department = "IT", Salary = 70000 }
};// 按部门分组
var groups = employees.GroupBy(e => e.Department);foreach (var group in groups)
{Console.WriteLine($"部门: {group.Key}");foreach (var emp in group){Console.WriteLine($" - {emp.Name}: {emp.Salary}");}
}

输出结果

部门: HR- Alice: 50000- Charlie: 55000
部门: IT- Bob: 60000- David: 70000

2. 分组后聚合计算

var departmentStats = employees.GroupBy(e => e.Department).Select(g => new {Department = g.Key,EmployeeCount = g.Count(),AverageSalary = g.Average(e => e.Salary),MaxSalary = g.Max(e => e.Salary)});foreach (var stat in departmentStats)
{Console.WriteLine($"{stat.Department}部门: " +$"人数={stat.EmployeeCount}, " +$"平均工资={stat.AverageSalary}, " +$"最高工资={stat.MaxSalary}");
}

输出结果

HR部门: 人数=2, 平均工资=52500, 最高工资=55000
IT部门: 人数=2, 平均工资=65000, 最高工资=70000

高级分组技巧

1. 复合键分组

// 按部门和薪资范围分组
var groups = employees.GroupBy(e => new {e.Department,SalaryRange = e.Salary / 10000 * 10000 // 按万为单位分组
});foreach (var group in groups)
{Console.WriteLine($"部门: {group.Key.Department}, " +$"薪资范围: {group.Key.SalaryRange}-{group.Key.SalaryRange + 9999}");foreach (var emp in group){Console.WriteLine($" - {emp.Name}: {emp.Salary}");}
}

2. 分组后元素转换

// 分组后只保留员工姓名
var nameGroups = employees.GroupBy(e => e.Department, e => e.Name); // 元素选择器foreach (var group in nameGroups)
{Console.WriteLine($"部门: {group.Key}");Console.WriteLine($"员工: {string.Join(", ", group)}");
}

3. 自定义结果选择器

var results = employees.GroupBy(keySelector: e => e.Department,resultSelector: (key, elements) => new {Department = key,Employees = elements.Select(e => e.Name),TotalSalary = elements.Sum(e => e.Salary)});foreach (var result in results)
{Console.WriteLine($"{result.Department}部门: " +$"总薪资={result.TotalSalary}, " +$"员工={string.Join(", ", result.Employees)}");
}

IGrouping 的实际应用

1. 直接访问分组键

var groups = employees.GroupBy(e => e.Department);// 获取所有部门列表
var departments = groups.Select(g => g.Key).ToList();
// ["HR", "IT"]

2. 分组嵌套处理

foreach (var group in groups)
{Console.WriteLine($"--- {group.Key} 部门员工详情 ---");// 分组内排序var sortedEmployees = group.OrderByDescending(e => e.Salary);foreach (var emp in sortedEmployees){Console.WriteLine($"{emp.Name}: {emp.Salary}");}
}

3. 转换为字典

// 将分组转换为字典
Dictionary<string, List<Employee>> departmentDict = groups.ToDictionary(g => g.Key, g => g.ToList());// 访问特定部门
var hrEmployees = departmentDict["HR"];

性能注意事项

  1. 延迟执行

    • GroupBy() 是延迟执行方法
    • 实际分组操作在枚举结果时发生
  2. 内存占用

    • 分组操作需要将整个数据集加载到内存
    • 大数据集考虑使用数据库分组
  3. 数据库优化

    • 在 Entity Framework 中,GroupBy() 会转换为 SQL 的 GROUP BY
    • 确保分组字段有索引
// EF Core 中的分组
var departmentStats = dbContext.Employees.GroupBy(e => e.Department).Select(g => new {Department = g.Key,Count = g.Count()}).ToList();

投影:

把集合中的每一项转换为另外一种类型。
IEnumerable names = list.Select(e=> e.Gender?“男”:“女”);
var dogs = list.Select(p => new Dog { NickName = e.Name, Age = e.Age });

C# LINQ 投影操作详解

投影的本质

投影(Projection)是 LINQ 中的核心概念,指的是将集合中的每个元素转换为另一种形式或类型的操作。这类似于数学中的映射函数,将输入集合中的每个元素映射到输出集合中的新元素。

C# LINQ 投影操作详解

投影的本质

投影(Projection)是 LINQ 中的核心概念,指的是将集合中的每个元素转换为另一种形式或类型的操作。这类似于数学中的映射函数,将输入集合中的每个元素映射到输出集合中的新元素。

核心方法:Select()

Select() 方法是 LINQ 中实现投影的主要方式,它允许您:

  • 提取对象的特定属性
  • 创建新的对象结构
  • 执行计算并返回结果
  • 转换数据类型

方法签名

IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source,Func<TSource, TResult> selector
)

投影的基本用法

1. 提取属性值

List<Employee> employees = new List<Employee>
{new Employee { Name = "Alice", Age = 30 },new Employee { Name = "Bob", Age = 25 }
};// 投影到名字列表
IEnumerable<string> names = employees.Select(e => e.Name);
// 结果: ["Alice", "Bob"]

2. 创建新对象

// 投影到匿名对象
var employeeInfos = employees.Select(e => new {Name = e.Name,BirthYear = DateTime.Now.Year - e.Age
});
// 结果: [{Name="Alice", BirthYear=1993}, {Name="Bob", BirthYear=1998}]

3. 转换类型

// 转换为DTO对象
List<EmployeeDTO> dtos = employees.Select(e => new EmployeeDTO {EmployeeName = e.Name,Age = e.Age
}).ToList();

高级投影技巧

1. 带索引的投影

// 包含元素索引
var indexed = employees.Select((e, index) => new {Index = index,e.Name
});
// 结果: [{Index=0, Name="Alice"}, {Index=1, Name="Bob"}]

2. 嵌套投影

// 嵌套集合投影
var departments = new List<Department>
{new Department {Name = "Dev",Employees = new List<Employee> { /* ... */ }}
};var employeeNamesByDept = departments.Select(d => new {DeptName = d.Name,EmployeeNames = d.Employees.Select(e => e.Name)
});

3. 条件投影

// 根据条件返回不同投影
var mixed = employees.Select(e => e.Age > 25 ? new { e.Name, Category = "Senior" } : new { e.Name, Category = "Junior" });

4. 计算字段投影

// 计算年薪(月薪*12)
var annualSalaries = employees.Select(e => new {e.Name,AnnualSalary = e.MonthlySalary * 12
});

实际应用场景

1. 数据转换(Entity → DTO)

// 数据库实体转视图模型
var viewModels = dbContext.Products.Where(p => p.Price > 100).Select(p => new ProductViewModel {Id = p.Id,Name = p.Name,Price = p.Price * 1.1 // 添加增值税}).ToList();

2. 数据简化

// 只选择需要的字段
var lightweights = bigList.Select(item => new {item.Id,item.CreatedDate
});

3. 计算字段

// 计算BMI
var bmiData = persons.Select(p => new {p.Name,BMI = p.Weight / (p.Height * p.Height)
});

4. 组合数据

// 组合多个来源的数据
var combined = employees.Select(e => new {e.Name,DepartmentName = departments.First(d => d.Id == e.DeptId).Name
});

性能考虑

  1. 延迟执行

    • Select() 是延迟执行的,只有在实际枚举结果时才会执行投影
  2. 高效转换

    • 在数据库查询中(如 EF Core),Select() 会转换为 SQL 的 SELECT 子句
    • 只选择需要的字段可以减少数据传输量
  3. 避免重复计算

    // 低效:重复计算
    var inefficient = list.Select(x => new {Value = HeavyCalculation(x)});// 高效:预计算
    var efficient = list.Select(x => {var result = HeavyCalculation(x);return new { Value = result };});
    

与 SelectMany() 的区别

特性SelectSelectMany
输入单个元素元素集合
输出转换后的单个元素展平的集合
嵌套集合返回嵌套集合展平嵌套集合
使用场景简单转换处理一对多关系
// Select 返回嵌套集合
var nested = departments.Select(d => d.Employees.Select(e => e.Name));// SelectMany 展平嵌套集合
var flat = departments.SelectMany(d => d.Employees.Select(e => e.Name));

最佳实践

  1. 明确目标类型

    • 使用具体类型而非 var 提高可读性
    List<string> names = employees.Select(e => e.Name).ToList();
    
  2. 避免过度投影

    • 只选择真正需要的字段
    • 避免选择整个对象再丢弃不需要的字段
  3. 结合过滤

    // 先过滤再投影,提高效率
    var activeUsers = users.Where(u => u.IsActive).Select(u => u.Email);
    
  4. 使用查询语法

    // 与方法语法等效
    var results = from e in employeesselect new { e.Name, e.Age };
    

集合转换:

有一些地方需要数组类型或者List类型的变量,我们可以用ToArray()方法和ToList()分别把IEnumerable<T>转换为数组类型和List<T>类型。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/95567.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/95567.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第 2 讲:Kafka Topic 与 Partition 基础

课程概述 在第一篇课程中&#xff0c;我们了解了 Kafka 的基本概念和简单的 Producer/Consumer 实现。 本篇课程将深入探讨 Kafka 的核心机制&#xff1a;Topic 和 Partition。 学习目标 通过本课程&#xff0c;您将掌握&#xff1a; Topic 和 Partition 的设计原理&#x…

三阶Bezier曲线曲率极值及对应的u的计算方法

三阶&#xff08;三次&#xff09;Bezier曲线的曲率极值及其对应的参数 u 的计算是一个复杂的非线性优化问题。由于三阶Bezier曲线是参数化曲线&#xff0c;其曲率表达式较为复杂&#xff0c;通常无法通过解析方法直接求得所有极值点&#xff0c;但可以通过求解曲率导数为零的方…

Unity:XML笔记(二)——Xml序列化、反序列化、IXmlSerializable接口

写在前面&#xff1a;写本系列(自用)的目的是回顾已经学过的知识、记录新学习的知识或是记录心得理解&#xff0c;方便自己以后快速复习&#xff0c;减少遗忘。三、Xml序列化序列化就是把想要存储的内容转换为字节序列用于存储或传递。1、序列化我们先创建一个类&#xff0c;之…

java注解、Lambda表达式、Servlet

一、Java注解注解的概念&#xff1a; Java注解是代码中的元数据&#xff0c;可以用于描述其他代码。注解在编译、类加载、运行时被处理&#xff0c;并且不会改变代码逻辑。注解的用途&#xff1a; 提供代码元信息&#xff0c;如 Override 表明一个方法覆盖了父类的方法。 编译检…

【单片机day02】

GPIO&#xff1a;Genral Purpose Input/Output&#xff0c;GPIO是51单片机和外界交互最基本的方式工作模式&#xff1a;输出模式&#xff1a;单片机给定引脚一个电平(高电平(5V) 低电平(0V)),控制引脚实现高低电平输入模式&#xff1a;检测引脚电平变化GPIO水龙头输出模式&…

Java中最常用的设计模式

Java设计模式之结构型—代理模式-CSDN博客 观察者模式详解-CSDN博客 单例模式详解-CSDN博客 Java设计模式之结构型—享元模式-CSDN博客 Java设计模式之创建型—建造者模式-CSDN博客 Java设计模式之结构型—工厂模式-CSDN博客 Java设计模式之结构型—适配器模式-CSDN博客 …

使用Axure动态面板制作轮播图案例详解

在现代网页设计中&#xff0c;轮播图&#xff08;Carousel&#xff09;是一种常见且高效的展示方式&#xff0c;用于在同一空间内循环展示多张图片或内容。Axure RP作为一款强大的原型设计工具&#xff0c;提供了动态面板和丰富的交互事件功能&#xff0c;使得制作轮播图变得简…

VUE的中 computed: { ...mapState([‘auditObj‘]), }写法详解

具体解析&#xff1a;computed&#xff1a;这是 Vue 组件选项中的计算属性&#xff0c;用于声明依赖于其他数据而存在的派生数据。计算属性会根据依赖进行缓存&#xff0c;只有当依赖的数据发生变化时才会重新计算。mapState&#xff1a;这是 Vuex 提供的一个辅助函数&#xff…

【ProtoBuf】以 “数据秘语” 筑联络:通讯录项目实战 1.0 启步札记

文章目录引言筑路之备&#xff1a;快速上手ProtoBuf步骤一&#xff1a;创建.proto文件⽂件规范添加注释指定 proto3 语法package 声明符定义消息&#xff08;message&#xff09;定义消息字段【定义联系人 message】字段唯一编号的范围步骤2&#xff1a;编译 contacts.proto ⽂…

在 macOS 下升级 Python 几种常见的方法

在 macOS 下升级 Python 有几种常见的方法&#xff0c;具体取决于你最初是如何安装 Python 的。了解你的安装方式是关键。 首先&#xff0c;你需要知道你当前 Python 版本以及它的安装路径。 检查 Python 版本&#xff1a; python --version # 可能指向 Python 2.x python3 …

Linux 入门到精通,真的不用背命令!零基础小白靠「场景化学习法」,3 个月拿下运维 offer,第二十五天

三、Shell脚本编程 Shell脚本语言的运算 算数运算 shell支持算术运算&#xff0c;但只支持整数&#xff0c;不支持小数 Bash中的算术运算 -- 加法运算 -- - 减法运算 -- * 乘法运算 -- / 除法运算 -- % 取模&#xff0c;即取余数 -- ** 乘方 ​ #乘法符号在有些场景需要转…

SpringAI系列---【多租户记忆和淘汰策略】

1.多租户工作原理 2.引入jdbc的pom spring官网链接&#xff1a;https://docs.spring.io/spring-ai/reference/api/chat-memory.html&#xff0c;推荐使用官网的jdbc。 阿里巴巴ai链接&#xff1a;https://github.com/alibaba/spring-ai-alibaba/tree/main/community/memories j…

Linux gzip 命令详解:从基础到高级用法

Linux gzip 命令详解&#xff1a;从基础到高级用法 在 Linux 系统中&#xff0c;文件压缩与解压缩是日常运维和文件管理的常见操作。gzip&#xff08;GNU Zip&#xff09;作为一款经典的压缩工具&#xff0c;凭借其高效的压缩算法和简洁的使用方式&#xff0c;成为 Linux 用户处…

Redis有什么优点和缺点?

优点&#xff1a;极致性能&#xff1a; 基于内存操作和高效的单线程 I/O 模型&#xff0c;读写速度极快。数据结构丰富&#xff1a; 支持多种数据结构&#xff0c;如 String、Hash、List、Set、ZSet、Stream、Geo 等&#xff0c;编程模型灵活。持久化与高可用&#xff1a; 提供…

NestJS 3 分钟搭好 MySQL + MongoDB,CRUD 复制粘贴直接运行

基于上一篇内容《为什么现代 Node 后端都选 NestJS TypeScript&#xff1f;这组合真香了》&#xff0c;这篇文章继续写数据库的连接。 所以今天把MySQL、MongoDB全接上&#xff0c;做个小实例。朋友们项目里用什么数据库可以视情况而定。 这里的功能分别为&#xff1a; MySQ…

用了企业微信 AI 半年,这 5 个功能让我彻底告别重复劳动

每天上班不是在整理会议纪要&#xff0c;就是在翻聊天记录找文件&#xff0c;写文档还要自己抠数据…… 这些重复劳动是不是也在消耗你的时间&#xff1f;作为用了企业微信 AI 功能半年的 “老用户”&#xff0c;我必须说&#xff1a;企业微信 AI 的这 5 个功能&#xff0c;真的…

从入门到高手,Linux就应该这样学【好书推荐】

从入门到高手&#xff0c;请这样学Linux 一、Linux基础与终端操作 1.1 Linux简介 Linux 是一种开源的类 Unix 操作系统&#xff0c;以其稳定性、安全性和高效性被广泛应用于服务器、嵌入式系统及开发环境中。掌握基本命令和操作技巧是 Linux 学习的关键。 1.2 终端基础 打开…

【数据可视化-104】安徽省2025年上半年GDP数据可视化分析:用Python和Pyecharts打造炫酷大屏

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

组件库UI自动化

一、背景 背景&#xff1a; 组件库全局改动场景多&#xff0c;组件之间耦合场景多–时常需要全场景回归组件库demo有200多个&#xff0c;手动全局回归耗时耗力细微偏差纯视觉无法辨别 可行性分析&#xff1a; 组件库功能占比 L1&#xff08;视觉层&#xff09;&#xff1a;图片…

面试题:JVM与G1要点总结

一.Java内存区域 1.运行时数据区的介绍 2.站在线程的角度看Java内存区域 3.深入分析堆和栈的区别 4.方法的出入栈和栈上分配、逃逸分析及TLAB 5.虚拟机中的对象创建步骤 6.对象的内存布局 1.运行时数据区的介绍 运行时数据区的类型&#xff1a;程序计数器、Java虚拟机栈、本地方…