查询 MongoDB - .NET SDK
在此页面上
您可以使用Realm .NET SDK 的 MongoClient和Query API直接从.NET应用程序代码查询存储在MongoDB Atlas中的数据。 Atlas App Services提供集合的数据访问规则,以便根据登录用户或每个文档的内容安全地检索结果。
以下操作允许使用 Realm .NET SDK 从 .NET 应用程序访问链接的 MongoDB Atlas 集群。
注意
本页描述的每个操作都使用查询来匹配操作执行所依据的集合中的某些文档。 当筛选器匹配集合中的多个文档时,除非指定排序参数,否则将以不确定的顺序返回这些文档。 这意味着,如果您没有为 、 findOne()
、 updateOne()
或deleteOne()
函数指定排序,您的操作可能会匹配与查询匹配的任何文档。 有关排序的更多信息,请参阅cursor.sort()。
用例
您可能出于多种原因想要查询 MongoDB 数据源。通过 Atlas Device Sync 处理客户端中的数据并不总是可行或可能的。您可能希望在以下情况下查询 MongoDB:
数据集较大或客户端设备有限制,无法加载整个数据集
您正在创建或更新自定义用户数据
您正在检索未在 Realm 中建模的文档
您的应用程序需要访问没有严格模式的集合
非 Realm 服务会生成要访问的集合
以上查询场景虽未穷尽,却是直接查询 MongoDB 的一些常见使用案例。
先决条件
从 .NET 应用程序查询 MongoDB 之前,必须在 App Services App 中设置 MongoDB 数据访问权限。要了解如何设置后端应用程序以让 Realm SDK 查询 Atlas,请参阅 App Services 文档中的设置 MongoDB 数据访问权限。
设置
要直接使用MongoDB Atlas cluster 中的数据,首先要实例化一个 MongoClient 对象,并在Atlas 应用程序中传入Realm 服务的名称。然后,为要使用的每个集合实例化MongoClient.Database和MongoClient.Collection 。 以下代码使用默认的“mongodb-atlas”Atlas 服务名称,并为“库存”数据库中的“工厂”集合创建MongoClient.Collection
:
mongoClient = user.GetMongoClient("mongodb-atlas"); dbPlantInventory = mongoClient.GetDatabase("inventory"); plantsCollection = dbPlantInventory.GetCollection<Plant>("plants");
示例数据
本页上的示例使用以下 MongoDB 集合,而该集合描述了连锁植物商店中待售的各种植物:
{ _id: ObjectId("5f87976b7b800b285345a8c4"), name: "venus flytrap", sunlight: "full", color: "white", type: "perennial", _partition: "Store 42" }, { _id: ObjectId("5f87976b7b800b285345a8c5"), name: "sweet basil", sunlight: "partial", color: "green", type: "annual", _partition: "Store 42" }, { _id: ObjectId("5f87976b7b800b285345a8c6"), name: "thai basil", sunlight: "partial", color: "green", type: "perennial", _partition: "Store 42" }, { _id: ObjectId("5f87976b7b800b285345a8c7"), name: "helianthus", sunlight: "full", color: "yellow", type: "annual", _partition: "Store 42" }, { _id: ObjectId("5f87976b7b800b285345a8c8"), name: "petunia", sunlight: "full", color: "purple", type: "annual", _partition: "Store 47" }
映射类
在 MongoDB 中处理对象时,应创建与 BSON 对象相对应的.NET 类 (POCO)。这允许您直接序列化和反序列化对象,而不是使用通用的 BsonDocument
对象。在此页面上的所有示例中,我们都使用以下 Plant
映射类来实现此目的:
public partial class Plant : IRealmObject { [ ] public ObjectId Id { get; set; } = ObjectId.GenerateNewId(); [ ] public string? Name { get; set; } [ ] [ ] public string? Sunlight { get; set; } [ ] [ ] public string? Color { get; set; } [ ] [ ] public string? Type { get; set; } [ ] public string? Partition { get; set; } } public enum Sunlight { Full, Partial } public enum PlantColor { White, Green, Yellow, Purple } public enum PlantType { Perennial, Annual }
注意
如果您选择提供自定义构造函数,则必须声明一个不带参数的公共构造函数。
创建文档
要在 MongoDB 数据存储中创建文档,您需要实例化映射类,并将新对象传递给InsertOneAsync() 。 还可以创建多个文档,并使用InsertManyAsync() 在一次调用中将其插入。
插入单一文档
以下代码片段会将描述“Venus Flytrap”(捕蝇草)植物的单个文档插入到我们的“plants”(植物)集合中:
var plant = new Plant { Name = "Venus Flytrap", Sunlight = Sunlight.Full.ToString(), Color = PlantColor.White.ToString(), Type = PlantType.Perennial.ToString(), Partition = "Store 42" }; var insertResult = await plantsCollection.InsertOneAsync(plant); var newId = insertResult.InsertedId;
插入多个文档
您可以使用InsertManyAsync() 同时插入多个文档。
以下代码段通过实例化对象、将它们添加到 List<Plant>
并将该列表传递给 InsertManyAsync()
来将四个 Plant
对象插入到“工厂”集合中:
var sweetBasil = new Plant { Name = "Sweet Basil", Sunlight = Sunlight.Partial.ToString(), Color = PlantColor.Green.ToString(), Type = PlantType.Annual.ToString(), Partition = "Store 42" }; var thaiBasil = new Plant { Name = "Thai Basil", Sunlight = Sunlight.Partial.ToString(), Color = PlantColor.Green.ToString(), Type = PlantType.Perennial.ToString(), Partition = "Store 42" }; var helianthus = new Plant { Name = "Helianthus", Sunlight = Sunlight.Full.ToString(), Color = PlantColor.Yellow.ToString(), Type = PlantType.Annual.ToString(), Partition = "Store 42" }; var petunia = new Plant { Name = "Petunia", Sunlight = Sunlight.Full.ToString(), Color = PlantColor.Purple.ToString(), Type = PlantType.Annual.ToString(), Partition = "Store 47" }; var listofPlants = new List<Plant> { sweetBasil, thaiBasil, helianthus, petunia }; var insertResult = await plantsCollection.InsertManyAsync(listofPlants); var newIds = insertResult.InsertedIds;
读取文档
要从数据存储中检索文档,您可以创建一个 BsonDocument
筛选器来定义要在Atlas Search上进行搜索的属性,然后将该筛选器传递给FindOneAsync()或FindAsync() 。 还可以通过调用CountAsync() 来获取与筛选器匹配的所有文档的计数。
查找单个文档
以下示例展示了如何查找“name”(名称)属性为“petunia”(牵牛花)的植物:
var petunia = await plantsCollection.FindOneAsync( new { name = "Petunia" }, null);
查找多个文档
以下示例展示了如何查找“type”(类型)属性为“perennial”(多年生)的所有植物:
var allPerennials = await plantsCollection.FindAsync( new { type = PlantType.Perennial.ToString() }, new { name = 1 });
重要
我们使用的是 FindAsync()
的第三个参数,它指定了排序顺序。如果要查询多个文档,则应包含排序顺序,以确保结果一致。
对集合中的文档进行计数
下面的示例会返回集合中所有植物的计数:
var allPlants = await plantsCollection.CountAsync();
Update Documents
要更新MongoDB数据存储中的现有文档,您可以创建一个 BsonDocument
过滤器来定义您想要Atlas Search的属性,然后创建第二个 BsonDocument
来定义您想要更改的属性。 如果只更新一个文档,则将这两个对象传递给UpdateOneAsync() 。 如果要批量更新多个文档,请调用UpdateManyAsync()。
更新单份文档
以下代码会查找“name”(名称)属性为“petunia”(牵牛花)的植物,并将其“sunlight”(光照)属性更改为“partial”(部分):
var updateResult = await plantsCollection.UpdateOneAsync( new { name = "Petunia" }, new BsonDocument("$set", new BsonDocument("sunlight", Sunlight.Partial.ToString())) );
更新多个文档
以下代码会查找“_partition”(分区)值为“store 47”(存储 47)的所有植物,并将它们全部更改为“area 51”(区域 51):
var filter = new { _partition = "Store 47" }; var updateDoc = new BsonDocument("$set", new BsonDocument("_partition", "Area 51")); var updateResult = await plantsCollection.UpdateManyAsync( filter, updateDoc);
更新或插入文档
UpdateOneAsync()和UpdateManyAsync()都有一个可选的布尔属性,用于指定更新是否应为更新或插入(即,如果文档不存在,则应创建它)。 默认情况下,不执行更新或插入操作。
以下示例查找 name
属性为“Pothos”、type
属性为“perennial”且 sunlight
属性为“full”的工厂。如果工厂符合这些条件,则该方法将工厂的 _partition
值更新为“Store 42”。如果集合中不存在具有该名称的工厂,该方法将创建具有所有已定义属性(包括更新)的新工厂。
var filter = new BsonDocument() .Add("name", "Pothos") .Add("type", PlantType.Perennial.ToString()) .Add("sunlight", Sunlight.Full.ToString()); var updateResult = await plantsCollection.UpdateOneAsync( filter, new BsonDocument("$set", new BsonDocument("_partition", "Store 42")), upsert: true); /* The upsert will create the following object: { "name": "pothos", "sunlight": "full", "type": "perennial", "_partition": "Store 42" } */
Delete Documents
删除文档的过程与创建(或更新)文档的过程非常相似:创建一个BsonDocument
来定义要匹配的属性,然后调用DeleteOneAsync() 。 或DeleteManyAsync()。
删除单个文档
以下示例删除了它找到的第一个“名称”属性值为“Thai Basil”的文档:
var filter = new BsonDocument("name", "Thai Basil"); var deleteResult = await plantsCollection.DeleteOneAsync(filter);
删除多个文档
以下示例删除了“类型”属性值为“年度”的所有文档:
var filter = new BsonDocument("type", PlantType.Annual); var deleteResult = await plantsCollection.DeleteManyAsync(filter);
聚合文档
聚合操作会通过一系列名为聚合管道的数据聚合阶段来运行集合中的所有文档。聚合允许您过滤和转换文档、收集有关相关文档群组的摘要数据以及其他复杂的数据操作。
聚合操作接受聚合阶段数组作为输入,并返回 Task(任务),解析为管道处理过的文档集合。
注意
Compass 提供了一个实用程序,用于构建聚合管道并将其导出为 C# 和其他语言。 有关更多信息,请参阅聚合管道构建器。
对集合中的文档进行分组
.NET SDK 支持使用 AggregateAsync() 方法及其泛型过载对集合进行聚合。
以下示例按 type
值对工厂集合中的所有文档进行分组,聚合每种类型的数量,然后按升序排序:
var groupStage = new BsonDocument("$group", new BsonDocument { { "_id", "$type" }, { "count", new BsonDocument("$sum", 1) } }); var sortStage = new BsonDocument("$sort", new BsonDocument("_id", 1)); var aggResult = await plantsCollection.AggregateAsync(groupStage, sortStage); foreach (var item in aggResult) { var id = item["_id"]; var count = item["count"]; Console.WriteLine($"Plant type: {id}; count: {count}"); }
上面的示例使用一系列嵌套的 BsonDocuments 构建管道,这可能会导致写入和调试变得复杂。 如果您已经熟悉 QueryAPI ,则可以将查询作为string 传递给 BsonDocument_Parse() 方法。以下示例执行与前面的示例相同的聚合:
var groupStep = BsonDocument.Parse(@" { $group: { _id: '$type', count: { $sum: 1 } } } "); var sortStep = BsonDocument.Parse("{$sort: { _id: 1}}"); aggResult = await plantsCollection.AggregateAsync(groupStep, sortStep); foreach (var item in aggResult) { var id = item["_id"]; var count = item["count"]; Console.WriteLine($"Id: {id}, Count: {count}"); }
筛选文档
您可以使用$match阶段使用标准 MongoDB查询语法筛选文档。
以下示例展示了在使用聚合时如何过滤文档。由于我们知道此聚合管道会返回 Plant
对象的集合,因此我们使用 AggregateAsync() 方法的泛型过载。
var matchStage = new BsonDocument("$match", new BsonDocument("type", new BsonDocument("$eq", PlantType.Perennial))); // Alternate approach using BsonDocument.Parse(...) matchStage = BsonDocument.Parse(@"{ $match: { type: { $eq: '" + PlantType.Perennial + @"' } }}"); var sortStage = BsonDocument.Parse("{$sort: { _id: 1}}"); var aggResult = await plantsCollection.AggregateAsync<Plant>(matchStage, sortStage); foreach (var plant in aggResult) { Console.WriteLine($"Plant Name: {plant.Name}, Color: {plant.Color}"); }
项目数据
您可以使用 $project 阶段包含或省略文档中的特定字段,或使用聚合运算符计算新字段。投影有两种工作方式:
显式包含值为 1 的字段。此举的不利影响是会隐式排除所有未指定的字段。
隐式排除值为 0 的字段。此举的不利影响是会隐式包含所有未指定的字段。
这两种投影方法是互斥的:如果显式包含字段,则不能显式排除字段,反之亦然。
注意
_id
字段是一个特例:除非另行显式指定,否则它始终包含在每个查询中。因此,您可以排除具有 0
值的 _id
字段,同时包含其他字段,例如具有 1
的 _partition
。只有排除 _id
字段的特殊情况才允许在一个 $project
阶段中同时进行排除和包含。
以下示例展示了在使用聚合时如何使用项目。在此示例中,我们执行以下操作:
不包含 "Id" 属性,
包含:“Partition”(分区)、“Type”(类型)和“Name”(名称)属性,
创建一个名为“storeNumber”的新属性,该属性是通过在空白处拆分 _partition 值,然后仅返回第二部分来构建的。
var projectStage = new BsonDocument("$project", new BsonDocument { { "_id", 0 }, { "_partition", 1 }, { "type", 1 }, { "name", 1 }, { "storeNumber", new BsonDocument("$arrayElemAt", new BsonArray { new BsonDocument("$split", new BsonArray { "$_partition", " " }), 1 }) } }); var sortStage = BsonDocument.Parse("{$sort: { storeNumber: 1}}"); var aggResult = await plantsCollection.AggregateAsync(projectStage, sortStage); foreach (var item in aggResult) { Console.WriteLine($"{item["name"]} is in store #{item["storeNumber"]}."); }
以下示例展示了如何使用 BsonDocument.Parse()
方法构建 projectStage
:
projectStage = BsonDocument.Parse(@" { _id:0, _partition: 1, type: 1, name: 1, storeNumber: { $arrayElemAt: [ { $split:[ '$_partition', ' ' ] }, 1 ] } }");