MongoDB 和 C#






4.90/5 (66投票s)
使用 C# 操作 MongoDB。
引言
很有可能你已经使用过关系型数据库,并且对它相当满意。我知道我曾经是。无论是 SQL Server 还是 MySQL,我都深知如何高效地使用我的工具来推送、拉取和转换我需要的数据。当我们坐下来分析一个项目时,我们会争论语言、服务器等等,但我们从来不讨论哪种类型的数据库最适合这个问题。我们总是假设我们会使用关系型数据库。但这种选择可能会带来很多问题。我不仅需要了解我偏好的语言(C#、Ruby),还需要了解 SQL。此外,关系型结构和我精心构建的领域模型之间存在一个已知的 阻抗不匹配。虽然我确实使用 OR/M(nHibernate、LINQ to SQL 等),但这只是让我(大多数时候)忽略了这个问题。
那么我到底想说什么呢?嗯,还有其他类型的数据库存在。像 db4o 这样的对象数据库和其他非关系型数据库,如 Casandra 或 Amazon 的 SimpleDB,提供了一些缓解,但没有一个真正解决了我的问题。我需要一些跨语言的东西,同时也要支持对象模型。我需要一些高度可扩展但仍然快速的东西。
结果发现,还有另一种类型的数据库。一种 面向文档的数据库。在我环顾四周时,有许多符合要求的。CouchDB、RavenDB 和 MongoDB 是最值得注意的。我选择了 MongoDB,因为它持续为我提供了其他两者所不具备的动态查询能力。
在这篇文章中,我想帮助你开始使用 MongoDB,从安装到你的第一个项目。此外,我还会介绍一些关于 LINQ 提供程序的内容,让你自己去玩一玩。
获取 MongoDB
MongoDB 网站的顶部有一个下载链接。你应该下载并安装与你的系统相关的版本。在撰写本文时,我正在使用版本 1.4.2。
下载后,你可以简单地将其解压缩到你机器上的任意位置。我将其解压缩到 c:\Program Files\MongoDB。此外,你还需要手动创建一个目录 c:\data\db。这是 MongoDB 存储其文件的默认位置。最后,如果你回到 MongoDB 目录,可以通过执行 mongod.exe(对于 Windows 用户)来运行服务器。
此时,你正在本地主机上运行 MongoDB 服务器,端口为 27017。这些是默认设置,对于我们的测试项目来说完全足够。
有一个 shell 你可以运行来随意摆弄数据库。我在这里就不深入介绍了,但如果你点击 MongoDB 网站上的“Try It Out”,就可以找到一个教程。请注意,这里的语法不是 C#,而是 JavaScript。
MongoDB 简介
我们将创建一个简单的测试项目来模拟一个博客系统。这是一个相当容易理解的模式,所以我不会详细介绍对象模型。下面展示了我们将要使用的实体模型。
public class Post
{
public Oid Id { get; private set; }
public string Title { get; set; }
public string Body { get; set; }
public int CharCount { get; set; }
public IList<Comment> Comments { get; set; }
}
public class Comment
{
public DateTime TimePosted { get; set; }
public string Email { get; set; }
public string Body { get; set; }
}
经验丰富的 OR/M 用户会注意到 Comment
上没有标识符。这是因为这些实体没有自己的表。它们是 Post 文档的一部分,并存储为数组。换句话说,当我们获取一个 Post 时,我们会检索所有相关的评论,而无需做任何特殊的事情。这就是介绍中提到的阻抗不匹配消失的地方。现在,我的实体模型和存储在 MongoDB 中的模型之间没有区别。
MongoDB 以 BSON(二进制 JSON)格式存储其数据。每个服务器有许多数据库,每个数据库有许多集合。你可以将集合想象成关系型存储中的表。在我们上面的例子中,我们只需要一个集合来建模我们的数据。我们暂时将其命名为“Post”,与我们的类名保持一致。
如果我们从 shell(在插入一些数据后)查询 Post 集合,我们会看到返回的 JSON 代表我们的数据。在我们的例子中,blog 将是我们的数据库名称。下面是这些数据的一个示例。
//blog is our database name.
//shell commands
use blog
db.Post.find()
//results
{ _id: ObjectId("4be05365340d000000002554"),
Title: "My First Post",
Body: "This isn't a very long post.",
CharCount: 28,
Comments: [
{ TimePosted: "Fri Jan 01 2010 00:00:00 GMT-0600 (Central Standard Time)",
Email: "bob_mcbob@gmail.com",
Body: "This article is too short!"
},
{ TimePosted: "Fri Jan 02 2010 00:00:00 GMT-0600 (Central Standard Time)",
Email: "Jane.McJane@gmail.com",
Body: "I agree with Bob."
}
]
}
关于上述结果有几点需要注意。首先,_id
是我们的标识符。虽然我认为你可能已经猜到了,但你可能不知道其中的一些细节。如果你不提供 _id
,它将自动生成。由于我在创建此记录时没有提供,因此使用了 `ObjectId` 这种类型的对象。MongoDB 网站上有关于此类型的文档,并且它得到了所有客户端实现(包括 MongoDB-CSharp)的完全支持。MongoDB-CSharp 还允许你指定自己的标识符,或者你可以使用其他类型的自动生成标识符,如 GUID。其次,请注意 comments 如何存储为数组并直接嵌入在 Post 文档中。正如我之前提到的,我们无需执行连接即可获取关于某个帖子所需的所有信息,它已经包含在文档中了。
C# 项目
好了,闲话少说。让我们来构建我们的项目。随附的示例项目包含 MongoDB.dll 以供引用。如果你想自己下载,项目地址是 http://github.com/samus/mongodb-csharp/downloads。我在 libs 文件夹中包含了 .90 beta 1 DLL。市面上还有许多其他的 C# 驱动程序,但到目前为止,我认为这个驱动程序提供的功能最为成熟。坦白说,我有所偏颇,因为我参与了项目的编写。任何早期版本都不会有这个版本所具有的 LINQ 支持或配置支持。此外,你可以在上述地址查阅 wiki 以获取示例和其他文档。
因此,到目前为止,我创建了一个名为 MongoDB Blog 的新 VS2008 解决方案,其中一个项目我称之为 MongoDBBlog.Tester。Tester 是一个控制台应用程序。此外,我还添加了对我们之前从 github 下载的 MongoDB.dll 的引用。
最后,上代码!!!
首先,我们需要保存一些帖子。
using MongoDB;
using MongoDB.Linq;
//etc...
//Create a default mongo object. This handles our connections to the database.
//By default, this will connect to localhost,
//port 27017 which we already have running from earlier.
var mongo = new Mongo();
mongo.Connect();
//Get the blog database. If it doesn't exist, that's ok because MongoDB will create it
//for us when we first use it. Awesome!!!
var db = mongo.GetDatabase("blog");
//Get the Post collection. By default, we'll use
//the name of the class as the collection name. Again,
//if it doesn't exist, MongoDB will create it when we first use it.
var collection = db.GetCollection<Post>();
//this deletes everything out of the collection so we can run this over and over again.
collection.Delete (p => true);
//Create a Post to enter into the database.
var post = new Post()
{
Title = "My First Post",
Body = "This isn't a very long post.",
CharCount = 27,
Comments = new List<Comment>
{
{ new Comment() { TimePosted = new DateTime(2010,1,1),
Email = "bob_mcbob@gmail.com",
Body = "This article is too short!" } },
{ new Comment() { TimePosted = new DateTime(2010,1,2),
Email = "Jane.McJane@gmail.com",
Body = "I agree with Bob." } }
}
};
//Save the post. This will perform an upsert. As in, if the post
//already exists, update it, otherwise insert it.
collection.Save(post);
太好了。现在我们的数据库里有一个帖子了,但那些敏锐的读者会注意到我数错了。我不应该硬编码那个值,但那样我就没有理由向你们展示如何更新了。上面的实际字符数是 28,而不是 27。没关系,很容易更新。
//Get the first post that is not matching correctly...
var post = collection.Linq().First(x => x.CharCount != x.Body.Length);
post.CharCount = post.Body.Length;
//this will perform an update this time because we have already inserted it.
collection.Save(post);
好的,很好。一切又恢复正常了。此时,我们可以继续查询我们的帖子了。在附件的代码中,我到目前为止已经录入了三篇帖子,以便我们的查询有一些内容。
LINQ 完全支持到 MongoDB 的限制。投影、Where
子句和排序都是我们 LINQ 支持的一部分。但是,连接(Joins)不支持。这有很多原因,可以追溯到一致性,但我们不支持连接是因为 MongoDB 本身也不支持连接。下面,我们将看到一些简单的查询。
//count all the Posts
var totalNumberOfPosts = collection.Count();
//count only the Posts that have 2 comments
var numberOfPostsWith2Comments =
collection.Count(p => p.Comments.Count == 2);
//find the titles of the posts that Jane commented on...
var postsThatJaneCommentedOn =
from p in collection.Linq()
where p.Comments.Any(c => c.Email.StartsWith("Jane"))
select p.Title;
//find the titles and comments of the posts
//that have comments after January First.
var postsWithCommentsAfterJanuary1st = from p in collection.Linq()
where p.Comments.Any(c => c.TimePosted >
new DateTime(2010, 1, 1))
select new { Title = p.Title,
Comments = p.Comments };
//find posts with less than 40 characters
var postsWithLessThan40Chars = from p in collection.Linq()
where p.CharCount < 40
select p;
正如你所看到的,MongoDB 的查询能力是令人满意的。我们可以随时获得我们想要的东西。我们甚至可以使用 MongoDB 的 Map-Reduce 功能进行一些聚合。
MongoDB 使用 Map-Reduce 来执行可扩展的数据聚合和汇总。这通常需要编写一些 JavaScript 来实现。下面是一个使用 JavaScript 来汇总我们字数的示例,首先是使用 JavaScript,然后是使用我们的 LINQ 提供程序的自动转换。
//Manual map-reduce
var sum = Convert.ToInt32(collection.MapReduce()
.Map(new Code(@"
function() {
emit(1, this.CharCount);
}"))
.Reduce(new Code(@"
function(key, values) {
var sum = 0;
values.forEach(function(prev) {
sum += prev;
});
return sum;
}"))
.Documents.Single()["value"]);
//Using Linq to automatically build the above query. Awesome!!!
var linqSum = collection.Linq().Sum(p => p.CharCount);
//Now imagine about doing this by hand...
var stats = from p in collection.Linq()
where p.Comments.Any(c => c.Email.StartsWith("bob"))
group p by p.CharCount < 40 into g
select new
{
LessThan40 = g.Key,
Sum = g.Sum(x => x.CharCount),
Count = g.Count(),
Average = g.Average(x => x.CharCount),
Min = g.Min(x => x.CharCount),
Max = g.Max(x => x.CharCount)
};
摘要
希望你已经看到了入门有多么容易。还有很多我们可以讨论的内容,从正确使用文档数据库的方式到 CAP 定理及其适用性。我鼓励你自己深入研究一些想法。另外,请随意玩玩示例项目。添加一些字段,尝试一些操作。它仍在开发中,因此某些 LINQ 功能可能无法按预期工作。请告知我们,我们将尽力解决。
我们有一个 Google 群组供用户前来提问,地址是 http://groups.google.com/group/mongodb-csharp。来参与吧,如果你愿意,可以下载源代码并开始帮助我们。我们真心希望它能成为 .NET 社区一个很棒的库,它依赖于反馈和贡献。
参考文献
- MongoDB: https://mongodb.ac.cn
- MongoDB-CSharp: http://github.com/samus/mongodb-csharp/downloads
- MongoDB-CSharp Group: http://groups.google.com/group/mongodb-csharp