岁月袅袅 發表於 2020-8-20 08:03:00

在.NET Core中使用MongoDB明细教程(3):Skip, Sort, Limit, Projections

<p><img src="https://img2020.cnblogs.com/blog/1377250/202008/1377250-20200818205326644-42430411.png" alt="" loading="lazy"><br>
到目前为止,我们已经讨论了创建文档, 检索文档,现在让我们来研究一下文档排序,指定要跳过或限制返回的文档数量,以及如何进行投影。此篇文章中的实例代码摘录自原文,未像前几篇文章一样进行实际代码的验证。</p>
<blockquote>
<p>作者:依乐祝</p>
<p>译文地址:https://www.cnblogs.com/yilezhu/p/13525942.html</p>
<p>英文地址:https://www.codementor.io/@pmbanugo/working-with-mongodb-in-net-part-3-skip-sort-limit-and-projections-oqfwncyka</p>
</blockquote>
<h3 id="limit">Limit</h3>
<p>当我们查询一个文档时,我们有时不想返回所有符合过滤条件的文档,而只返回其中的一部分。这就是<code>limit</code> 方法的具体应用。对于MongoDB,可以通过调用<code>Find</code>返回的<code>IFindFluent</code>的<code>limit</code>方法来限制文档的数量。因此,如果我查询数据库中年龄小于40岁的学生,我会得到以下信息:</p>
<pre><code>S/N: 1        Id: 582489339798f091295b9094, FirstName: Gregor, LastName: Felix
S/N: 2        Id: 582489339798f091295b9095, FirstName: Machiko, LastName: Elkberg
S/N: 3        Id: 582489339798f091295b9096, FirstName: Julie, LastName: Sandal
S/N: 4        Id: 583da304f03a84d4d4f4678d, FirstName: Peter, LastName: Cyborg
</code></pre>
<p>为了让它把结果限制在最多两个学生,我调用了<code>Limit()</code>方法,并传递值为2的参数:</p>
<pre><code class="language-csharp">int count = 1;
await collection.Find(x =&gt; x.Age &lt; 40)
    .Limit(2)
    .ForEachAsync(
      student =&gt;
      {
            Console.WriteLine($"S/N: {count} \t Id: {student.Id}, FirstName: {student.FirstName}, LastName: {student.LastName}");
            count++;
      });
</code></pre>
<p>然后得到以下输出,它只返回两个文档:</p>
<pre><code>S/N: 1,        Id: 582489339798f091295b9094, FirstName: Gregor, LastName: Felix
S/N: 2,        Id: 582489339798f091295b9095, FirstName: Machiko, LastName: Elkberg
</code></pre>
<h3 id="skip">Skip</h3>
<p>如果我们想告诉数据库要跳过多少文档,我们使用<code>fluent</code>接口中的<code>skip</code>方法。因此,它类似于我们之前使用的代码,但是告诉数据库返回年龄小于40的所有代码,并跳过第一个。</p>
<pre><code class="language-csharp">int count = 1;
await collection.Find(x =&gt; x.Age &lt; 40)
    .Skip(1)
    .ForEachAsync(
      student =&gt;
      {
            Console.WriteLine($"S/N: {count} \t Id: {student.Id}, FirstName: {student.FirstName}, LastName: {student.LastName}");
            count++;
      });
</code></pre>
<pre><code>S/N: 1,        Id: 582489339798f091295b9095, FirstName: Machiko, LastName: Elkberg
S/N: 2,        Id: 582489339798f091295b9096, FirstName: Julie, LastName: Sandal
S/N: 3,        Id: 583da304f03a84d4d4f4678d, FirstName: Peter, LastName: Cyborg
</code></pre>
<p>你会注意到<code>Gregor Felix </code>被跳过了。使用<code>skip</code>和<code>sort</code>,我们可以将分页添加到应用程序中。</p>
<p>假设我们要检索集合中的每个学生,一个页面上最多显示两个学生。我们可以通过如下过程实现:</p>
<ul>
<li>
<p>跟踪当前页面和要检索的最大文档数。</p>
</li>
<li>
<p>确定总页数。</p>
</li>
<li>
<p>然后检索文档,同时相应地应用<code>skip</code>和<code>limit</code>。</p>
</li>
</ul>
<p>我们可以使用以下代码来完成此操作,并将每个页面的结果打印到控制台:</p>
<pre><code class="language-csharp">var client = new MongoClient();

var db = client.GetDatabase("schoool");

var collection = db.GetCollection&lt;Student&gt;("students");

int currentPage = 1, pageSize = 2;

double totalDocuments = await collection.CountAsync(FilterDefinition&lt;Student&gt;.Empty);
var totalPages = Math.Ceiling(totalDocuments / pageSize);

for (int i = 1; i &lt;= totalPages; i++)
{
    Console.WriteLine($"Page {currentPage}");
    Console.WriteLine();

    int count = 1;
    await collection.Find(FilterDefinition&lt;Student&gt;.Empty)
      .Skip((currentPage - 1) * pageSize)
      .Limit(pageSize)
      .ForEachAsync(
            student =&gt;
            {
                Console.WriteLine($"S/N: {count}, \t Id: {student.Id}, FirstName: {student.FirstName}, LastName: {student.LastName}");
                count++;
            });

    Console.WriteLine();
    currentPage++;
}
</code></pre>
<p>我们在控制台窗口中得到以下结果:</p>
<pre><code class="language-c#">Page 1

S/N: 1,        Id: 58469c732adc9f5370e50c9c, FirstName: Gregor, LastName: Felix
S/N: 2,        Id: 58469c732adc9f5370e50c9d, FirstName: Machiko, LastName: Elkberg

Page 2

S/N: 1,        Id: 58469c732adc9f5370e50c9e, FirstName: Julie, LastName: Sandal
S/N: 2,        Id: 58469c732adc9f5370e50c9f, FirstName: Peter, LastName: Cyborg

Page 3

S/N: 1,        Id: 58469c732adc9f5370e50ca0, FirstName: James, LastName: Cyborg
</code></pre>
<p>这样,我们得到三个页面,因为我们总共有五个记录,每页最多检索两个文档。</p>
<h3 id="sort">Sort</h3>
<p><code>fluent</code>接口的<code>Sort</code>方法采用<code>SortDefinition</code>作为参数,它可以从<code>string</code>或<code>BsonDocument</code>隐式转换,就像<code>FilterDefinition</code>一样。因此,如果我们想使用字符串作为排序定义,按姓氏升序排序,那么它将是:</p>
<pre><code class="language-csharp">await collection.Find(FilterDefinition&lt;Student&gt;.Empty)
    .Skip((currentPage - 1) * pageSize)
    .Limit(pageSize)
    .Sort("{LastName: 1}")
    .ForEachAsync(
      student =&gt;
      {
            Console.WriteLine($"S/N: {count}, \t Id: {student.Id}, FirstName: {student.FirstName}, LastName: {student.LastName}, Age: {student.Age}");
            count++;
      });
</code></pre>
<p>在字符串中,我们有<code>{LastName:1}</code>,其中1告诉它升序排序,而-1告诉它按降序排序。如果我们使用前面更新的代码运行应用程序,它会在第一页返回James和Peter作为结果,如下所示:</p>
<pre><code>Page 1

S/N: 1,        Id: 58469c732adc9f5370e50ca0, FirstName: James, LastName: Cyborg, Age: 39
S/N: 2,        Id: 58469c732adc9f5370e50c9f, FirstName: Peter, LastName: Cyborg, Age: 39

Page 2

S/N: 1,        Id: 58469c732adc9f5370e50c9d, FirstName: Machiko, LastName: Elkberg, Age: 23
S/N: 2,        Id: 58469c732adc9f5370e50c9c, FirstName: Gregor, LastName: Felix, Age: 23

Page 3

S/N: 1,        Id: 58469c732adc9f5370e50c9e, FirstName: Julie, LastName: Sandal, Age: 25
</code></pre>
<p>如果我们希望使用<code>BsonDocument</code>将姓氏按降序排列,则这将是:</p>
<pre><code class="language-csharp">await collection.Find(FilterDefinition&lt;Student&gt;.Empty)
    .Skip((currentPage - 1) * pageSize)
    .Limit(pageSize)
    .Sort(new BsonDocument("LastName", -1))
    .ForEachAsync(
      student =&gt;
      {
            Console.WriteLine($"S/N: {count}, \t Id: {student.Id}, FirstName: {student.FirstName}, LastName: {student.LastName}, Age: {student.Age}");
            count++;
      });
</code></pre>
<p>给出了与之前结果相反的结果:</p>
<pre><code>Page 1

S/N: 1,        Id: 58469c732adc9f5370e50c9e, FirstName: Julie, LastName: Sandal, Age: 25
S/N: 2,        Id: 58469c732adc9f5370e50c9c, FirstName: Gregor, LastName: Felix, Age: 23

Page 2

S/N: 1,        Id: 58469c732adc9f5370e50c9d, FirstName: Machiko, LastName: Elkberg, Age: 23
S/N: 2,        Id: 58469c732adc9f5370e50ca0, FirstName: James, LastName: Cyborg, Age: 39

Page 3

S/N: 1,        Id: 58469c732adc9f5370e50c9f, FirstName: Peter, LastName: Cyborg, Age: 39
</code></pre>
<p>我们也可以使用<code>SortDefinitionBuilder</code>。因此,我们可以使用构建器帮助方法更新代码以创建一个排序定义,如下所示:</p>
<pre><code class="language-csharp">await collection.Find(FilterDefinition&lt;Student&gt;.Empty)
    .Skip((currentPage - 1) * pageSize)
    .Limit(pageSize)
    .Sort(Builders&lt;Student&gt;.Sort.Descending("LastName"))
    .ForEachAsync(
      student =&gt;
      {
            Console.WriteLine($"S/N: {count}, \t Id: {student.Id}, FirstName: {student.FirstName}, LastName: {student.LastName}, Age: {student.Age}");
            count++;
      });
</code></pre>
<p>我们仍然可以得到相同的结果,我们还可以组合不同字段上的升序和降序列表:</p>
<pre><code class="language-csharp">await collection.Find(FilterDefinition&lt;Student&gt;.Empty)
    .Skip((currentPage - 1) * pageSize)
    .Limit(pageSize)
    .Sort(Builders&lt;Student&gt;.Sort.Descending("LastName").Ascending("FirstName"))
    .ForEachAsync(
      student =&gt;
      {
            Console.WriteLine($"S/N: {count}, \t Id: {student.Id}, FirstName: {student.FirstName}, LastName: {student.LastName}, Age: {student.Age}");
            count++;
      });
</code></pre>
<p>或使用强类型对象时,使用表达式树:</p>
<pre><code class="language-csharp">await collection.Find(FilterDefinition&lt;Student&gt;.Empty)
    .Skip((currentPage - 1) * pageSize)
    .Limit(pageSize)
    .Sort(Builders&lt;Student&gt;.Sort.Descending(x =&gt; x.LastName).Ascending(x =&gt; x.FirstName))
    .ForEachAsync(
      student =&gt;
      {
            Console.WriteLine($"S/N: {count}, \t Id: {student.Id}, FirstName: {student.FirstName}, LastName: {student.LastName}, Age: {student.Age}");
            count++;
      });
</code></pre>
<p>我们还可以使用表达式树来指定对<code>SortBy</code>, <code>SortByDescending</code>, <code>ThenBy</code>和<code>ThenByDescending</code>FLUENT接口的方法。按照前面的示例,这将被定义为:</p>
<pre><code class="language-csharp">await collection.Find(FilterDefinition&lt;Student&gt;.Empty)
    .Skip((currentPage - 1) * pageSize)
    .Limit(pageSize)
    .SortByDescending(x =&gt; x.LastName)
    .ThenBy(x =&gt; x.Age)
    .ForEachAsync(
      student =&gt;
      {
            Console.WriteLine($"S/N: {count}, \t Id: {student.Id}, FirstName: {student.FirstName}, LastName: {student.LastName}, Age: {student.Age}");
            count++;
      });
</code></pre>
<p>大多数情况下,我们将使用强类型对象,因为使用表达式树构建查询要容易得多。</p>
<h3 id="projection投影">Projection投影</h3>
<p>我们也可以使用fluent接口的<code>Project</code>方法进行投影。我们指定一个类似于排序和过滤的投影。</p>
<p>使用表达式树或投影定义会导致稍微不同的行为。不同之处之一是,在使用投影定义语法时,必须明确地告诉它排除<code>_id</code>字段,否则,它会将其作为结果集的一部分返回。让我们更新代码,只返回<code>FirstName</code></p>
<pre><code class="language-csharp">await collection.Find(FilterDefinition&lt;Student&gt;.Empty)
    .Skip((currentPage - 1) * pageSize)
    .Limit(pageSize)
    .SortByDescending(x =&gt; x.LastName)
    .ThenBy(x =&gt; x.Age)
    .Project("{FirstName: 1}")
    .ForEachAsync(
      student =&gt;
      {
            Debug.WriteLine($"S/N: {count}, \t Id: {student.Id}, FirstName: {student.FirstName}, LastName: {student.LastName}, Age: {student.Age}");
            count++;
      });
</code></pre>
<p>使用更新的代码,我们的应用程序无法编译。给我们带来了另一个区别:通过投影定义,它隐式地将文档类型从<code>Student</code>转换为<code>bsondocument</code>,因此我们得到的是一个fluent对象,其结果将是一个<code>BsonDocument</code>(即使我们使用的是Student类型)。如果我们想和Student一起工作,我们必须指出我们仍然希望将类型保留为<code>Student</code>。</p>
<pre><code class="language-csharp">.Project&lt;Student&gt;("{FirstName: 1}")
</code></pre>
<p>因此,通过将<code>Student</code>设置为方法的类型来更新我们的代码,将得到以下输出:</p>
<pre><code>Page 1

S/N: 1,        Id: 58469c732adc9f5370e50c9e, FirstName: Julie, LastName: , Age: 0
S/N: 2,        Id: 58469c732adc9f5370e50c9c, FirstName: Gregor, LastName: , Age: 0

Page 2

S/N: 1,        Id: 58469c732adc9f5370e50c9d, FirstName: Machiko, LastName: , Age: 0
S/N: 2,        Id: 58469c732adc9f5370e50ca0, FirstName: James, LastName: , Age: 0

Page 3

S/N: 1,        Id: 58469c732adc9f5370e50c9f, FirstName: Peter, LastName: , Age: 0
</code></pre>
<p>您可以看到,虽然我们只需要<code>FirstName</code>,但是<code>FirstName</code>和<code>Id</code>被返回,而其他的则保持默认值。为了解决这个问题,我们显式地告诉它排除Id字段,并对投影定义进行以下更新:</p>
<pre><code class="language-csharp">.Project&lt;Student&gt;("{FirstName: 1, _id: 0}")
</code></pre>
<p>然后运行它,我们只返回<code>FirstName</code>,而其他值保持默认值:</p>
<pre><code>Page 1

S/N: 1,        Id: 000000000000000000000000, FirstName: Julie, LastName: , Age: 0
S/N: 2,        Id: 000000000000000000000000, FirstName: Gregor, LastName: , Age: 0

Page 2

S/N: 1,        Id: 000000000000000000000000, FirstName: Machiko, LastName: , Age: 0
S/N: 2,        Id: 000000000000000000000000, FirstName: James, LastName: , Age: 0

Page 3

S/N: 1,        Id: 000000000000000000000000, FirstName: Peter, LastName: , Age: 0
</code></pre>
<p>我们也可以使用投影生成器。<code>.Project&lt;Student&gt;(Builders&lt;Student&gt;.Projection.Include(x =&gt; x.FirstName).Exclude(x =&gt; x.Id))</code>这与使用定义生成器进行排序和筛选类似。我们也可以使用表达式树进行投影,然后将其投影到不同的结果。以下代码将只返回first 和last name,并将其映射到匿名类型:</p>
<pre><code class="language-csharp">int count = 1;
await collection.Find(FilterDefinition&lt;Student&gt;.Empty)
    .Project(x =&gt; new {x.FirstName, x.LastName})
    .ForEachAsync(
      student =&gt;
      {
            Console.WriteLine($"{count}. \t FirstName: {student.FirstName} - LastName {student.LastName}");
            count++;
      });

Console.WriteLine();
</code></pre>
<pre><code>1.        FirstName: Gregor - LastName Felix
2.        FirstName: Machiko - LastName Elkberg
3.        FirstName: Julie - LastName Sandal
4.        FirstName: Peter - LastName Cyborg
5.        FirstName: James - LastName Cyborg
</code></pre>
<p>您可能已经注意到,我们并没有显式地指明要排除Id,而是与另一种方式不同,这是因为在强类型表达式树中,它同意只返回您指定的那些字段,而排除其他字段。</p>
<h3 id="总结">总结</h3>
<p>本文带着你一起研究了一下文档的排序,指定要跳过或限制返回的文档数量,以及如何进行投影。此篇文章中的实例代码摘录自原文,未像前几篇文章一样进行实际代码的验证。希望对你有所帮助。</p>


</div>
<div id="MySignature" role="contentinfo">
    <div style="border: #e0e0e0 1px dashed; padding: 15px 20px; padding-left: 20px; background: #e5f1f4; position: relative">

<p style="margin-bottom: 5px">作者:依乐祝(祝雷)</p>
<p style="margin-bottom: 5px">
    出处:https://www.cnblogs.com/yilezhu
</p>
<p style="margin-bottom: 5px">
    联系:1015657114@qq.com   微信:jkingzhu
</p>
<div>
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如有问题或建议,请多多赐教,非常感谢。
</div>
<div>
   <img src="https://images.cnblogs.com/cnblogs_com/yilezhu/1359617/o_%E6%89%AB%E7%A0%81_%E6%90%9C%E7%B4%A2%E8%81%94%E5%90%88%E4%BC%A0%E6%92%AD%E6%A0%B7%E5%BC%8F-%E5%BE%AE%E4%BF%A1%E6%A0%87%E5%87%86%E7%BB%BF%E7%89%88.jpg" alt="DotNetCore实战">
</div>
</div><br><br>
来源:https://www.cnblogs.com/yilezhu/p/13525942.html
頁: [1]
查看完整版本: 在.NET Core中使用MongoDB明细教程(3):Skip, Sort, Limit, Projections