小小小橘子 發表於 2025-6-25 09:09:00

C#.Net筑基-优雅LINQ的查询艺术

<p><img src="https://img2024.cnblogs.com/blog/151257/202506/151257-20250622165856393-1246465763.png" alt="" loading="lazy"></p>
<p>Linq(Language Integrated Query,集成查询语言),顾名思义就是用来查询数据的一种语言(可以看作是一组功能、框架特性的集合)。在<code>.NETFramework3.5</code>(大概2007年)引入C#,用统一的C#语言快速查询各种数据,如数据库、XML文档、对象集合等等。Linq的诞生对 C# 编程领域产生了深远而持久的影响,改变了开发人员对查询的思考方式。</p>
<ul>
<li><strong>使用简单</strong>:统一语法(链式方法语法、类似SQL的查询语法),智能提示。</li>
<li><strong>类型安全</strong>:编译时强类型检查,减少运行时错误。</li>
<li><strong>延迟执行</strong>,查询本身只是构建了一个表达式,在真正使用的时候(foreach、ToList、查询数据库)才会执行。</li>
<li><strong>支持多种数据源</strong>:内存中的集合,以及各种外部数据库。</li>
</ul>
<p><img src="https://img2024.cnblogs.com/blog/151257/202506/151257-20250622165856335-2056776412.jpg" alt="" loading="lazy"></p>
<p>Linq支持查询任何实现了<code>IEnumerable&lt;T&gt;</code>接口的集合类型,基本上所有集合数据都支持Linq查询。如下示例:大于&nbsp;5&nbsp;的偶数,并倒叙排列取前三名</p>
<pre><code class="language-csharp">//方法链语法
var query = arr.Where(n =&gt; n &gt; 5 &amp;&amp; n % 2 == 0).OrderByDescending(n =&gt; n).Take(3);
</code></pre>
<hr>
<h1 id="01linq-基础概括">01、Linq 基础概括</h1>
<h2 id="11linq语法链式方法查询表达式">1.1、Linq语法:链式方法、查询表达式</h2>
<p>Linq 有两种语法风格,如下实例代码,一种是常规C#方法调用方式,另外一种是类似SQL的查询表达式。这两种语法其本质是一样的,编译后的中间语言(IL)是一样的,<font style="color: rgba(210, 45, 141, 1)">确实仅仅只是语法形式不同而已</font>。</p>
<p><strong>🔸链式方法</strong>:就是字面意思,函数式方法调用。这些方法都来自IEnumerable 接口或 IQueryable 接口的扩展方法,这些方法提供了过滤、聚合、排序等多种查询功能。</p>
<p><strong>🔸查询表达式</strong>:查询表达式由一组用类似于 SQL的声明性语法所编写的子句组成。 每个子句依次包含一个或多个 C# 表达式,而这些表达式可能本身就是查询表达式,或者包含查询表达式。查询表达式必须以 <code>from</code> 子句开头,且必须以 <code>select</code> 或 <code>group</code> 子句结尾。</p>
<pre><code class="language-csharp">//方法链语法
var query = arr.Where(n =&gt; n &gt; 5 &amp;&amp; n % 2 == 0).OrderByDescending(n =&gt; n).Take(3);
//查询表达式语法,类似数据库SQL语言+C#的语法风格
var query2 = (from n in arr
                               where n &gt; 5 &amp;&amp; n % 2 == 0
                  orderby n descending
                  select n).Take(3);
</code></pre>
<table>
<thead>
<tr>
<th><strong>比较</strong></th>
<th><strong>链式方法</strong></th>
<th><strong>查询表达式(SQL)</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>特点</td>
<td>链式方法调用,函数式编程</td>
<td>类似SQL语句,自然语言,容易掌握</td>
</tr>
<tr>
<td>语法形式</td>
<td>点点点链式方法调用,Where().Select().Order()</td>
<td>以<code>from</code>开头:<code>from...where...select</code></td>
</tr>
<tr>
<td>常用方法/语法</td>
<td><font style="color: rgba(77, 77, 77, 1)">System.Linq</font> 上提供的扩展方法或第三方扩展:Where、OrderBy、Select、Skip、Take、Union</td>
<td>仅支持编译器识别的关键字:from、where、orderby、group、join、let、select、into、in、on等</td>
</tr>
<tr>
<td>本质</td>
<td><font style="color: rgba(77, 77, 77, 1)">System.Linq </font>提供的扩展方法调用</td>
<td>编译为标准查询运算符方法调用,<font style="color: rgba(223, 42, 63, 1)"><strong>编译结果和链式方法一样</strong></font></td>
</tr>
<tr>
<td>功能完整性</td>
<td>完整的Linq功能</td>
<td>有些能力没有对应语法(如Max),需要结合链式方法使用</td>
</tr>
</tbody>
</table>
<p><img src="https://img2024.cnblogs.com/blog/151257/202506/151257-20250622165856327-1700455281.webp" alt="" loading="lazy"></p>
<blockquote>
<p>📢 两种编写方式编译后生成的IL代码实际上是一样的,也可以混合使用,因此他们并没有性能差异。</p>
</blockquote>
<p>查询表达式并不能实现获取前3个元素,此时就需要两者混合使用,</p>
<pre><code class="language-csharp">var query = from u in list
    where u.Age&gt;14
    group u by u.Address into gu
    orderby gu.Count() descending
    select (gu.Key,gu.Count());
query = query.Take(3);
</code></pre>
<h2 id="12linq执行本地查询解释型查询">1.2、Linq执行:本地查询、解释型查询</h2>
<p>LINQ 提供了两种用途的架构:针对本地(内存)对象的本地查询,以及针对远程数据源(数据库)的解释性查询。两者的语法形式基本一样,都支持链式方法、查询表达式。</p>
<p><font style="color: rgba(96, 27, 222, 1)"><strong>🔸本地查询</strong></font>:实现了针对<code>IEnumerable</code>的内存集合(数组、List)的查询,其Linq的扩展方法都在 System.Linq.Enumerable 类中。查询只是构建了一个可枚举的迭代装饰器序列,延迟在使用(消费)数据时执行。</p>
<p><strong>🔸解释查询</strong>:解释查询是描述性的,实现了针对<code>IQueryable</code>(Table、DbSet)的远程数据查询,对应扩展方法都在 System.Linq.Queryable 类中。他们在运行时生成表达式树,并进行解释为SQL语句,在数据库中执行该SQL语句并获取数据。</p>
<table>
<thead>
<tr>
<th><strong>比较</strong></th>
<th><strong>本地查询 Enumerable</strong></th>
<th><strong>解释查询 Queryable</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>操作对象</td>
<td>内存中的集合(<code>IEnumerable&lt;T&gt;</code>)</td>
<td>外部数据源的查询接口(<code>IQueryable&lt;T&gt;</code>)</td>
</tr>
<tr>
<td>延迟执行</td>
<td>支持,真正使用(消费)数据时才执行,如 foreach、ToList</td>
<td>支持,消费数据时才翻译成SQL并在数据库中执行获取数据</td>
</tr>
<tr>
<td>执行原理</td>
<td>参数为委托方法,C#内部执行委托、迭代器</td>
<td>参数为表达式树,LINQ Provider 在运行时遍历该树转换为目标语言(如 SQL)</td>
</tr>
<tr>
<td>谁来执行</td>
<td>CLR本地执行,数据在内存中</td>
<td>数据库执行SQL,数据在数据库中</td>
</tr>
<tr>
<td>执行过程</td>
<td>本地逐个元素迭代调用委托</td>
<td>数据库中执行SQL,返回查询结果</td>
</tr>
<tr>
<td>使用场景</td>
<td>List、Array、普通内存数据</td>
<td>Entity Framework、LINQ to SQL、MongoDB 查询</td>
</tr>
<tr>
<td>语法</td>
<td>都支持链式方法、表达式查询</td>
<td>同样支持链式方法、表达式查询</td>
</tr>
<tr>
<td>Linq方法在哪里?</td>
<td>System.Linq.Enumerable 静态类</td>
<td>System.Linq.Queryable 类,方法和 Enumerable 大部分对应。有些方法并不能生成数据库兼容的SQL语法。</td>
</tr>
<tr>
<td>扩展性</td>
<td>内存查询支持任意C#方法,扩展性强</td>
<td>受限,只能使用数据库兼容的方法。如正则表达式SQLServer就不支持。</td>
</tr>
<tr>
<td>结合使用</td>
<td>本地数据只能用本地查询</td>
<td>远程数据可以结合本地查询混用。</td>
</tr>
</tbody>
</table>
<p>IQueryable 继承自 IEnumerable,因此解释查询可以转换为本地查询,<code>query.AsEnumerable()</code>,不过需谨慎使用,会将数据库的相应数据都加载到内存中。</p>
<pre><code class="language-csharp">public interface IQueryable&lt;out T&gt; : IEnumerable&lt;T&gt;, IEnumerable, IQueryable
{
}
</code></pre>
<blockquote>
<p>📢表达式树是一个微型的代码DOM结构,树中的节点是Expression类型的节点,涵盖各种语法形式,如参数、变量、常量、赋值、比较、循环等等。表达式树可以转换(Compile)为委托,反之则不能。</p>
</blockquote>
<p><img src="https://img2024.cnblogs.com/blog/151257/202506/151257-20250622165856415-751901152.png" alt="" loading="lazy"></p>
<h2 id="13延迟执行的注意事项">1.3、延迟执行的注意事项</h2>
<p><font style="color: rgba(96, 27, 222, 1)"><strong>延迟执行</strong></font>是指查询代码不会立刻执行,而是在正真取数的时候才会执行。他是Linq最主要的特点,是优点,也不全是,有些需要注意的地方。</p>
<ul>
<li>并不是所有的Linq方法都是延迟的,如:First()、Last()、ToArray()、ToList(),及Count、Max等聚合计算方法会立即执行。</li>
<li>如果数据源变了,结果也会变化。</li>
</ul>
<pre><code class="language-csharp">List&lt;int&gt; list = ;
var query = list.Where(s=&gt;s&gt;5);
Console.WriteLine(query.Sum()); //9
list.Add(6);
Console.WriteLine(query.Sum()); //15
</code></pre>
<ul>
<li>重复取数时,查询也会重复执行,可能会浪费性能,特别是复杂、耗时的查询。避免的方式就是<code>query.ToList()</code> 一次性立即获取数据。</li>
<li>Lambda变量捕获,变量的值在真正执行查询的时候才会获取,这是方法闭包的特点。</li>
</ul>
<pre><code class="language-csharp">List&lt;int&gt; list = ;
int n = 5;
var query = list.Where(s=&gt;s&gt;n);
n = 4;
Console.WriteLine(query.Sum()); //14 //使用的n=4
</code></pre>
<h2 id="14迭代装饰器序列">1.4、迭代装饰器序列</h2>
<p>为了支持延迟执行,Linq内部封装了很多<strong>迭代装饰器</strong>,偷偷看了下源码,如 WhereIterator、SelectEnumerableIterator、ReverseIterator、UnionIterator 等,都是Linq内部的迭代装饰器。迭代装饰器会保留输入序列的引用及其他相关参数,仅当枚举结果时才会执行。</p>
<p>迭代序列装饰器本身继承自IEnumerable,因此就支持装饰器之间的嵌套。下面为迭代装饰器序列基类的源码 Iterator.cs。</p>
<pre><code class="language-csharp">internal abstract class Iterator&lt;TSource&gt; : IEnumerable&lt;TSource&gt;, IEnumerator&lt;TSource&gt;
{
    private readonly int _threadId;
    internal int _state;
    internal TSource _current = default!;
}
</code></pre>
<p><img src="https://img2024.cnblogs.com/blog/151257/202506/151257-20250622165856384-991647256.png" alt="" loading="lazy"></p>
<hr>
<h1 id="2enumerable扩展方法汇总">2、Enumerable扩展方法汇总⭐</h1>
<p>内存集合的Linq扩展方法,基本都来自Enumerable类,参考官方 Enumerable 类。用于数据库的解释性查询方法在 System.Linq.Queryable 类中,方法和 Enumerable 基本上都是对应的。基本上所有的Linq方法都在这里汇总:</p>
<table>
<thead>
<tr>
<th><strong>方法</strong></th>
<th><strong>说明</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>Chunk(Int32)</td>
<td><strong>分块</strong>拆分为多个固定大小的数组,返回<code>IEnumerable&lt;TSource[]&gt;</code>,内部每次迭代会构建一个数组<code>new TSource</code></td>
</tr>
<tr>
<td>Append(T)</td>
<td>末尾追加一个元素,原理是内部构建了一个新的迭代器<code>AppendPrepend1Iterator</code>实现返回这个元素。</td>
</tr>
<tr>
<td>Prepend(T)</td>
<td>在前面追加一个元素,原理同上,是同一个<code>AppendPrepend1Iterator</code></td>
</tr>
<tr>
<td><strong>🔸聚合计算,立即执行</strong></td>
<td></td>
</tr>
<tr>
<td>Count()</td>
<td>获取集合中元素的数量,可指定条件参数Func。<code>arr.Count()</code>,内部原理比较简单,如果集合是<code>ICollection</code>等,则直接获取<code>Count</code>,否则只能<code>e.MoveNext()</code>一个一个的数了。</td>
</tr>
<tr>
<td>TryGetNonEnumeratedCount</td>
<td>获取元素数量,在不真正遍历(不枚举)集合的情况下,尽量尝试快速拿到集合元素的数量</td>
</tr>
<tr>
<td>Max()</td>
<td>返回最大的那个元素。截止<code>.NET8</code>,整数类型用了Vector提升性,其他循环比较,性能一般😒。</td>
</tr>
<tr>
<td>Min()</td>
<td>返回最小的那个元素,性能原理同Max</td>
</tr>
<tr>
<td>Average()</td>
<td>计算平均值,对于数值类型,内部用到了<font style="color: rgba(96, 27, 222, 1)">Vector</font><font style="color: rgba(96, 27, 222, 1)">⭐</font>,性能还是不错的。<code>var a = arr.Average()</code></td>
</tr>
<tr>
<td>Sum()</td>
<td>求和,<code>arr1.Sum()</code></td>
</tr>
<tr>
<td>Aggregate(Func)</td>
<td>执行累加器函数,函数的的输出为作为下一轮迭代的输入,依次迭代执行。<br>示例,计算序列最大值:<code>var max = arr.Aggregate((acc,n)=&gt;acc&gt;n?acc:n)</code></td>
</tr>
<tr>
<td><strong>🔸条件判断</strong></td>
<td></td>
</tr>
<tr>
<td>Contains(T)</td>
<td>判断是否包含指定元素,返回bool,可指定比较器。<code>bool f = arr.Contains(6)</code></td>
</tr>
<tr>
<td>Any()</td>
<td>集合是否包含元素,判断集合是否不为空。<code>if(arr.Any()){}</code></td>
</tr>
<tr>
<td>Any(Func)</td>
<td>集合是否包含指定条件的元素,示例:是否有人考试满分,<code>bool flag = arr.All(n=&gt;n==100)</code></td>
</tr>
<tr>
<td>All(Func)</td>
<td>所有元素是否满足条件,示例:是否所有同学都及格了,<code>bool flag = arr.All(n=&gt;n&gt;=60)</code></td>
</tr>
<tr>
<td>SequenceEqual(IEnumerable)</td>
<td><strong>序列相等</strong>比较,比较两个序列是否相同,长度相同、每个元素相等则返回True</td>
</tr>
<tr>
<td><strong>🔸元素选择</strong></td>
<td></td>
</tr>
<tr>
<td>First()</td>
<td>返回第一个元素,如果一个都没有抛出异常,<code>arr1.First()</code></td>
</tr>
<tr>
<td>FirstOrDefault()</td>
<td>返回第一元素,如果一个都没有则返回默认值,arr1.FirstOrDefault()</td>
</tr>
<tr>
<td>Last()</td>
<td>返回最后一个元素,如果一个都没有抛出异常。如果不是常规集合,会<code>foreach</code>循环所有😒。</td>
</tr>
<tr>
<td>LastOrDefault()</td>
<td>同上,如果一个都木有则返回默认值</td>
</tr>
<tr>
<td>Single()、SingleOrDefault()</td>
<td>获取<strong>唯一</strong>元素,如果元素数量大于1则抛出异常。这个方法在数据库按主键查询时比较有用。</td>
</tr>
<tr>
<td>ElementAt(Index)</td>
<td>返回指定索引<code>Index</code>位置的元素,<code>arr.ElementAt(0)</code>。还有个更安全的 ElementAtOrDefault</td>
</tr>
<tr>
<td>DefaultIfEmpty(defaultT)</td>
<td>如果集合为空(集合中没有元素)返回含一个默认值的IEnumerable,否则返回原序列。</td>
</tr>
<tr>
<td><strong>🔸筛选查询</strong></td>
<td></td>
</tr>
<tr>
<td>Where(Func)</td>
<td>条件查询,最常用的Linq函数了,<code>arr1.Where(s=&gt;s&gt;5)</code></td>
</tr>
<tr>
<td>Select(selector)</td>
<td>返回指定Key(元素选择处理器结果)的集合,<code>list.Select(s=&gt;s.Name+s.Age)</code></td>
</tr>
<tr>
<td>SelectMany()</td>
<td>将每个元素的“内部集合”展开合并为一个大集合,<code>list.SelectMany(s=&gt;s.Name.Split('-'))</code></td>
</tr>
<tr>
<td>Distinct()</td>
<td>去重,<code>arr.Distinct()</code>,内部使用<code>HashSet&lt;TSource&gt;</code>来去重。DistinctBy&gt;可指定键Key。</td>
</tr>
<tr>
<td>OfType()</td>
<td>根据类型T筛选集合,源码中用<code>obj is TResult</code>来筛选,不符合的丢弃。<code>list.OfType&lt;double&gt;()</code></td>
</tr>
<tr>
<td>Skip(int count)</td>
<td><strong>跳过</strong>指定数量的元素,返回剩余的元素,<code>arr1.Skip(5)</code></td>
</tr>
<tr>
<td>SkipLast(int count)</td>
<td>忽略<strong>后面</strong>的元素,返回前面剩余的元素。<code>arr1.SkipLast(3)</code></td>
</tr>
<tr>
<td>SkipWhile(Func)</td>
<td>从开头<strong>跳过</strong>符合条件的元素,<strong>直到遇到不符合</strong>条件时停下,返回剩下的元素。</td>
</tr>
<tr>
<td>Take(int count)</td>
<td>返回前n个元素,Skip的逆运算,<code>Take(3)</code></td>
</tr>
<tr>
<td>TakeLast(int count)</td>
<td>返回最后n个元素,<code>arr1.TakeLast(3)</code></td>
</tr>
<tr>
<td>TakeWhile(Func)</td>
<td>从开头返回符合条件的元素,<strong>直到遇到不符合</strong>条件时停下,与SkipWhile相反<code>arr1.TakeWhile(s=&gt;s&lt;5)</code></td>
</tr>
<tr>
<td><strong>🔸排序分组</strong></td>
<td></td>
</tr>
<tr>
<td>Order()</td>
<td><strong>升序</strong>排列集合,<code>arr2.Order()</code></td>
</tr>
<tr>
<td>OrderBy(TKey)</td>
<td>指定Key键<strong>升序</strong>排列集合,<code>list.OrderBy(s=&gt;s.Age)</code></td>
</tr>
<tr>
<td>OrderByDescending(TKey)</td>
<td>指定Key键<strong>降序</strong>排列集合,<code>list.OrderByDescending(s=&gt;s.Age)</code></td>
</tr>
<tr>
<td>ThenBy、ThenByDescending</td>
<td>二次排序,跟着OrderBy使用,设置第二排序键。<code>list.OrderBy(s=&gt;s.Grade).ThenBy(s=&gt;s.Age)</code></td>
</tr>
<tr>
<td>Reverse()</td>
<td><strong>反转</strong>序列中元素的顺序,<code>arr2.Reverse()</code><font style="color: rgba(0, 0, 0, 1)">。内部源码是创建了一个数组来实现翻转,性能不佳</font>😒<font style="color: rgba(0, 0, 0, 1)">,数组推荐使用</font><code>Array.Reverse()</code><font style="color: rgba(0, 0, 0, 1)">,原地翻转,不会创建额外对象。</font></td>
</tr>
<tr>
<td>GroupBy</td>
<td>按指定的Key分组,返回一个分组集合<code>IGrouping&lt;TKey, TSource&gt;</code>,<code>list.GroupBy(s=&gt;s.Name)</code></td>
</tr>
<tr>
<td>GroupJoin</td>
<td>带分组的连接(Join)操作,类似Sql中的Left Join + 分组,每个「左边元素」对应到「右边的一组元素」</td>
</tr>
<tr>
<td><strong>🔸多集合操作</strong></td>
<td></td>
</tr>
<tr>
<td>Union(IEnumerable)</td>
<td><strong>并集</strong>,合并两个集合并<font style="color: rgba(210, 45, 141, 1)">去重</font>,<code>arr1.Union(arr2)</code></td>
</tr>
<tr>
<td>Intersect(IEnumerable)</td>
<td><strong>交集</strong>(Intersect /ˌɪntəˈsekt/ 相交),返回两个集合都包含的元素。IntersectBy <font style="color: rgba(0, 0, 0, 1)">可指定键Key。</font></td>
</tr>
<tr>
<td>Except(IEnumerable)</td>
<td><strong>移除</strong>(Except /ɪkˈsept/ 除外)<code>arr1.Except(arr2)</code><font style="color: rgba(0, 0, 0, 1)">移除</font><code>arr2</code><font style="color: rgba(0, 0, 0, 1)">中也存在的元素。</font>ExceptBy<font style="color: rgba(0, 0, 0, 1)">可指定键Key。</font></td>
</tr>
<tr>
<td>Concat(IEnumerable)</td>
<td>“合并”两个序列集合(<font style="color: rgba(210, 45, 141, 1)"></font>),内部由私有的<code>ConcatIterator</code>实现的连接迭代,<code>arr.Concat()</code></td>
</tr>
<tr>
<td>Join(arr2, k2,k1,Func)</td>
<td>两个“表”内连接,类似Sql中的 Inner Join,用于两个不同类型元素的的连接,两个表Key匹配的元素合并</td>
</tr>
<tr>
<td>Zip</td>
<td>就像拉链(zipper)一样,把两个序列一对一地配对合并成一个新序列,<code>arr1.Zip(arr2,(n1,n2)=&gt;n1+n2)</code></td>
</tr>
<tr>
<td><strong>🔸转换,ToXX立即执行</strong></td>
<td><font style="color: rgba(223, 42, 63, 1)">❗</font><font style="color: rgba(223, 42, 63, 1)">谨慎使用,会创建新的集合对象</font></td>
</tr>
<tr>
<td>Cast<tresult>()</tresult></td>
<td>强制类型转换,内部使用强制转换“<code>(TResult)obj</code>”</td>
</tr>
<tr>
<td>ToArray()</td>
<td>从 IEnumerable 创建新<strong>数组</strong>,慎用。<code>var narr = arr1.Order().ToArray()</code></td>
</tr>
<tr>
<td>ToList()</td>
<td>从 IEnumerable 创建新<strong>List</strong>,<code>arr1.Take(3).ToList()</code></td>
</tr>
<tr>
<td>ToHashSet</td>
<td>从 IEnumerable 创建新<strong>HashSet</strong>(不可重复集合,<font style="color: rgba(96, 27, 222, 1)">自动去重</font>),<code>arr1.ToHashSet()</code></td>
</tr>
<tr>
<td>ToDictionary()</td>
<td>从 IEnumerable 创建新<strong>字典</strong><code>Dictionary&lt;TK,TV&gt;</code>,<code>list.ToDictionary(s=&gt;s.Name,s=&gt;s.Age)</code></td>
</tr>
<tr>
<td>ToLookup()</td>
<td>从 IEnumerable 创建新 Lookup(<strong>分组的字典</strong>),<code>arr1.ToLookup(s=&gt;s%2)</code></td>
</tr>
<tr>
<td><strong>🔸其他</strong></td>
<td></td>
</tr>
<tr>
<td>Range(start, end)</td>
<td>静态方法,创建一个连续的序列,可用来创建测试数据,<code>Enumerable.Range(1,10).ToArray()</code></td>
</tr>
<tr>
<td>Repeat(T, count)</td>
<td>静态方法,创建一个重复值的序列,<code>Enumerable.Repeat(18,10)</code></td>
</tr>
<tr>
<td>Empty()</td>
<td>静态方法,获得一个空的序列,<code>Enumerable.Empty&lt;int&gt;().Any(); //false</code></td>
</tr>
<tr>
<td>AsEnumerable()</td>
<td>返回自己,什么也不干。在Linq to SQL中可以强制让后续操作在本地内存中进行,而不会翻译成SQL。</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<h2 id="21示例构建动态查询">2.1、示例:构建动态查询</h2>
<p>根据用户输入条件,构建动态查询条件,<font style="color: rgba(44, 44, 54, 1)">使用 </font><code>Skip</code><font style="color: rgba(44, 44, 54, 1)"> 和 </font><code>Take</code><font style="color: rgba(44, 44, 54, 1)"> 实现分页</font>。</p>
<pre><code class="language-csharp">var query = list.AsEnumerable();
if (!string.IsNullOrWhiteSpace(name))
    query = query.Where(s =&gt; s.Name.Contains(name, StringComparison.OrdinalIgnoreCase));
if (age.HasValue)
    query = query.Where(s =&gt; s.Age == age);
if (!string.IsNullOrWhiteSpace(address))
    query = query.Where(s =&gt; s.Address.Contains(address));
//使用 Skip 和 Take 实现分页
query = query
    .Skip((pageNumber - 1) * pageSize)
    .Take(pageSize);
//执行查询,获取结果
var result = query.ToArray();
</code></pre>
<h2 id="22自定义扩展">2.2、自定义扩展</h2>
<p>本地查询扩展是很容易的,基于<code>IEnumerable&lt;T&gt;</code>实现扩展方法即可。<code>IQueryable</code>扩展则要考虑数据库的支持和映射,一般无需自定义扩展。</p>
<pre><code class="language-csharp">//交替获取元素集合
public static IEnumerable&lt;T&gt; AlternateElements&lt;T&gt;(this IEnumerable&lt;T&gt; source)
{
    int index = 0;
    foreach (T element in source)
    {
      if (index % 2 == 0)
      {
            yield return element;
      }

      index++;
    }
}

//使用
var query = list.AlternateElements();
</code></pre>
<hr>
<h1 id="参考资料">参考资料</h1>
<ul>
<li>MSDN:Enumerable 类</li>
<li>LINQ概述</li>
<li>《C#8.0 In a Nutshell》</li>
</ul>
<hr>
<blockquote>
<p><strong>©️版权申明</strong>:版权所有@安木夕,本文内容仅供学习,欢迎指正、交流,转载请注明出处!<em>原文编辑地址-语雀</em></p>
</blockquote><br><br>
来源:https://www.cnblogs.com/anding/p/18942909
頁: [1]
查看完整版本: C#.Net筑基-优雅LINQ的查询艺术