65.9K
CodeProject 正在变化。 阅读更多。
Home

初探 NoSQL 及 MongoDB

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (19投票s)

2014年11月5日

CPOL

11分钟阅读

viewsIcon

39923

用 MongoDB 入门 NoSQL!

今天,我决定研究一下 NoSQL。它并不算新,实际上我有点晚才搭上 NoSQL 这趟车,但到目前为止我还没有用到它的需求(实际上现在也还没有,但我有一些空闲时间并且要写一篇博客)。由于 NoSQL 可能会相当复杂,因为它强加了一种新的数据存储思维方式,而且我不可能讨论所有可以讨论的内容,所以我会在这篇文章的末尾添加一些额外阅读材料。

NoSQL 概述

首先,什么是 NoSQL?顾名思义,它不是 SQL(结构化查询语言),SQL 是数据库支持关系数据库模型的标准。由于 SQL 已经成为标准大约二三十年了,我将不再讨论它,你可能已经知道了。对 NoSQL 的一个常见误解是它代表“no SQL”(没有 SQL),而它实际上意味着“Not Only SQL”(不仅仅是 SQL),这意味着 NoSQL 中至少有一些 SQL 的优点。无论这种 SQL 的优点是什么,它都不是关系模型。这就是 NoSQL 与 SQL 根本不同的地方,期望去规范化和重复数据。这个“特性”使得模式可以灵活。在 NoSQL 中,通常很容易向数据库添加字段。在 SQL 数据库中,如果表包含一些数据,你可能需要锁定几分钟,而在 NoSQL 中,你可以在运行时(生产过程中!)动态添加字段。由于去规范化,你减少甚至消除了昂贵的连接,查询数据也可能比典型的 SQL 数据库更快。这种数据存储方法的缺点是,数据一致性更难实现。在 SQL 中,如果你的数据库已经规范化,一致性或多或少是有保证的,而 NoSQL 提供了一致性或最终一致性。NoSQL 数据库如何提供这种(最终)一致性因供应商而异,但它不像 SQL 数据库那样自然。此外,由于数据存储和查询的方式,NoSQL 数据库往往比 SQL 数据库更容易跨机器扩展。

除此之外,NoSQL 没有统一的定义,因为没有标准。尽管如此,NoSQL 大致可以分为四种数据库模型(有些人会说更多,我们不深入讨论这些细节):文档、图、键值和宽列。所以让我们快速了解一下这些模型并尝试一个!

文档模型

首先是文档模型。当我们想到文档时,不要想到 Word 或 Excel 文档,要想到像 Java 或 C# 这样的面向对象语言中的对象。每个文档都有包含值的字段,例如字符串、日期、另一个文档或值的数组。文档的模式是动态的,因此添加新字段非常容易。文档可以根据任何字段进行查询。

由于一个值可以是另一个文档或文档数组,因此数据访问得到简化,并且减少甚至消除了对 `join` 的使用,就像在关系数据库中需要的那样。这也意味着你需要去规范化并存储冗余数据!

文档模型数据库可用于各种应用程序。该模型灵活,文档具有丰富的查询功能。此外,文档结构与现代编程语言中的对象非常相似。

文档数据库的一些例子有 MongoDB 和 CouchDB。

图模型

接下来是图模型。顾名思义,这个模型将数据存储在图中,用节点、边和属性来表示数据。图是一种数学结构,我不会再深入讨论它。图数据库将数据建模为实体之间的关系网络。听起来很困难?我也这么认为。无论如何,当你的应用程序基于各种关系时,例如社交网络,图数据库是首选。

图数据库的一些例子有 HyperGraphDB 和 Neo4j。

键值模型

键值数据库是 NoSQL 数据库中最简单的。它们基本上提供一个键和一个值,其中值可以是任何东西。数据只能通过键进行查询。每个键可以有不同的(类型)值。由于这种简单性,这些数据库往往具有很高的性能和可扩展性,但是,由于这种简单性,它们也不适用于许多应用程序。

键值数据库的一些例子是 Redis 和 Riak。

宽列模型

最后是宽列模型。与键值模型一样,宽列模型由一个可以查询数据的键组成,可以具有很高的性能,并且不适用于所有应用程序。每个键都包含一个“单一”值,该值可以具有可变数量的列。每列可以嵌套其他列。列可以分组到族中,并且每列可以属于多个列族。与对象模型一样,宽列存储的模式是灵活的。呼,我还以为图模型很复杂呢!
宽列数据库的一些例子有 Cassandra 和 HBase。

MongoDB 入门

好了,就是这样。我必须承认,我实际上还没有使用过它们中的任何一个,但我肯定计划更深入地研究它们。而且实际上,正如承诺的那样,我现在就要尝试一个!我选择了 MongoDB,它是目前发展最快的数据库之一。它是一个文档存储,因此比其他类型具有更广泛的适用性。你可以在 www.mongodb.org 下载免费版本。上面也有很多文档,所以我建议你稍后多看看。安装非常简单。只需点击几次下一步即可安装。如果你更改了任何设置,如果它无法工作或你无法理解本文的其余部分,我概不负责。所以,继续吧,我等你。

准备好了吗?安装 MongoDB 后,你需要运行它。我有点惊讶它默认情况下不像(例如)SQL Server 那样作为服务运行。

那么,如何启动 MongoDB 呢?打开一个命令窗口(是的,真的)。首先,你需要创建 MongoDB 存储文件的 `` 目录。默认是 `\data\db`,要创建它,在你的命令窗口中输入 `md \data\db`。接下来,你需要导航到你安装 MongoDB 的文件夹。对我来说,这是 `C:\Program Files\MongoDB 2.6 Standard\bin`。然后启动 `mongod.exe`。如果你像我一样,从未在命令窗口中工作过,这里是你需要在命令窗口中输入的内容:

cd C:\
md data\db
cd C:\Program Files\MongoDB 2.6 Standard\bin
mongod.exe

如果你仍然遇到问题或者你没有运行 Windows,你可以查看这个 MongoDB 安装教程。它还解释了如何将 MongoDB 作为服务运行,因此是推荐阅读材料!

你可能想知道 MongoDB 是否有一个管理系统,我们可以在其中查询和编辑数据,而无需编程语言。你可以使用命令窗口向 MongoDB 数据库发出 JavaScript 命令。为此,你需要通过命令窗口启动 `mongo.exe`。MongoDB 入门页面更详细地解释了这一点。但是,我强烈建议你下载 MongoVUE。它是一个易于使用的图形化 MongoDB 管理系统。帮自己一个忙,在继续阅读之前安装它。你可以查看我们将在下一段中插入和编辑的数据。

在我们继续之前还有一件事。Mongo 将其文档存储为 BSON,它代表二进制 JSON。现在并不重要,但知道这一点很好。我们会看到一些名为 Bson* 的类,现在你知道它从何而来了。MongoVUE 允许你以 JSON 格式查看你存储的文档。

MongoDB 的 C# 端

既然 MongoDB 已经运行,请在 Visual Studio 中启动一个新的 C# 控制台项目。确保你已保存项目(只需将其命名为 `MongoDBTest` 或其他)。现在打开包管理器控制台,它可以在菜单中的“工具 -> 库包管理器 -> 包管理器控制台”下找到。让 MongoDB 在你的项目中工作就像输入以下命令一样简单:`PM> Install-Package mongocsharpdriver`。MongoDB 驱动程序将自动安装并添加到你的项目中。确保将以下命名空间导入到你的文件中:

using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using MongoDB.Driver.Builders;
using MongoDB.Driver.Linq;
using System;
using System.Linq;

那么,你准备好编写一些代码了吗?首先,我们需要一些要存储在数据库中的东西,比如说一个 `Person`。我创建了以下类以便我们开始时使用。

public class Person
{
    public ObjectId Id { get; set; }
    public string Name { get; set; }
}

类再简单不过了。请注意,我在 `Id` 字段使用了 `ObjectId`。为 ID 字段使用此类型会使 Mongo 为你生成一个 ID。你可以使用任何类型作为 ID 字段,但你需要自己将其设置为唯一值(否则你将覆盖已具有该 ID 的记录)。另一个注意事项是你需要将你的 ID 字段命名为 Id(区分大小写)或用 `BsonIdAttribute` 进行标注。既然我们谈论属性,这里还有另一个很快会派上用场的属性,`BsonIgnoreAttribute`。带有该属性的属性将不会持久化到存储中。

public class Person
{
    [BsonId()]
    public ObjectId MyID { get; set; }
    public string Name { get; set; }
    [BsonIgnore()]
    public string NotPersisted { get; set; }
}

目前,我们将使用默认的 Id 字段。现在让我们连接到我们的实例并创建一个数据库。你会发现这实际上相当容易。无论何时你向其中放入数据,Mongo 都会自动创建一个数据库。连接到数据库后,我们希望将一些数据放入该数据库中。更具体地说,我们想要创建一个 `Person` 并存储它。为此,我们首先会请求一个具有特定名称的 `Persons` 集合(如果你愿意,可以将其视为表名)。如果你对集合使用不同的名称,则可以存储多个 `Persons` 集合,所以请注意拼写错误!从数据库获取集合后,我们将创建一个 `Person` 并将其保存到数据库中。这些事情一下子很多,但实际上代码非常简单,你无论如何都会明白的!

// Connect to the database.
string connectionString = "mongodb://";
MongoClient client = new MongoClient(connectionString);
MongoServer server = client.GetServer();
MongoDatabase database = server.GetDatabase("testdb");

// Store a person.
MongoCollection persons = database.GetCollection("person");
Person p1 = new Person() { Name = "Sander" };
persons.Save(p1);
Console.WriteLine(p1.Id.ToString());
Console.ReadKey();

哇,那真是太简单了,不是吗!?Mongo 为你生成了一个 ID,正如你所看到的。接下来,我们将从数据库中取回这个 `Person`。有几种方法可以做到这一点。我们可以使用 Mongo API,也可以使用 LINQ。两者都提供了查询一个或多个记录的多种方法。我建议你阅读文档并进行一些实验。我将向你展示几种从数据库中取回我们的 `Person` 的方法。

// Using the MongoDB API.
ObjectId id = p1.Id;
Person sanderById = persons.FindOneById(id);
Person sanderByName = persons.FindOne(Query.EQ(p => p.Name, "Sander"));

// Using LINQ.
var sandersByLinq = from p in persons.AsQueryable()
                    where p.Name == "Sander"
                    select p;
Person sander = sandersByLinq.SingleOrDefault();

你会注意到 `Query.EQ`。`EQ` 代表 equal(等于),它构建了一个查询,用于测试字段是否等于特定值。还有其他查询类型,如 `GT`(大于)、`LT`(小于)、`In`、`Exists` 等。

但是等等,我一点也不满意这段代码!`Person` 真正需要的是 `LastName` 和 `Age` 字段。现在,我要说的灵活的模式来了。只需将属性添加到你的类中。如果你获取一个没有指定这些字段的 `Person`,它们将被设置为默认值。如果是 `Age`,你可能想使用 `int?` 而不是 `int`,否则你已有的 `Persons` 将会有 `0` 的年龄而不是 `null`。

Person incompleteSander = persons.FindOne(Query.EQ(p => p.Name, "Sander"));
Console.WriteLine(String.Format("{0}'s last name is {1} and {0}'s age is {2}",
    incompleteSander.Name, incompleteSander.LastName, incompleteSander.Age.ToString()));

incompleteSander.LastName = "Rossel";
incompleteSander.Age = 27;

// Let's save those new values.
persons.Save(incompleteSander);

Console.ReadKey();
// Retrieve the person again, but this time with last name and age.
Person completeSander = persons.FindOne(Query.EQ(p => p.Name, "Sander"));
Console.WriteLine(String.Format("{0}'s last name is {1} and {0}'s age is {2}",
    completeSander.Name, completeSander.LastName, completeSander.Age.ToString()));

Console.ReadKey();

现在我们还为 `Person` 添加一个 `address`。`Address` 将是一个新类,而 `Person` 将持有一个对 `Address` 的引用。现在你可以像往常一样对其进行建模。

public class Person
{
    public ObjectId Id { get; set; }
    public string Name { get; set; }
    public string LastName { get; set; }
    public int? Age { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public string AddressLine { get; set; }
    public string PostalCode { get; set; }
}

注意 `Address` 不需要 `Id` 字段吗?那是因为它是 `Person` 的一个子文档,它不能独立于 `Person` 存在,因此不需要 `Id` 来使其唯一。现在从数据库中获取你已有的 `Person`,检查它的地址是否为空,创建一个地址,保存它并再次获取它。

Person addresslessSander = persons.FindOne(Query.EQ(p => p.Name, "Sander"));
if (addresslessSander.Address != null)
{
    Console.WriteLine(String.Format("Sander lives at {0} on postal code {1}", 
        addresslessSander.Address.AddressLine, addresslessSander.Address.PostalCode));
}
else
{
    Console.WriteLine("Sander lives nowhere...");
}

addresslessSander.Address = new Address() { AddressLine = "Somewhere", PostalCode = "1234 AB" };
persons.Save(addresslessSander);

Person addressSander = persons.FindOne(Query.EQ(p => p.Name, "Sander"));
if (addressSander.Address != null)
{
    Console.WriteLine(String.Format("Sander lives at {0} on postal code {1}", 
          addressSander.Address.AddressLine, addressSander.Address.PostalCode));
}
else
{
    Console.WriteLine("Sander lives nowhere...");
}

Console.ReadKey();

请务必查看 MongoVUE 中的 JSON。还可以尝试使用类列表进行实验。例如,尝试添加更多地址。我们也没有删除或更新任何记录,我们只是覆盖了整个条目。进行实验并阅读文档。

我们现在已经触及了 NoSQL,特别是 MongoDB 的表面。当然,MongoDB 还有很多功能,但我希望这篇博文能帮助你在 NoSQL 和 MongoDB 中入门。也许它给了你开始所需的一点动力。对我来说确实如此。期待未来有更多关于 NoSQL 的博客!

其他阅读

正如承诺的那样,这里有一些额外的阅读材料:

欢迎评论。祝您编程愉快!

文章 NoSQL 和 MongoDB 初探 最初发布于 Sander 的点滴

NoSQL 和 MongoDB 初探 - CodeProject - 代码之家
© . All rights reserved.