在.NET环境下用C#使用MongoDB的功能的教程

1,068 阅读10分钟

NoSQL的世界在10年代真正繁荣起来。与过去几十年相比,它在选择数据库方面给了我们更多的选择。现在,我们可以考虑你的数据性质和系统架构,并实际挑选最适合我们需求的数据库类型。

关系型数据库是结构化的,有预定义的模式,而非关系型数据库是非结构化的,分布式的,有动态的模式。关于NoSQL生态系统最酷的事情之一是,它包含许多数据库,每种类型都最适合应用于不同类型的问题。

ML.NET Full-Stack: Complete Guide to Machine Learning for .NET Developers

从机器学习的基础知识到更复杂的主题,如神经网络、对象检测和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最大的好处之一。开发人员不需要事先了解数据库的模式,而是可以在开发过程中动态地修改模式。

Data Science Visual

这在我们一开始就不能把模式弄得很好,或者有很多边缘情况需要覆盖的系统中尤其好。同时,这种方式也避免了整个阻抗不匹配的问题,也就是说,消除了对象关系映射层。这看起来像什么呢?

好吧,我们假设之前的文档被存储在名为 "用户 "的集合中。然后,我们可以在该集合中添加另一个文档,该文档将包含前一个文档没有的字段和/或不会有前一个文档有的字段。例如,我们可以在集合中添加下一个文档。

{
    "_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反对。如果你想了解更多这方面的知识,你可以这样做这里这里.

Data Visual

1.4 部署MongoDB的不同方法

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

MongoDB C# - MongoDB Atlas

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

C# and MongoDB - Database and Collection

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中的每个文档都有这个元素,它的类型是ObjectIdBsonElement(field_name) 用于将对象中的其他字段映射到对象属性上。

Programming Visual

因为我们知道文档数据库没有严格的模式,所以有可能在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");
    }
}

Coding Visual

好吧,这是很直接的。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使用MongoCollectionSkipLimit 方法来获取一个必要的数据块。例如,这最后一个函数可以用于分页。

Programming Visual

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的功能。