MongoDB学习笔记

53 阅读16分钟

MongoDB概念解析

  1. database 数据库
    • 一个mongodb中可以建立多个数据库。MongoDB的默认数据库为"db",该数据库存储在data目录中。MongoDB的单个实例可以容纳多个独立的数据库,每一个都有自己的集合和权限,不同的数据库也放置在不同的文件中。
    • show dbs 命令可以显示所有数据的列表。
    • db 命令可以显示当前数据库对象或集合
    • 运行use命令,可以连接到一个指定的数据库
  2. table 数据库表/集合
    • mongodb跟关系型数据库不一样,关系型数据库是表格,但是mongodb是文档组,也就是集合。集合存在于数据库中,集合没有固定的结构,这意味着你在对集合可以插入不同格式和类型的数据,但通常情况下我们插入集合的数据都会有一定的关联性。
    • 集合命名规范:
      • 集合名不能是空字符串""
      • 集合名不能含有\0字符(空字符),这个字符表示集合名的结尾
      • 集合名不能以"system."开头,这是为系统集合保留的前缀
      • 用户创建的集合名字不能含有保留字符。有些驱动程序的确支持在集合名里面包含,这是因为某些系统生成的集合中包含该字符。除非你要访问这种系统创建的集合,否则千万不要在名字里出现$
    • capped collections:也就是固定大小的collections
      • 是高性能自动的维护对象的插入顺序。
      • 创建方式:db.createCollection("mycoll", {capped:true, size:100000})
  3. row 数据记录行/文档
    • mongodb的记录行是文档,也就是一组键值(key-value)对(即 BSON)。MongoDB 的文档不需要设置相同的字段,并且相同的字段不需要相同的数据类型,这与关系型数据库有很大的区别,也是 MongoDB 非常突出的特点。
    • 文档的编写需要注意的地方:
      • MongoDB区分类型和大小写
      • MongoDB的文档不能有重复的键
      • 文档中的键/值对是有序的
      • 文档的键是字符串。除了少数例外情况,键可以使用任意UTF-8字符
      • 键不能含有\0 (空字符)。这个字符用来表示键的结尾
      • .和$有特别的意义,只有在特定环境下才能使用
      • 以下划线"_"开头的键是保留的(不是严格要求的)
  4. 数据类型
    • String:字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的。
    • Integer:整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位。
    • Boolean:布尔值。用于存储布尔值(真/假)。
    • Double:双精度浮点值。用于存储浮点值。
    • Min/Max keys:将一个值与 BSON(二进制的 JSON)元素的最低值和最高值相对比。
    • Array:用于将数组或列表或多个值存储为一个键
    • Timestamp:时间戳。记录文档修改或添加的具体时间。
    • Object:用于内嵌文档。
    • Null:用于创建空值。
    • Symbol:符号。该数据类型基本上等同于字符串类型,但不同的是,它一般用于采用特殊符号类型的语言。
    • Date:日期时间。用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间:创建 Date 对象,传入年月日信息。
    • Object ID:对象 ID。用于创建文档的 ID。
    • Binary Data:二进制数据。用于存储二进制数据。
    • Code:代码类型。用于在文档中存储 JavaScript 代码。
    • Regular expression:正则表达式类型。用于存储正则表达式。
  5. ObjectId
    • ObjectId 类似唯一主键,可以很快的去生成和排序,包含 12 bytes,含义是:
      • 前 4 个字节表示创建 unix 时间戳,格林尼治时间 UTC 时间,比北京时间晚了 8 个小时
      • 接下来的 3 个字节是机器标识码
      • 紧接的两个字节由进程 id 组成 PID
      • 最后三个字节是随机数
    • MongoDB 中存储的文档必须有一个 _id 键。这个键的值可以是任何类型的,默认是个 ObjectId 对象,由于 ObjectId 中保存了创建的时间戳,所以你不需要为你的文档保存时间戳字段,你可以通过 getTimestamp 函数来获取文档的创建时间。

MongoDB数据库

  1. 创建数据库:use DATABASE_NAME
  2. 删除数据库:db.dropDatabase() (要删除数据库就需要切换到指定的数据库下才能删除)

MongoDB集合

  1. 创建集合:db.createCollection(name, options)
    • name: 要创建的集合名称
    • options: 可选参数, 指定有关内存大小及索引的选项
      • capped:布尔(可选):如果为 true,则创建固定集合。固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。当该值为 true 时,必须指定 size 参数。
      • autoIndexId:布尔:3.2 之后不再支持该参数。(可选)如为 true,自动在 _id 字段创建索引。默认为 false。
      • size:数值(可选):为固定集合指定一个最大值,即字节数。如果 capped 为 true,也需要指定该字段。
      • max:数值 (可选):指定固定集合中包含文档的最大数量。
  2. 删除集合:db.collection.drop()
  3. 查看集合:show collections 或者 show tables

MongoDB文档

  1. 插入文档:db.COLLECTION_NAME.insert(document) 或者db.COLLECTION_NAME.save(document)
    • save():如果 _id 主键存在则更新数据,如果不存在就插入数据。该方法新版本中已废弃,可以使用 db.collection.insertOne() 或 db.collection.replaceOne() 来代替。
    • insert(): 若插入的数据主键已经存在,则会抛 org.springframework.dao.DuplicateKeyException 异常,提示主键重复,不保存当前数据。
  2. 插入文档新的方式:
    • db.collection.insertOne() :用于向集合插入一个新文档
    • db.collection.insertMany():用于向集合插入一个多个文档 在这里插入图片描述
  3. 更新文档:update() 方法
    • query : update的查询条件,类似sql update查询内where后面的。
    • update : update的对象和一些更新的操作符(如,,inc...)等,也可以理解为sql update查询内set后面的
    • upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
    • multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
    • writeConcern :可选,抛出异常的级别。
db.collection.update(
   <query>,
   <update>,
   {
     upsert: <boolean>,
     multi: <boolean>,
     writeConcern: <document>
   }
)

在这里插入图片描述

  1. 删除文档:db.collection.remove( <query>, <justOne>, writeConcern: <document> )

    • query :(可选)删除的文档的条件。
    • justOne : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值 false,则删除所有匹配条件的文档。
    • writeConcern :(可选)抛出异常的级别。
    • 提示:如果要选择_id删除的话,另一边需要选择ObjectId()方法进行删除 在这里插入图片描述
  2. 查询文档:db.collection.find(query, projection)

    • query :可选,使用查询操作符指定查询条件

    • projection :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。

    • 如果你需要以易读的方式来读取数据,可以使用 pretty() 方法 在这里插入图片描述

    • 条件操作符:关于大小写的书写比较 在这里插入图片描述

    • MongoDB 的 find() 方法可以传入多个键(key),每个键(key)以逗号隔开,即常规 SQL 的 AND 条件。

    • OR 条件语句使用了关键字 $or 在这里插入图片描述

    • 操作符 - $type 在这里插入图片描述

    • 如果你需要在MongoDB中读取指定数量的数据记录,可以使用MongoDB的Limit方法,limit()方法接受一个数字参数,该参数指定从MongoDB中读取的记录条数。 在这里插入图片描述

    • 除了可以使用limit()方法来读取指定数量的数据外,还可以使用skip()方法来跳过指定数量的数据,skip方法同样接受一个数字参数作为跳过的记录条数。 在这里插入图片描述

    • 可以使用 sort() 方法对数据进行排序 在这里插入图片描述

    • 聚合方法(相当于sql中count()方法等函数):db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)

      • 聚合的表达式 在这里插入图片描述 在这里插入图片描述
  3. 修改嵌套文档方法()

    • $push方法():可以在嵌套文档中插入 在这里插入图片描述在这里插入图片描述
    • $pull()方法:删除嵌套文档中指定的值 在这里插入图片描述

性能分析函数:explain方法

  • MongoDB中的explain()函数可以帮助我们查看查询相关的信息,这有助于我们快速查找到搜索瓶颈进而解决它,本文我们就来看看explain()的一些用法及其查询结果的含义。 在这里插入图片描述

聚合管道

  • 聚合管道是 MongoDB 2.2版本引入的新功能。它由阶段(Stage)组成,文档在一个阶段处理完毕后,聚合管道会把处理结果传到下一个阶段。
  • 功能:
    • 对文档进行过滤,查询出符合条件的文档
    • 对文档进行变换,改变文档的输出形式
  • 每个阶段用阶段操作符(Stage Operators)定义,在每个阶段操作符中可以用表达式操作符(Expression Operators)计算总和、平均值、拼接分割字符串等相关操作,直到每个阶段进行完成,最终返回结果,返回的结果可以直接输出,也可以存储到集合中。
  • 下图就是最直观的过程:match用于获取status="A"的记录,然后将符合条件的记录送到下一阶段match 用于获取 status = "A" 的记录,然后将符合条件的记录送到下一阶段 group 中进行分组求和计算,最后返回 Results。 在这里插入图片描述

阶段操作符

  • 下面时需要使用的案例数据: 在这里插入图片描述
  1. $project

    • 作用:修改文档的结构,可以用来重命名、增加或删除文档中的字段。
    • 范例: 在这里插入图片描述 在这里插入图片描述
  2. $match

    • 作用:用于过滤文档。用法类似于 find() 方法中的参数。
    • 范例: 在这里插入图片描述
  3. $group

    • 作用:将集合中的文档进行分组,可用于统计结果
    • 范例: 在这里插入图片描述
  4. $sort

    • 作用:将集合中的文档进行排序。(其中1代表升序,-1代表降序)
    • 范例: 在这里插入图片描述
  5. $limit

    • 作用:限制返回的文档数量
    • 范例: 在这里插入图片描述
  6. $skip

    • 作用:跳过指定数量的文档,并返回余下的文档。
    • 范例: 在这里插入图片描述
  7. $unwind

    • 作用:将文档中数组类型的字段拆分成多条,每条文档包含数组中的一个值。
    • 注意:
      • $unwind 参数数组字段为空或不存在时,待处理的文档将会被忽略,该文档将不会有任何输出
      • $unwind 参数不是一个数组类型时,将会抛出异常
      • $unwind 所作的修改,只用于输出,不能改变原文档
    • 范例: 在这里插入图片描述

Mongodb索引

  1. MongoDB使用 createIndex() 方法来创建索引。一旦创建索引,您将无法重命名。相反,您必须删除并使用新名称来重新创建索引。 在这里插入图片描述
    • options为参数 在这里插入图片描述
  2. 索引的类型
    • 单字段索引:下面的是创建的不同情况,下图是一个集合的字段 在这里插入图片描述

      • 在单个字段上创建升序索引:在score上创建索引 在这里插入图片描述

      • 在嵌入式字段上创建索引:在location文档的state上创建索引 在这里插入图片描述

      • 在嵌入式文档上创建索引:在location文档上创建索引 在这里插入图片描述

    • 复合索引:db.collection.createIndex( { <field1>: <type>, <field2>: <type2>, ... } )

      • 复合索引中列出的字段的顺序很重要,索引将包含对文档的引用,用下面一个假设进行说明:
        • db.products.createIndex( { "item": 1, "stock": 1 } )
        • 这些文档首先按上一面的item字段的值排序,然后在该item字段的每个值按stock字段值进行排序。
      • 排序顺序
      • 索引前缀: 在这里插入图片描述
    • 多键索引 :如果任何索引字段是数组,MongoDB 会自动创建一个多键索引;您不需要显式指定多键类型

    • 地理空间索引

    • 文本索引:要创建text索引,请使用 db.collection.createIndex()方法。要索引包含字符串或字符串元素数组的字段,请"text"在索引文档中包含该字段并指定字符串文字:db.reviews.createIndex( { comments: "text" } )

    • 哈希索引

MongoDB索引原理

这里我自己先不做笔记,毕竟这个我要多看看才好写,我推荐一个MongoDB中文社区的大佬写的:mongoing.com/archives/27…

MongoDB复制集

  • 由于访问量的日益增加,一个MongoDB服务器已经不足以满足人们的访问需求,所以我们需要做一个MongoDB的集群

复制集的简介

  • MongoDB的复制至少需要两个节点。其中一个是主节点,负责处理客户端请求,其余的都是从节点,负责复制主节点上的数据。建议提供仲裁节点,此节点不存储数据,作用是当主节点出现故障时,选举出某个备用节点成为主节点,保证MongoDB的正常服务。客户端只需要访问主节点或从节点,不需要访问仲裁节点。
  • MongoDB各个节点常见的搭配方式为:一主一从一仲裁、一主多从一仲裁。主节点记录在其上的所有操作oplog(操作日志),从节点定期轮询主节点获取这些操作,然后对自己的数据副本执行这些操作,从而保证从节点的数据与主节点一致。
  • 下图为他们复制集的结构图: 在这里插入图片描述

搭建复制集

  1. 这次主要做的是一主两从一仲裁的复制集模式,所以我们需要在data目录下,创建一个rs文件夹,然后通过在rs文件夹中创建4个mongodb服务的数据库 在这里插入图片描述
  2. 然后我们需要给4个mongodb服务配置文件,所以我们需要在mongodb下创建一个conf文件夹,然后创建rs,再分别编写他们的配置 在这里插入图片描述 在这里插入图片描述
  • primary.conf (主节点配置文件)
dbpath=/opt/mongodb/data/rs/p0  数据库目录
logpath=/opt/mongodb/log/primary.log  日志文件
pidfilepath=/opt/mongodb/pids/primary.pid  进程描述文件
bind_ip_all=true
directoryperdb=true  为数据库自动提供重定向
logappend=true   日志追加写入
replSet=rs    复制集名称, 一个复制集中的多个节点命名一致。
port=27117   端口
oplogSize=10000   操作日志容量
fork=true   后台启动

  • secondary0.conf(从节点配置文件)
dbpath=/usr/local/mongodb/data/rs/s0/
logpath=/usr/local/mongodb/logs/rs/secondary0.log
logappend=true
bind_ip_all=true
port=27118
fork=true
replSet=rs
pidfilepath=/usr/local/mongodb/pids/rs/secondary0.pid
oplogSize=1000
directoryperdb=true
  • secondary1.conf(从节点配置文件)
dbpath=/usr/local/mongodb/data/rs/s1/
logpath=/usr/local/mongodb/logs/rs/secondary1.log
logappend=true
bind_ip_all=true
port=27119
fork=true
replSet=rs
pidfilepath=/usr/local/mongodb/pids/rs/secondary1.pid
oplogSize=1000
directoryperdb=true

  • arbiter.conf(仲裁节点的配置文件)
dbpath=/usr/local/mongodb/data/rs/a0/
logpath=/usr/local/mongodb/logs/rs/arbiter.log
logappend=true
bind_ip_all=true
port=27120
fork=true
replSet=rs
pidfilepath=/usr/local/mongodb/pids/rs/arbiter.pid
oplogSize=1000
directoryperdb=true
  1. 配置完之后就可以分别启动4个mongodb服务了 在这里插入图片描述
  2. 使用语句ps aux | grep mongod查看一下服务 在这里插入图片描述
  3. 已经启动,然后通过 bin/mongo --port 27117(你自己设置的端口号)访问到主节点服务器(下图是已经初始化的展示) 在这里插入图片描述
  4. 但是现在还差点,因为没有初始化,我们需要初始化,初始化之后就可以有主从模式了
rs.initiate({
    _id:"rs", 复制集命名,与配置文件对应
    members:[ 成员信息
    _id:唯一标记,host:主机地址,priority:权重(复制集中工作的优先级,数字越大优先级越高) arbiterOnly:是否是仲裁节点
        {_id:0,host:"127.0.0.1:27117",priority:3},
        {_id:1,host:"127.0.0.1:27118",priority:2},
        {_id:2,host:"127.0.0.1:27119",priority:1},
        {_id:3,host:"127.0.0.1:27120",arbiterOnly:true}
    ]
});

  1. 启动另一个链接,通过 bin/mongo --port 27118(你自己设置的端口号)访问到从节点节点服务器 在这里插入图片描述
  2. 但是从节点现在无法访问主节点的内容,主要是因为没有放权限,所以从节点使用rs.slaveOk()就可以获得权限
    • 默认情况下直接连接从节点是无法查询数据的(db.集合名称.find()报错)。因为从节点是不可读的。如果需要在从节点上读取数据,则需要在从节点控制台输入命令rs.slaveOk([true|false])来设置。rs.slaveOk()或rs.slaveOk(true)代表可以在从节点上做读操作;rs.slaveOk(false)代表不可在从节点上做读操作。 在这里插入图片描述
  3. 使用命令查看复制集状态:rs.status();

在这里插入图片描述 在这里插入图片描述

  1. 使用命令查看当前节点是否是主节点:rs.isMaster();

在这里插入图片描述

总结

  • 当主节点宕机时,仲裁节点会根据配置信息中的权重值优先选举权重高的节点作为主节点继续提供服务。当宕机的主节点恢复后,复制集会恢复原主节点状态,临时主节点重新成为从节点。

.NET Core集成mongodb

  1. 首先你需要在mongobd上制造一些数据
use BookstoreDb//创建一个库

db.createCollection('Brooks')//创建一个集合

db.Books.insertMany([{'Name':'Design Patterns','Price':54.93,'Category':'Computers','Author':'Ralph Johnson'}, {'Name':'Clean Code','Price':43.15,'Category':'Computers','Author':'Robert C. Martin'}])

db.Books.find({}).pretty()

  1. 然后创建一个.net core api(自行创建),并且导入MongoDB.Driver 在这里插入图片描述

  2. 将mongodb配置,写到配置文件appsettings.json

  "BookstoreDatabaseSettings": {
    "BooksCollectionName": "Books",
    "ConnectionString": "mongodb://localhost:27017",
    "DatabaseName": "BookstoreDb"
  }
  1. 在startup.cs 上配置
    • appsettings.json 文件的 BookstoreDatabaseSettings 部分绑定到的配置实例在依赖关系注入 (DI) 容器中注册。 例如BookstoreDatabaseSettings 对象的 ConnectionString 属性使用 appsettings.json 中的BookstoreDatabaseSettings:ConnectionString 属性进行填充。
    • IBookstoreDatabaseSettings 接口使用单一实例服务生存期在 DI 中注册。 在注入时,接口实例时将解析为 BookstoreDatabaseSettings 对象。
			//配置mongodb
            services.Configure<BookstoreDatabaseSettings>(
                    Configuration.GetSection(nameof(BookstoreDatabaseSettings)));
                    
			//上面的代码向 DI 注册了 BookService 类,以支持消费类中的构造函数注入。 
			//单一实例服务生存期是最合适的,因为 BookService 直接依赖于 MongoClient。
			//根据官方 Mongo Client 重用准则,应使用单一实例服务生存期在 DI 中注册 MongoClient。
            services.AddSingleton<IBookstoreDatabaseSettings>(sp =>
                sp.GetRequiredService<IOptions<BookstoreDatabaseSettings>>().Value);

            services.AddSingleton<BookDao>();
  1. 创建用于存储 appsettings.json 文件的 BookstoreDatabaseSettings 属性值的类 BookstoreDatabaseSettings
	 /// <summary>
    /// 存储 appsettings.json 文件的 BookstoreDatabaseSettings 属性值
    /// </summary>
    public class BookstoreDatabaseSettings:IBookstoreDatabaseSettings
    {
        public string BooksCollectionName { get; set; }
        public string ConnectionString { get; set; }
        public string DatabaseName { get; set; }
    }

    /// <summary>
    /// 接口用于依赖注入
    /// </summary>
    public interface IBookstoreDatabaseSettings
    {
        string BooksCollectionName { get; set; }
        string ConnectionString { get; set; }
        string DatabaseName { get; set; }
    }
  1. 创建Book的实体类
    • 在前面的类中,Id属性:
      • 需要在将公共语言运行时 (CLR) 对象映射到 MongoDB 集合时使用。
      • 使用 [BsonId] 进行批注,以将此属性指定为文档的主键。
      • 使用 [BsonRepresentation(BsonType.ObjectId)] 进行批注,以允许将参数作为类型 string 而非 ObjectId 结构传递。 Mongo 处理从 string 到 ObjectId 的转换。
    • BookName 属性使用 [BsonElement] 特性进行批注。 Name 的属性值表示 MongoDB 集合中的属性名称。
 public class Book
    {
        [BsonId]
        [BsonRepresentation(BsonType.ObjectId)]
        public string Id { get; set; }

        [BsonElement("Name")]
        public string Name { get; set; }

        [BsonElement("Price")]
        public decimal Price { get; set; }

        [BsonElement("Category")]
        public string Category { get; set; }

        [BsonElement("Author")]
        public string Author { get; set; }
    }
  1. 接下来可以创建操作类 BookDao,下面是一些细节
    • 通过构造函数注入从 DI 检索 IBookstoreDatabaseSettings 实例。 使用此方法可访问在添加配置模型部分中添加的 appsettings.json 配置值。
    • MongoClient:读取用于执行数据库操作的服务器实例。 此类的构造函数提供了 MongoDB 连接字符串
    • IMongoDatabase:表示用于执行操作的 Mongo 数据库。(也就是下图的database)
    • 使用泛型 GetCollection(collection) 方法来获取对特定集合中的数据的访问权限。 调用此方法后,对集合执行 CRUD 操作。 在 GetCollection(collection) 方法调用中:
      • collection 表示集合名称。
      • TDocument 表示存储在集合中的 CLR 对象类型。
	/// <summary>
    /// 书本集合的操作类
    /// </summary>
    public class BookDao
    {
        /// <summary>
        /// 依赖注入
        /// </summary>
        private readonly IMongoCollection<Book> _books;

        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="bookstoreDatabaseSettings"></param>
        public BookDao(IBookstoreDatabaseSettings bookstoreDatabaseSettings)
        {
            var client = new MongoClient(bookstoreDatabaseSettings.ConnectionString);
            var database = client.GetDatabase(bookstoreDatabaseSettings.DatabaseName);

            _books = database.GetCollection<Book>(bookstoreDatabaseSettings.BooksCollectionName);
        }

        /// <summary>
        /// 获取所有
        /// </summary>
        /// <returns></returns>
        public IEnumerable<Book> Get()
        {
           return _books.Find(book => true).ToList();
        }

        /// <summary>
        /// 根据Id获取一条记录
        /// </summary>
        /// <param name="Id"></param>
        /// <returns></returns>
        public Book Get(string id)
        {
            return _books.Find(book => book.Id == id).FirstOrDefault();
        }

        /// <summary>
        /// 添加信息
        /// </summary>
        /// <param name="book"></param>
        /// <returns></returns>
        public bool Add(Book book)
        {
            try
            {
                _books.InsertOne(book);
                return true;
            }
            catch (Exception e)
            {
                return false;
            }
        }

        /// <summary>
        /// 修改信息
        /// </summary>
        /// <param name="Id"></param>
        /// <param name="bookIn"></param>
        /// <returns></returns>
        public bool Update(Book bookIn)
        {
            try
            {
                _books.ReplaceOne(book => book.Id == bookIn.Id, bookIn);
                return true;
            }
            catch (Exception e)
            {
                return false;
            }
        }

        /// <summary>
        /// 删除信息
        /// </summary>
        /// <param name="bookIn"></param>
        /// <returns></returns>
        public bool Remove(string id)
        {
            try
            {
                _books.DeleteOne(book => book.Id == id);
                return true;
            }
            catch (Exception e)
            {
                return false;
            }
        }
    }
  1. 将操作类注册到startup.cs中 在这里插入图片描述
  2. 这样就完成了,然后 通过前端控制器啊进行测试
  3. 补充一点,我刚学会的,就是mongodb可以使用 聚合函数的,但是如何让.net core也使用mongodb的聚合函数呢,我上网查了之后我就分享一下:
  public List<BsonDocument> Count()
        {
        	//这个就是我们在mongodb上书写的bson语句
            BsonDocument db = new BsonDocument { { "_id", "null" }, { "num", new BsonDocument("$sum", 1) } };
            //上面的方法都可以找到,找不到的就用上面的形式书写即可(温馨提示下面的我只是举个例子,里面的db需要按照mongodb的格式进行书写的)
            var list = _books.Aggregate().Project(db).Match(db).Group(db).Skip(2).Limit(1).ToList<BsonDocument>();
            return list;
        }

查询到数据了但是如何取数据呢?因为你如果直接把数据打印就会是这样的: 在这里插入图片描述

在这里插入图片描述 是不是很难看,所以我在想要是能把之前的数据拿出来就好了,然后我发现是可以的,就是通过下面的方式取到数据: 在这里插入图片描述 在这里插入图片描述