NoSQL的世界在10年代真正繁荣起来。与过去几十年相比,它在选择数据库方面给了我们更多的选择。现在,我们可以考虑你的数据性质和系统架构,并实际挑选最适合我们需求的数据库类型。
关系型数据库是结构化的,有预定义的模式,而非关系型数据库是非结构化的,分布式的,有动态的模式。关于NoSQL生态系统最酷的事情之一是,它包含许多数据库,每种类型都最适合应用于不同类型的问题。
从机器学习的基础知识到更复杂的主题,如神经网络、对象检测和NLP,本课程将引导你成为ML.NET的超级英雄。
目前在NoSQL市场上占主导地位的数据库之一无疑是MongoDB。它是一个文件型NoSQL数据库,因此比其他一些类型的NoSQL数据库更接近于传统的关系型数据库。这可能可以解释它的成功。在这篇文章中,我们将看到我们如何在C#中使用MongoDB。
1.MongoDB基础知识
在我们看到用C#使用MongoDB的不同方式之前,让我们先了解一下这个数据库的一些基本概念。来自MongoDB的人对他们所谓的Nexus架构非常自豪。这种架构让人有能力将关系型数据库的成熟概念和能力与NoSQL的创新结合起来。
1.1 MongoDB文档
MongoDB是一个面向文档的数据库,如前所述,它与关系型数据库有某些相似之处。MongoDB拥有的不是行,而是文档。一个给定记录的所有数据都存储在一个文档中,与关系型数据库不同的是,一个给定记录的信息分散在许多表中。在引擎盖下,文档是BSON文件,它是JSON文件的二进制编码序列化。尽管如此,从程序员的角度来看,MongoDB用纯JSON文件进行操作。例如,我们可以像这样表示一个用户:
{
"_id" : ObjectId("58e28d41b1ad7d0c5cd27549"),
"name" : "Nikola Zivkovic",
"blog" : "rubikscode.net",
"numberOfArticles" : 10,
"Adress" : [
"street" : "some street",
"city" : "Berlin",
"country" : "Germany"
],
"expertise" : [".NET", "Machine Learning", "Deep Learning", "Management", "Leadership"]
}
你可以注意到在这个JSON文件的开头有一个 _id 字段。这个字段是唯一的,由MongoDB为数据库中的每个文件生成。这样,MongoDB就保持了关系型数据库的一个重要特性--强一致性。
1.2 MongoDB集合
文档被存储在集合中。集合是由某种程度上相关的文档组成的群体,但这些文档不需要有相同的结构。这就是MongoDB最大的好处之一。开发人员不需要事先了解数据库的模式,而是可以在开发过程中动态地修改模式。

这在我们一开始就不能把模式弄得很好,或者有很多边缘情况需要覆盖的系统中尤其好。同时,这种方式也避免了整个阻抗不匹配的问题,也就是说,消除了对象关系映射层。这看起来像什么呢?
好吧,我们假设之前的文档被存储在名为 "用户 "的集合中。然后,我们可以在该集合中添加另一个文档,该文档将包含前一个文档没有的字段和/或不会有前一个文档有的字段。例如,我们可以在集合中添加下一个文档。
{
"_id" : ObjectId("58e28da0b1ad7d0c5cd2754a"),
"name" : "Vanja Zivkovic",
"blog" : "abrahadabra.rs",
"Adress" : [
"street" : "some street",
"city" : "Berlin",
"country" : "Germany"
],
"expertise" : ["Event Management", "SEO", "Marketing", "Social Media"]
"location" : [45, 19]
}
这些文档是相似的,但不相同。集合将它们分组,并让你有能力为这些文档添加索引。索引是MongoDB从关系型数据库中继承的概念之一,形式相同。
1.3 MongoDB和关系型数据库的区别
有必要强调一下MongoDB和关系型数据库的其他一些区别。首先,MongoDB没有外键。但是,它有一个看起来很像的功能--引用。基本上,任何对象都可以有对其他对象的引用,使用其id,但这不会自动更新,而是由应用程序来跟踪这些连接。
这样做的原因是,一旦在关系型数据库中引入外键,就很难从中解开。由于文档数据模型,并且由于一个 "记录 "的所有必要信息都存储在一个文档中,MongoDB不提供连接。然而,有一个类似的机制,叫做Lookup。在其他差异中,应该提到的是,MongoDB中没有多表交易。
在这一节中,我还没有涵盖从外壳、复制集和分片的CRUD反对。如果你想了解更多这方面的知识,你可以这样做这里和 这里.

1.4 部署MongoDB的不同方法
有不同的方法来安装和部署这个NoSQL数据库。你可以把它安装在你的本地计算机或服务器上,或者你可以使用云选项--MongoDB Atlas。在这篇文章中,我们使用前者。有几个可用的包,在本教程中我们使用社区包。在这里,我们创建一个集群并将其部署在Azure中。

为了这个练习的目的,我们创建了名为 "博客 "的数据库 , 这个数据库有一个集合--用户, 这个集合包含每个用户的文档 。

2.安装MongoDB驱动
MongoDB的伙计们为不同的编程语言提供了大量的驱动程序。驱动程序只是客户端库,应用程序可以使用它来与Mongo数据库进行通信。对于.NET,我们需要做的就是安装NuGet包。
安装-包MongoDB.Driver
或者如果你使用dotnet CLI。
dotnet add package MongoDB.Driver
请注意,在这个实现中,我们使用.NET 6和2.17版本的驱动程序。
3.将BSON映射为强类型的C#对象
如果你看了我以前的 文章,你已经知道MongoDB以BSON格式存储文件。这些文件基本上是二进制的JSON文件(好吧,比这更复杂一点:))。当我们使用.NET驱动时,我们通过 BsonDocument 来消费文档 。 在这个练习中,我们要做的是将这些BsonDocuments映射为强类型的C#对象。但在这之前,让我们先看看我们的数据库和它的实体是怎样的。
正如已经提到的,我们使用的数据库叫--博客 ,它有一个集合--用户 。 单个用户的JSON文档看起来像这样的东西:
{
"_id" : ObjectId("59ce6b34f48f171624840b05"),
"name" : "Nikola",
"blog" : "rubikscode.net",
"age" : 34,
"location" : "Berlin"
}
在C#中的等价物看起来是这样的:
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace MongoDb
{
/// <summary>
/// Class used in business logic to represent user.
/// </summary>
public class User
{
[BsonId]
public ObjectId Id { get; set; }
[BsonElement("name")]
public string Name { get; set; }
[BsonElement("blog")]
public string Blog { get; set; }
[BsonElement("age")]
public int Age { get; set; }
[BsonElement("location")]
public string Location { get; set; }
}
}
你会注意到,这个类的每个属性都有属性。这些属性允许我们自动将数据从 BsonDocument 映射到我们的类。BsonId 用于映射唯一的文档标识符。MongoDB中的每个文档都有这个元素,它的类型是ObjectId。BsonElement(field_name) 用于将对象中的其他字段映射到对象属性上。

因为我们知道文档数据库没有严格的模式,所以有可能在JSON文档中拥有更多的字段,而这些字段是我们真正想在应用程序中使用的。另外,我们有可能不想从数据库中获取所有的字段。对于这一点,我们可以在类本身使用BsonIgnoreExtraElements 属性。
4.C#和MongoDB存储库的实现
本练习的主要目标是创建一个类,使我们有能力对用户集合进行简单的CRUD操作。我们需要做的第一件事就是从我们的应用程序连接到数据库。最简单的方法是使用*MongoClient* 类。它接受连接字符串,所以我们必须提供这个。
另外,也可以使用 MongoClientSettings 类,它提供了各种可能性。其中一个有趣的是ClusterConfiguration 属性,它属于 ClusterBuilder 类型,用于配置集群。我们需要使用的其他类是 MongoDatabase ,用于访问定义的数据库 (本例中为博客),以及 MongoCollection ,用于访问定义的集合 ( 本例中为 用户 )。下面是代码中的样子。
/// <summary>
/// Class used to access Mongo DB.
/// </summary>
public class UsersRepository
{
private IMongoClient _client;
private IMongoDatabase _database;
private IMongoCollection<User> _usersCollection;
public UsersRepository(string connectionString)
{
_client = new MongoClient(connectionString);
_database = _client.GetDatabase("blog");
_usersCollection = _database.GetCollection<User>("users");
}
}

好吧,这是很直接的。MongoCollection 是用Users作为模板类型来定义的,而这只是因为我们在Users类中添加了这些属性。另一种方法是通过使用BsonDocument来实现,但这样我们就必须手动将字段映射到Users类的属性上。
3.1 用C#和MongoDB创建操作
一旦遵循了前面的步骤,在数据库中插入文档就很容易完成。
public async Task InsertUser(User user)
{
await _usersCollection.InsertOneAsync(user);
}
很明显,我使用了异步操作InsertOneAsync,但你也可以使用同步操作。同样,因为我们在Users属性上映射了JSON字段,所以很容易进行这些操作。
3.2 使用C#和MongoDB的读取操作
读取用户是这样做的:
public async Task<List<User>> GetAllUsers()
{
return await _usersCollection.Find(new BsonDocument()).ToListAsync();
}
public async Task<List<User>> GetUsersByField(string fieldName, string fieldValue)
{
var filter = Builders<User>.Filter.Eq(fieldName, fieldValue);
var result = await _usersCollection.Find(filter).ToListAsync();
return result;
}
public async Task<List<User>> GetUsers(int startingFrom, int count)
{
var result = await _usersCollection.Find(new BsonDocument())
.Skip(startingFrom)
.Limit(count)
.ToListAsync();
return result;
}
这个功能有三种不同的实现方式。让我们来看看它们各自的情况。
GetAllUsers 返回数据库中的所有用户。我们使用MongoCollection 类的查找方法来实现,并将空的BsonDocument传给它。在下一个方法,GetUsersByField方法中,我们可以看到这个查找 方法实际上是接收过滤器对象,它将使用该对象作为获取数据的标准。
在第一个函数中,我们使用一个空的过滤器,因此从集合中接收所有的用户。在第二个函数中,我们使用Builder 来创建过滤器,该过滤器将被用于对抗数据库。最后,最后一个函数--GetUsers使用MongoCollection的Skip 和 Limit 方法来获取一个必要的数据块。例如,这最后一个函数可以用于分页。

3.3 使用C#和MongoDB的更新操作
文件可以用类似的方式进行更新。
public async Task<bool> UpdateUser(ObjectId id, string udateFieldName, string updateFieldValue)
{
var filter = Builders<User>.Filter.Eq("_id", id);
var update = Builders<User>.Update.Set(udateFieldName, updateFieldValue);
var result = await _usersCollection.UpdateOneAsync(filter, update);
return result.ModifiedCount != 0;
}
在这个例子中,使用了函数UpdateOneAsync ,但是如果你想改变多个文档的值,你可以使用UpdateMultipleAsync。另外,这些操作的同步同级也存在。
另一个有趣的事实是,使用这个函数你可以添加不在 "模式 "中的字段。例如,如果你想在用户文档中添加一个新字段,你可以这样做:
var users = await _mongoDbRepo.GetUsersByField("name", "Nikola");
var user = users.FirstOrDefault();
var result = await _mongoDbRepo.UpdateUser(user.Id, "address", "test address");
这样一来,MongoDB(以及其他的文档数据库)的模块化模式的特点就被利用了。你不会被定义的对象所阻挡,你可以在需要时动态地存储信息。
3.4 用C#和MongoDB进行删除操作
最后,用户可以通过这种方式被删除:
public async Task<bool> DeleteUserById(ObjectId id)
{
var filter = Builders<User>.Filter.Eq("_id", id);
var result = await _usersCollection.DeleteOneAsync(filter);
return result.DeletedCount != 0;
}
public async Task<long> DeleteAllUsers()
{
var filter = new BsonDocument();
var result = await _usersCollection.DeleteManyAsync(filter);
return result.DeletedCount;
}
很直接,你不觉得吗?与阅读示例中一样,Builder被用于创建一个过滤器。
3.5 用C#和MongoDB创建索引
添加索引是有点不同的。如果我们想动态地做,最简单的方法是使用BsonDocument(.)。 这里是它的操作方法。
public async Task CreateIndexOnCollection(IMongoCollection<BsonDocument> collection, string field)
{
var keys = Builders<BsonDocument>.IndexKeys.Ascending(field);
await collection.Indexes.CreateOneAsync(keys);
}
另一方面,如果我们事先知道我们的索引将是什么,我们可以使用像这样的强类型的实现。
public async Task CreateIndexOnNameField()
{
var keys = Builders<User>.IndexKeys.Ascending(x => x.Name);
await _usersCollection.Indexes.CreateOneAsync(keys);
}
结论
在这里,在一个简单的例子中,我们经历了MongoDB驱动的许多功能。我们学会了如何将JSON文档映射到.NET对象,这为我们使用其他功能提供了一种简单的方法。最常用的CRUD操作也得到了展示和解释。
总的来说,我们可以看到如何在.NET环境下用C#来使用MongoDB的功能。
