
LINQ详解
Linq原理
委托
委托(delegate):是一种引用类型变量,用于存储某个方法的引用地址。
定义格式:
public delegate 返回值类型 委托类型名字 (参数类型1 参数名字, 参数类型2 参数名字,.....);
案例:
public delegate int Calculate(int x,int y);
static public int Add(int x,int y)
{
return x+y;
}
static public int Multiply(int x,int y)
{
return x*y;
}
Calculate cal0=new Calculate(Add);
Calculate cal1=new Calculate(Multiply);
//等同于
Calculate cal0=Add;
Calculate cal1=Multiply;
//调用方法
cal0.Invoke(1,2);
cal1(1,2);
Invoke调用委托方法与()直接调用相同
委托存在的意义
回调方法(CallBack Method):当某个任务执行完毕后或者某事件触发后,调用的方法。
- 按钮彼此只有皮肤不同,所以肯定是在同一Class Button通过new 出来的对象。
- 按钮被点击后,所执行的任务彼此不同,即调用了不同的响应方法。
- 按钮对象中会保持一个回调方法,当点击后调用此回溯方法。
class Button{
//1.声明一个点击消息响应的方法类型签名(委托)
public delegate void OnClickDelegate();
//2.声明一个OnClickDelegate类型委托
public OnClickDelegate onclick=null;
//3.点击事件触发后,调用onclick委托存储的方法
public void Click()
{
if(onclick!=null)onclick();
}
}
注意:
- 委托是可以指向方法的类型,调用委托变量时执行的就是变量指向的方法。
- .NET中定义了泛型委托Action(无返回值)和Func(有返回值),所以一般不用自定义委托类型。
委托变量不仅可以指向普通方法,还可以指向匿名方法。
static void Main(string[] args)
{
Action f1=delegate(){
Console.WriteLine("liuliu");
};
f1();
Action<string, int> f2 = delegate (string n, int i){
Console.WriteLine($"n={n},i={i}");
};
f2("liuliu",18);
Func<int, int, int> f3 = delegate (int a, int b){
return a + b;
};
Console.WriteLine(f3(1, 2));
}
匿名方法可以写成Lambda表达式
Lambda
Lambda表达式(Lambda Expression)是一个匿名函数,即没有函数名的函数。
C#中的Lambda表达式使用Lambda运算符=>表示,该运算符读为goes to
运算符将表达式分为两部分,左边是输入的参数,右边是表达式的主体
(参数列表) => {表达式或者语法块}
static void Main(string[] args)
{
//可以省略参数数据类型,因为编译能根据委托类型推断出参数的类型,用=>引出来方法体
Func<int, int, int> f3 = (a, b)=>
{
return a + b;
};
Console.WriteLine(f3(1, 2));
//如果委托没有返回值,且方法体只有一行代码,可省略{}
Action f1=()=>Console.WriteLine("liuliu");
f1();
//如果=>之后的方法体中只有一行代码,且方法有返回值,那么可以省略方法体的{}以及return
Func<int, int, int> f3 =(a, b)=> a + b;
Console.WriteLine(f3(1, 2));
//如果只有一个参数,参数的()可以省略
Action<string, int> f2 = n => Console.WriteLine($"n={n},i={i}");
f2("liuliu");
Func<int,bool> f3 = n => n>0;
Console.WriteLine(f3(1));
}
IEnumerable
IEnumerable(可枚举/迭代的)
IEnumerable:是C#中的一个泛型接口,位于System.Collections.Generic命名空间中。
它定义了一个名为GetEnumerator的方法,该方法返回一个实现了IEnumerator接口的枚举器。
通过实现IEnumerable接口,任何类型的集合都可以使用foreach语句或其他基于枚举的技术进行迭代。
任何实现了 IEnumerable 接口的类型都可以使用 foreach 循环进行遍历。这是因为 foreach 循环依赖于 GetEnumerator() 方法来获取迭代器,并使用该迭代器来访问集合中的每个元素。
使用场景:
IEnumerable常用于表示可枚举的集合,如列表(List)、数组(arr[])、字典(Dictionary<TKey, TValue>)的键或值集合等。
它也用于LINQ查询的结果,使得开发者能够以一种统一的方式处理各种数据源。
IEnumerator(迭代器)
IEnumerator接口与IEnumerable紧密相关,它提供了遍历集合的方法。
主要成员:
- Current:获取枚举器当前位置的元素。(只读属性)
- MoveNext():将枚举器前进到集合的下一个元素。
- Reset():将枚举器设置为其初始位置。通常不建议使用,因为它可能不是所有枚举器都支持。
Linq扩展方法
由于IEnumerable的重要性,C#为其定义了许多扩展方法,这些方法可以在任何实现了该接口的集合上调用。
常见的扩展方法:
- Any(this IEnumerable source): 检查集合是否包含任何元素(是否至少有一条数据),性能高于Count()。
- Count(this IEnumerable source): 返回集合中的元素数量。
- Single(this IEnumerable source): 有且只有一条满足要求的数据。
- SingleOrDefault(this IEnumerable source): 最多只有一条满足要求的数据。
- First(this IEnumerable source): 返回集合中的第一个元素。
- FirstOrDefault(this IEnumerable source): 返回集合中的第一个元素,如果集合为空,则返回默认值。
- Where(this IEnumerable source, Func<T, bool> predicate): 根据指定的条件筛选集合中的元素。
排序补充:
- Order(): 对数据正序排序;
- OrderByDescending(): 倒叙排序;
注意:可以在Order()、OrderByDescending() 后继续写ThenBy()、ThenByDescending()。
案例:优先按照Age排序,如果Age相同再按照Salary排序
list.Order(e=>e.Age).ThenBy(e=>e.Salary)
限制结果集,获取部分数据:
Skip(n)跳过n条数据,Take(n)获取n条数据。
案例:获取从第2条开始,获取3条数据
var num=list.Skip(2).Take(3);
分组:
GroupBy() 方法参数是分组条件表达式,返回值为IGrouping<TKey, TSource>类型的泛型IEnumerable,也就是每一组以一个IGrouping对象形式返回。
IGrouping是一个继承自IEnumerable的接口,IGrouping中的Key属性表示这一组的分组数据的值。
投影:
把集合中的每一项转换为另外一种类型。
IEnumerable ages=list.Select(e => e.Age);
大部分都在System.Linq命名空间中。
匿名类型:
不使用匿名类型:Person p = new Person {Name="liuliu" , Id = 1};
使用匿名类型:var p = new {Name="liuliu" , Id = 1};
var p1 = new {name , Id =1 , p.Age };
匿名类型只能写 var 因为不能确定后面new 的类型。
yield
yield关键字用于遍历循环中,yield return用于返回IEnumerable,yield break用于终止循环遍历。
yield
关键字的用途是把指令推迟到程序实际需要的时候再执行,这个特性允许我们更细致地控制集合每个元素产生的时机。如果我们不使用 yield
关键字,则意味着需要把集合数据装载到内存中等待被使用,这可能导致数据在内存中占用较长的时间。
简单地说,当希望获取一个IEnumerable类型的集合,而不想把数据一次性加载到内存,就可以考虑使用yield return实现"按需供给"。
通过 yield
返回的 IEnumerable<T>
类型,表示这是一个可以被遍历的数据集合。它之所以可以被遍历,是因为它实现了一个标准的 IEnumerable
接口。一般,我们把像上面这种包含 yield
语句并返回 IEnumerable<T>
类型的方法称为迭代器(Iterator)。
//不用yield
static void Main(string[] args)
{
int[] arr= { 1, 2, 3 ,11 ,4 ,15};
IEnumerable<int> arr2 = MyWhere(arr,x => x >10);
foreach (int x in arr2)
{
Console.WriteLine(x);
}
}
static IEnumerable<int> MyWhere(IEnumerable<int> items ,Func<int,bool> f)
{
List<int> list = new List<int>();
foreach (int x in items)
{
if(f(x)==true)
{
list.Add(x);
}
}
return list;
}
//用yield
static void Main(string[] args)
{
int[] arr= { 1, 2, 3 ,11 ,4 ,15};
IEnumerable<int> arr2 = MyWhere(arr,x => x >10);
foreach (int x in arr2)
{
Console.WriteLine(x);
}
}
static IEnumerable<int> MyWhere(IEnumerable<int> items ,Func<int,bool> f)
{
foreach (int x in items)
{
if(f(x)==true)
{
yield return x;
}
}
}
扩展方法
扩展方法(Extension Methods):是一种特殊的静态方法,它允许我们向现有的类或接口添加新的方法,而无需修改原始类的定义或创建子类。通过扩展方法,我们可以在不改变已有代码的情况下,为现有类型添加新的功能。
扩展方法的定义需要满足以下几个条件:
- 扩展方法必须定义在一个静态类中。
- 扩展方法必须是静态的。
- 扩展方法的第一个参数必须使用this关键字来标识,指定要扩展的类型。
// 定义一个静态类
public static class StringExtensions
{
// 定义一个扩展方法,用于将字符串反转
public static string Reverse(this string str)
{
char[] charArray = str.ToCharArray();
Array.Reverse(charArray);
return new string(charArray);
}
}
// 不使用扩展方法
string reversedString=StringExtensions.Reverse("Hello World");
// 使用扩展方法
string originalString = "Hello World";
string reversedString = originalString.Reverse();
Console.WriteLine(reversedString); // 输出:dlroW olleH
需要注意的是,扩展方法只是一种语法糖,编译器会将扩展方法调用转换为对应的静态方法调用。因此,扩展方法并不能真正地修改原始类的定义,只是提供了一种更方便的方式来使用已有类型。
Linq查询方法基本上都是扩展方法,Enumerable类为IEnumerable接口扩展
**注意:**如果扩展方法与类型中原有方法同名,调用原有方法。
LINQ to SQL数据模型
LINQ to SQL 有查询表达式语法(Query Expression)和方法语法(Fluent Synatx)两种可供选择。
1. 查询表达式语法
查询表达式语法是一种接近于SQL语法的查询方式。(个人认为比较适合用于多表查询)
var 结果集 = from c in 数据源 where 过滤表达式 orderby 排序
select c
注意:
- 查询表达式语法于SQL语法相同。
- 查询表达式必须以from子句开头,以 select 或 group by 子句结束。
- 可以使用过滤、连接、分组、排序等运算符继续筛选操作,构造查找结果。
- 可以用隐式 var 类型变量保持查询结果。
2. 方法语法
也就是前面提到的lambda,方法语法又称流利语法,利用System.Linq.Enumerable 类中定义的扩展方法和Lambda表达式进行查询,类似于调用类的扩展方法。
//IEnumerable<T>可以替换为 var
IEnumerable<T> query=数据源集合.Where(bool类型的过滤表达式).OrderBy(排序条件).Select(选择条件)
**注意:**在使用LINQ进行数据处理时需要首先创建数据上下文对象 db,即" DataClasses1DataContext db = new DataClasses1DataContext();"
3.LINQ增删改查
查询
普通查询:
select sname,age from student
//基于Linq查询语句查询
var stu=from s in db.student
select new{s.sname,s.age };
//基于Lambda表达式查询
var stu = db.student.Select(s => new { s.sname, s.age });
foreach (var s in stu)
{
Console.WriteLine(s.sname+","+s.age);
}
条件查询:
select * from student where age>18 and sex='女' order by age desc
//基于Linq查询语句查询
var stu = from s in db.student
where s.age>18 && s.sex =="女"
orderby s.age descending
select s;
//基于Lambda表达式查询
var stu = db.student.Where(c => c.age > 18 && c.sex=="女").OrderByDescending(c=>c.age);
foreach (var s in stu)
{
Console.WriteLine(s.sname+"," +s.sex+ "," + s.age + ","+s.dept);
}
多表查询:
select student.sname,course.cname,sc.grade from sc join student on sc.sno=student.sno join course on sc.cno=course.cno
//基于Linq查询语句查询
var stu = from sc in db.sc
join c in db.course on sc.cno equals c.cno
join s in db.student on sc.sno equals s.sno
select new
{
s.sname,
c.cname,
sc.grade
};
//基于Lambda表达式查询
var stu = db.sc.Select(g => new { g.student.sname, g.course.cname, g.grade });
foreach (var s in stu)
{
Console.WriteLine(s.sname+"," + "," + s.cname + ","+s.grade);
}
插入
使用LINQ to SQL 插入数据时,首先创建一条记录,然后判断该记录对应的主键在数据源中是否存在
如果不存在则调用**InsertOnSubmit()方法将该记录添加到数据源,调用SubmitChanges()**方法保持修改
//插入之前先查找有没有该数据
var data = db.student.FirstOrDefault(s=>s.sno=="202204300")
if (data == null)
{
student xm = new student
{
sno=="202204300",
sname = "小明",
sex = "男",
age = 18,
dept = "宣传部"
};
//执行插入操作
db.student.InsertOnSubmit(xm);
db.SubmitChanges();
Console.WriteLine("插入成功");
}
//如果该数据已存在
else
{
Console.WriteLine("该数据已存在,无法插入");
}
修改
SubmitChanges () 计算要插入、更新或删除的已修改对象的集,并执行相应命令以实现对数据库的更改。
因为数据可能存在外键关系,通常情况下主键不允许修改。
var stu = db.student.SingleOrDefault(s=>s.sname=="小明");
if (stu == null)
{
Console.WriteLine("不存在该学生信息");
return;
}
else
{
stu.sname = "小明";
stu.sex = "女";
stu.age = 21;
stu.dept = "宣传部";
//执行相应命令以实现对数据库的更改
db.SubmitChanges();
Console.WriteLine("信息更新成功");
}
删除
使用LINQ to SQL删除数据时,先检查该记录是否存在,如果存在则调用**DeleteOnSubmit()方法从数据源中移除该记录,然后调用SubmitChanges()**方法对数据源进行保存。
var stu = db.student.SingleOrDefault(s=>s.sname=="小明");
if (stu != null)
{
db.student.DeleteOnSubmit(stu);
db.SubmitChanges();
Console.WriteLine("删除成功!");
}
else
{
Console.WriteLine("该记录不存在,无法删除!");
}
注意:
在LINQ to SQL中,DeleteOnSubmit
和DeleteAllOnSubmit
是两个用于删除数据的方法,它们的主要区别在于操作的对象数量和方式:
-
DeleteOnSubmit
:此方法用于标记单个实体对象为待删除状态。当你调用DeleteOnSubmit
方法时,你只能传递一个对象作为参数,这个方法会将这个对象标记为需要删除。在调用SubmitChanges
之后,只有这个特定的对象会被删除。示例代码:
var entity = context.Table.Single(e => e.Id == someId); context.Table.DeleteOnSubmit(entity); context.SubmitChanges();
-
DeleteAllOnSubmit
:此方法用于标记一个实体对象的集合为待删除状态。当你调用DeleteAllOnSubmit
方法时,你可以传递一个对象的集合作为参数,这个方法会将这个集合中的所有对象标记为需要删除。在调用SubmitChanges
之后,这个集合中的所有对象都会被删除。示例代码:
var entities = from e in context.Table where e.SomeCondition select e; context.Table.DeleteAllOnSubmit(entities); context.SubmitChanges();
总结来说,DeleteOnSubmit
用于单个对象的删除,而DeleteAllOnSubmit
用于多个对象的批量删除。在使用这些方法时,需要注意它们对数据库操作的影响,以及可能需要处理的外键约束问题。
- 感谢你赐予我前进的力量