在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
原文地址:http://www.cnblogs.com/fish-li/archive/2011/06/26/2090800.html
【目标】:本文将以实战的形式,向您展示如何用C#访问MongoDB,完成常见的数据库操作任务, 同时,也将介绍MongoDB的客户端(命令行工作模式)以及一些基础的命令。 【说明】:MongoDB是什么?有什么用?如果不清楚这些问题的,请自己google一下吧。 【适合对象】:完全没有接触MongoDB或对MongoDB有一点了解的C#开发人员。因此本文是一篇入门级的文章。 【示例项目】:本文的完整示例是一个简单的【客户,商品,订单】业务场景,预览界面效果请点击此处(但并不完全相同),也包含下载示例项目的源码。 让我们开始MongoDB的实战入门吧。 您可以在这个地址下载到MongoDB: http://www.mongodb.org/downloads, 本文将以【mongodb-win32-i386-1.8.2-rc2】来演示MongoDB的使用。 下载好了吗?我们继续吧。请解压缩您刚才下载的MongoDB的zip压缩包,进入解包的bin目录,会发现有一堆exe文件。 现在,请打开命令行窗口并切换到刚才的bin目录,然后输入以下命令: 这里,我运行了程序mongod.exe,并告诉它数据文件的保存目录(这个目录要事先创建好),至于mongod的更多命令行参数,请输入命令: mongod /? 来获得。 顺便提一下:运行mongod后,它会显示一些有用的信息。比如:pid (进程ID), tcp port (监听端口), http 端口(用于查看运行状态), 操作系统版本,数据目录,32 or 64位版本,如果是32位版本,它还会告诉你【数据有2G的限制】,所以正式使用建议运行64位版本。 接下来,我们还要去下载MongoDB的C#驱动,它可以让我们在C#中使用MongoDB 。下载地址:https://github.com/samus/mongodb-csharp 在C#使用MongoDB好了,有了前面的准备工作,我们可以开始在C#中使用MongoDB了。不过,由于本示例项目的代码也不少,因此本文将只展示与MongoDB交互的相关代码, 更完整的代码请自行查阅示例项目。 接下来,本文演示通过C#完成【客户资料】的一些基本的数据操作,还是先来定义一个客户资料的类型吧。 public sealed class Customer { [MongoId] public string CustomerID { get; set; } public string CustomerName { get; set; } public string ContactName { get; set; } public string Address { get; set; } public string PostalCode { get; set; } public string Tel { get; set; } } 说明:这就是一个简单的类,而且代码中的[MongoId]也是可以不要的(这个后面再说)。 在操作数据库之前,我要说明一下:MongoDB在使用前,并不要求您事先创建好相应的数据库,设计数据表结构! 再来定义二个变量: private static readonly string _connectionString = "Server=127.0.0.1"; private static readonly string _dbName = "MyNorthwind"; 新增记录public void Insert(Customer customer) { customer.CustomerID = Guid.NewGuid().ToString("N"); // 首先创建一个连接 using( Mongo mongo = new Mongo(_connectionString) ) { // 打开连接 mongo.Connect(); // 切换到指定的数据库 var db = mongo.GetDatabase(_dbName); // 根据类型获取相应的集合 var collection = db.GetCollection<Customer>(); // 向集合中插入对象 collection.Insert(customer); } } 上面的代码中,每一行都有注释,这里就不再解释了。 删除记录public void Delete(string customerId) { using( Mongo mongo = new Mongo(_connectionString) ) { mongo.Connect(); var db = mongo.GetDatabase(_dbName); var collection = db.GetCollection<Customer>(); // 从集合中删除指定的对象 collection.Remove(x => x.CustomerID == customerId); } } 更新记录public void Update(Customer customer) { using( Mongo mongo = new Mongo(_connectionString) ) { mongo.Connect(); var db = mongo.GetDatabase(_dbName); var collection = db.GetCollection<Customer>(); // 更新对象 collection.Update(customer, (x => x.CustomerID == customer.CustomerID)); } } 获取记录列表public List<Customer> GetList(string searchWord, PagingInfo pagingInfo) { using( Mongo mongo = new Mongo(_connectionString) ) { mongo.Connect(); var db = mongo.GetDatabase(_dbName); var collection = db.GetCollection<Customer>(); // 先创建一个查询 var query = from customer in collection.Linq() select customer; // 增加查询过滤条件 if( string.IsNullOrEmpty(searchWord) == false ) query = query.Where(c => c.CustomerName.Contains(searchWord) || c.Address.Contains(searchWord)); // 先按名称排序,再返回分页结果. return query.OrderBy(x => x.CustomerName).GetPagingList<Customer>(pagingInfo); } } 获取单个对象public Customer GetById(string customerId) { using( Mongo mongo = new Mongo(_connectionString) ) { mongo.Connect(); var db = mongo.GetDatabase(_dbName); var collection = db.GetCollection<Customer>(); // 查询单个对象 return collection.FindOne(x => x.CustomerID == customerId); } } 重构(简化)代码从上面代码可以看出,操作MongoDB大致都是这样一个操作过程。 // 首先创建一个连接 using( Mongo mongo = new Mongo(_connectionString) ) { // 打开连接 mongo.Connect(); // 切换到指定的数据库 var db = mongo.GetDatabase(_dbName); // 根据类型获取相应的集合 var collection = db.GetCollection<Customer>(); // 【访问collection,做你想做的操作】 } 针对这个问题,我提供一个包装类来简化MongoDB的使用。点击此处折叠 /// <summary> /// 对Mongo和MongoDatabase的包装类 /// </summary> public class MyMongoDb : IDisposable { private Mongo _mongo; private IMongoDatabase _db; /// <summary> /// 默认构造函数。 /// 为了本程序方便使用,直接使用二个固定的参数。 /// 采用MongoDb的默认连接字符串,连接MyNorthwind数据库。 /// </summary> public MyMongoDb() : this("Server=127.0.0.1", "MyNorthwind") { } /// <summary> /// 构造函数。根据指定连接字符串和数据库名 /// </summary> /// <param name="connectionString">连接字符串</param> /// <param name="dbName">数据库名,可为空,但必须在任何操作数据库之前要调用UseDb()方法</param> public MyMongoDb(string connectionString, string dbName) { if( string.IsNullOrEmpty(connectionString) ) throw new ArgumentNullException("connectionString"); _mongo = new Mongo(connectionString); // 立即连接 MongoDB _mongo.Connect(); if( string.IsNullOrEmpty(dbName) == false ) _db = _mongo.GetDatabase(dbName); } /// <summary> /// 切换到指定的数据库 /// </summary> /// <param name="dbName"></param> /// <returns></returns> public IMongoDatabase UseDb(string dbName) { if( string.IsNullOrEmpty(dbName) ) throw new ArgumentNullException("dbName"); _db = _mongo.GetDatabase(dbName); return _db; } /// <summary> /// 获取当前连接的数据库 /// </summary> public IMongoDatabase CurrentDb { get { if( _db == null ) throw new Exception("当前连接没有指定任何数据库。请在构造函数中指定数据库名或者调用UseDb()方法切换数据库。"); return _db; } } /// <summary> /// 获取当前连接数据库的指定集合【依据类型】 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public IMongoCollection<T> GetCollection<T>() where T : class { return this.CurrentDb.GetCollection<T>(); } /// <summary> /// 获取当前连接数据库的指定集合【根据指定名称】 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="name">集合名称</param> /// <returns></returns> public IMongoCollection<T> GetCollection<T>(string name) where T : class { return this.CurrentDb.GetCollection<T>(name); } public void Dispose() { if( _mongo != null ) { _mongo.Dispose(); _mongo = null; } } } 简化后的CRUD代码如下: public void Insert(Customer customer) { customer.CustomerID = Guid.NewGuid().ToString("N"); using( MyMongoDb mm = new MyMongoDb() ) { mm.GetCollection<Customer>().Insert(customer); } } public void Delete(string customerId) { using( MyMongoDb mm = new MyMongoDb() ) { mm.GetCollection<Customer>().Remove(x => x.CustomerID == customerId); } } public void Update(Customer customer) { using( MyMongoDb mm = new MyMongoDb() ) { mm.GetCollection<Customer>().Update(customer, (x => x.CustomerID == customer.CustomerID)); } } public Customer GetById(string customerId) { using( MyMongoDb mm = new MyMongoDb() ) { return mm.GetCollection<Customer>().FindOne(x => x.CustomerID == customerId); } } 看了上面这些代码,您应该会觉得MongoDB的使用也很容易,对吧。 到这里,你或许想知道:MongoDB有没有一个自己的客户端来查看数据呢?因为总不能只依赖自己写代码来查看数据吧? 使用MongoDB的客户端查看数据MongoDB自带一个Javascript shell,它可以从命令行与MongoDB实例交互。这个shell非常有用,通过它可以管理操作、检查运行实例、查询数据等操作。 这就是MongoDB的客户端的命令行模式了。通常我们在操作数据库前,要切换【当前数据】, 注意:MongoDB区分名字的大小写。 在MongoDB中,查看【当前数据库】,可以使用命令【db】, 嗯,怎么有乱码?CustomerId = 1 的记录应该是这样的才对呀? 看到这一幕,您应该不要怀疑是MongoDB的错了,这个错误是由于MongoDB的客户端使用的编码是UTF-8, 而Windows 命令行程序 cmd.exe 使用的gb2312(我目前使用中文语言) 造成的。 再运行 mongo 进入mongo命令行,切换数据库,并执行命令:【db.Customer.findOne({"_id" : "1"});】 现在可以看到汉字能正常显示了,但最后却显示"Failed to write to logfile",对于这个问题,我们如果执行命令 【db.Customer.findOne({"_id" : "91"});】(id=91的记录就是我最后录入的,全是a的那条,前面截图上有), 可以发现没有任何异常发生,因此认为这个问题还是和cmd.exe有关的。 如果切换回 chcp 936 ,这时将看到乱码,但没有"Failed to write to logfile",所以我将忽略这个错误。 使用MongoDB的客户端维护数据下面我来演示一下如何使用MongoDB的客户端来执行一些基本的数据维护功能。还是从CRUD操作开始吧,请看下图,为了方便,我将在一个截图中执行多个命令。 在上面的示例中,每个命令后,我加了一个红圈。在示例中,我先切换到 MyTest 数据库(它并不存在,但没关系), 然后我定义了一个文档 item 并插入到集合 table1 中,然后又定义了一个文档 item2,也插入到集合 table1 中。 注意:item , item2 的结构完全不同,但能放在一个集合中(不建议这样做)。最后调用 find() 显示集合中的所有文档。 此时,你可以看看前二张图片,可以发现:在定义Customer类时,有一个成员CustomerID此时却不存在! 我们可以再看一下Customer的定义: public sealed class Customer { [MongoId] public string CustomerID { get; set; } public string CustomerName { get; set; } public string ContactName { get; set; } public string Address { get; set; } public string PostalCode { get; set; } public string Tel { get; set; } } 此时,您应该发现CustomerID这个成员有一个[MongoId]的特性。正是由于这个特性,驱动程序将把CustomerID映射为"_id"来使用。 好了,再次回到命令行,我要演示其它的命令。请看下图: 为了要更新某个文档,我们要使用findOne()方法找到要修改的文档对象,并将它保存一个变量t中,然后,修改它的属性, 接着调用update()方法就可以更新文档了,注意在调用update()时,第一个参数【更新范围】是采用文档的形式给出的, 第二参数才是要更新的新对象。在删除时,删除条件也是采用文档的形式指定的。处处使用文档,这就是MongoDB的特色。 前面的示例代码中,我使用了find()和findOne(),它们是有区别的:findOne()只返回一个文档对象,find()返回一个集合列表, 如果不指定过滤范围,它将返回整个集合,但在客户端中最多只显示前20个文档。 再来个复杂的查询:搜索日期范围是 2011-06-25 到 2011-06-26 之间的订单记录。由于返回的结果太长,我的截图将不显示它们。 如果遇到 or 就更麻烦了,如:CustomerId = 1 or CustomerId = 2 ,有二种写法: 语法不难,相信能看懂JSON的人,也能看懂这二条命令。 再来个分页的命令: 与LINQ的语法类似,好理解。 MongoDB客户端还支持其它的语法,这里就不一一介绍了。因为我们的目标是在C#中使用MongoDB,在MongoDB提供的C#驱动中, 我们并不需要写那样麻烦的查询条件,只需要按LINQ的语法写查询就可以了,因此会很容易使用。 不过,有些维护性的操作,我们只能通过命令的方式去执行,比如:删除集合,删除数据库。 执行【db.runCommand({"drop" : "table1"});】便可以删除一个集合,也可以执行命令【db.table1.drop();】,二者的效果是一样的。 注意:命令【db.runCommand({"dropDatabase": 1});】只能删除【当前数据库】,所以要先切换当前数据库, 然后执行这个命令,执行删除数据库的命令后,我们再用命令【show dbs;】,发现数据库【MyTest】已不存在,即删除成功。 删除数据库还有一个方法:还记得我前面启动mongod.exe时给它传递了一个参数 【-dbpath "H:\AllTempFiles\mongodb\data"】吗? 我们现在去那个目录看一下有什么东西。 看了这张图,您有没有想过:这二个以【MyNorthwind】开头的文件会不会就是数据库的文件呢? 我现在就删除看看,先停止mongod.exe,然后删除文件(由于我目前只有一个数据库,我把目录下的文件全删除了),删除后: 现在,我再来启动mongod.exe,然后在客户端执行命令【show dbs;】看看: 现在可以发现我们之前的【MyNorthwind】数据库没有了,当然也就是删除了。 说明一下:现在这二个数据库,是MongoDB自带的,用于特殊用途的,我们可以不理会它们。 好了,我们还是再来看看MongoDB提供的C#驱动提供了什么东西吧。 MongoDB提供的C#驱动我把MongoDB提供的C#驱动中认为比较重要的类做了个截图: 再来看看我前面给出一段操作MongoDB的代码: // 首先创建一个连接 using( Mongo mongo = new Mongo(_connectionString) ) { // 打开连接 mongo.Connect(); // 切换到指定的数据库 var db = mongo.GetDatabase(_dbName); // 根据类型获取相应的集合 var collection = db.GetCollection<Customer>(); // 【访问collection,做你想做的操作】 } 这段代码大致也说明了在C#中操作MongoDB的一个过程,主要涉及上图中的前三个类,这三个类也是最核心的类。 这里值得一提的是:LinqExtensions.Linq()方法可以让我们在写查询时, 方便地使用LINQ的优雅语法,而不是一堆复杂的文档条件!这也是我选择这个驱动的原因。 还记得我前面举过几个在命令行中调用runCommand的示例吗?如果在C#中也需要执行这样的操作,可以调用MongoDatabase.SendCommand()方法。比如:删除集合Category,我们可以写成: void DeleteCategoryCollection() { using( MyMongoDb mm = new MyMongoDb() ) { mm.CurrentDb.SendCommand(new Document("drop", "Category")); } } 【MongoIdAttribute】:可以让我们将一个C#类的数据成员映射到文档的"_id"属性。前面有示例说明,这里就不再多说了。 【MongoAliasAttribute】:可以让我们将一个C#类的数据成员在映射到文档时采用其它的属性名称。 [MongoAlias("CName")] public string CustomerName { get; set; } 【MongoIgnoreAttribute】:可以让一个C#类在保存到MongoDB时,忽略某些成员。请看下面的代码: public sealed class OrderItem : MyDataItem { [MongoId] // 这个成员将映射到 "_id" public string OrderID { get; set; } [ScriptIgnore] // 在JSON序列化时,忽略这个成员 public DateTime OrderDate { get; set; } [MongoIgnore] // 在保存到MongoDB时,忽略这个成员 public string CustomerName { get; set; } // .... 还有其它的属性。 // 加这个属性仅仅为了在客户端中能更容易的显示,要不然,客户端处理格式转换实在是麻烦。 // 它将不会被写入到数据库。 [MongoIgnore] public string OrderDateText { get { return this.OrderDate.ToString("yyyy-MM-dd HH:mm:ss"); } } } MongoDB不支持在查询数据库时使用Join操作在MongoDB中,一个文档就是一个完整的对象,所以获取一个对象时,并不需要关系数据库的那种JOIN语法。 在上面定义的OrderItem中,CustomerName并没有保存到数据库,而是在加载时,采用了【引用】的设计方式, 根据CustomerID去访问集合Customer来获取对应的CustomerName ,这也算是JOIN的常见使用场景了。 示例项目中有一个需求:根据一个日期范围查询订单列表(支持分页)。我是这样实现这个查询操作的。 /// <summary> /// 根据指定的查询日期范围及分页参数,获取订单记录列表 /// </summary> /// <param name="dateRange">日期范围</param> /// <param name="pagingInfo">分页参数</param> /// <returns>订单记录列表</returns> public List<OrderItem> Search(QueryDateRange dateRange, PagingInfo pagingInfo) { dateRange.EndDate = dateRange.EndDate.AddDays(1); using( MyMongoDb mm = new MyMongoDb() ) { var collection = mm.GetCollection<OrderItem>(STR_Orders); var query = from ord in collection.Linq() where ord.OrderDate >= dateRange.StartDate && ord.OrderDate < dateRange.EndDate orderby ord.OrderDate descending select new OrderItem { OrderID = ord.OrderID, CustomerID = ord.CustomerID, OrderDate = ord.OrderDate, SumMoney = ord.SumMoney, Finished = ord.Finished }; // 获取订单列表,此时将返回符合分页的结果。 List<OrderItem> list = query.GetPagingList<OrderItem>(pagingInfo); // 获取订单列表中所有的客户ID string[] cids = (from ord in list where string.IsNullOrEmpty(ord.CustomerID) == false select ord.CustomerID) .Distinct() .ToArray(); // 找到所有客户记录 Dictionary<string, Customer> customers = (from c in mm.GetCollection<Customer>().Linq() where cids.Contains(c.CustomerID) select new Customer { CustomerID = c.CustomerID, CustomerName = c.CustomerName }) .ToDictionary(x => x.CustomerID); // 为订单列表结果设置CustomerName foreach( OrderItem ord in list ) { Customer c = null; if( string.IsNullOrEmpty(ord.CustomerID) == false && customers.TryGetValue(ord.CustomerID, out c) ) ord.CustomerName = c.CustomerName; else ord.CustomerName = string.Empty; } return list; } } 获取MongoDB服务端状态我们再来看一下当时启动服务端的截屏吧: 注意最后一行,它告诉我们它有一个WEB接口,端口是 28017 ,现在我就去看看那是个什么样子的。 可以看到它提供了一些服务端的状态信息。 我们还可以通过访问【http://localhost:28017/_status】来获得以JSON方式的统计信息。 我们还可以通过运行客户端的命令【db.runCommand({"serverStatus" : 1});】来获取这些信息:
好了,就说到这里吧。接下来,您也可以写点代码尝试一下,或者下载我准备的示例项目参考一下。点击此处进入下载页面。 祝您使用MongoDB顺利。
二天时间写此文,希望能给您一点收获。如果认为此文对您有帮助,别忘了支持一下哦。 |
请发表评论