这是MongoDB的系列学习笔记的第一篇,主要介绍什么是非关系型数据库MongoDB,如何下载,去哪儿下载,又该怎么正确的安装等一系列问题。
最近开始学习非关系型数据库MongoDB,却在博客园上找不到比较系统的教程,很多资料都要去查阅英文网站,效率比较低下。本人不才,借着自学的机会把心得体会都记录下来,方便感兴趣的童鞋分享讨论。部分资源出自其他博客,旨将零散知识点集中到一起,如果有侵犯您的权利,请联系[email protected]。大部分内容均系原创,欢迎大家转载分享,但转载的同时别忘了注明作者和原文链接哦。
MongoDB是一个高性能,开源,无模式的文档型数据库,是当前NoSql数据库中比较热门的一种。它在许多场景下可用于替代传统的关系型数据库或键/值存储方式。Mongo使用C++开发。Mongo的官方网站地址是:http://www.mongodb.org/,读者可以在此获得更详细的信息。
小插曲:什么是NoSql? |
NoSql,全称是 Not Only Sql,指的是非关系型的数据库。下一代数据库主要解决几个要点:非关系型的、分布式的、开源的、水平可扩展的。原始的目的是为了大规模web应用,这场运动开始于2009年初,通常特性应用如:模式自由、支持简易复制、简单的API、最终的一致性(非ACID)、大容量数据等。NoSQL被我们用得最多的当数key-value存储,当然还有其他的文档型的、列存储、图型数据库、xml数据库等。 |
特点:
功能:
适用场合:
安装Mongo数据库:
在发布本文的时间官方提供的最新版本是:1.6.5 ,如果不做特殊声明,本教程所用的版本将会是这个版本。
注:官方下载地址:http://www.mongodb.org/downloads
配置Mongo服务端:
打开CMD窗口,按照如下方式输入命令:
> d:
> cd D:\MongoDB
> mongod –dbpath D:\MongoDB\data
配置成功后会看到如下画面:
在浏览器输入:http://localhost:27017/,可以看到如下提示:
You are trying to access MongoDB on the native driver port. For http diagnostic access, add 1000 to the port number
如此,MongoDB数据库服务已经成功启动了。
传统的关系数据库一般由数据库(database)、表(table)、记录(record)三个层次概念组成,MongoDB是由(database)、集合(collection)、文档对象(document)三个层次组成。MongoDB对于关系型数据库里的表,但是集合中没有列、行和关系概念,这体现了模式自由的特点。
MongoDB支持多种语言的驱动,在此我们只介绍C#的驱动。仅C#驱动都有很多种,每种驱动的形式大致相同,但是细节各有千秋,因此代码不能通用。比较常用的是官方驱动和samus驱动。samus驱动除了支持一般形式的操作之外,还支持linq方式操纵数据。各人比较喜欢这种方式。
官方驱动下载地址:https://github.com/mongodb/mongo-csharp-driver/downloads
samus驱动下载地址:https://github.com/samus/mongodb-csharp
本篇将从samus驱动入手讲解数据库访问,国际惯例,存取“Hello World!”。
在进行下述操作之前,请先确定MongoDB服务已经开启,不知道怎么开启服务,请看上篇。下载驱动,新建控制台项目,并添加对MongoDB.dll的引用,如果你下载的是驱动源码,编译一遍引用生成的DLL即可。
基本代码如下:
- //链接字符串
- string connectionString = "mongodb://localhost";
- //数据库名
- string databaseName = "myDatabase";
- //集合名
- string collectionName = "myCollection";
- //定义Mongo服务
- Mongo mongo = new Mongo(connectionString);
- //获取databaseName对应的数据库,不存在则自动创建
- MongoDatabase mongoDatabase = mongo.GetDatabase(databaseName) as MongoDatabase;
- //获取collectionName对应的集合,不存在则自动创建
- MongoCollection<Document> mongoCollection = mongoDatabase.GetCollection<Document>(collectionName) as MongoCollection<Document>;
- //链接数据库
- mongo.Connect();
- try
- {
- //定义一个文档对象,存入两个键值对
- Document doc = new Document();
- doc["ID"] = 1;
- doc["Msg"] = "Hello World!";
- //将这个文档对象插入集合
- mongoCollection.Insert(doc);
- //在集合中查找键值对为ID=1的文档对象
- Document docFind = mongoCollection.FindOne(new Document { { "ID", 1 } });
- //输出查找到的文档对象中键“Msg”对应的值,并输出
- Console.WriteLine(Convert.ToString(docFind["Msg"]));
- }
- finally
- {
- //关闭链接
- mongo.Disconnect();
- }
运行程序,成功打印helloword。同时,我们打开数据文件夹,发现多了两个文件“myDatabase.ns”和“myDatabase.0”。
看到下图,是通过Jqgrid实现表格数据的基本增删查改的操作。表格数据增删改是一般企业应用系统开发的常见功能,不过不同的是这个表格数据来源是非关系型的数据库MongoDB。nosql虽然概念新颖,但是MongoDB基本应用实现起来还是比较轻松的,甚至代码比基本的ADO.net访问关系数据源还要简洁。由于其本身的“非关系”的数据存储方式,使得对象关系映射这个环节对于MongoDB来讲显得毫无意义,因此我们也不会对MongoDB引入所谓的“ORM”框架。
下面我们将逐步讲解怎么在MVC模式下将MongoDB数据读取,并展示在前台Jqgrid表格上。这个“简易系统”的基本设计思想是这样的:我们在视图层展示表格,Jqgrid相关Js逻辑全部放在一个Js文件中,控制层实现了“增删查改”四个业务,MongoDB的基本数据访问放在了模型层实现。下面我们一步步实现。
首先,我们新建一个MVC空白项目,添加好jQuery、jQueryUI、Jqgrid的前端框架代码:
然后在Views的Home文件夹下新建视图“Index.aspx”,在视图的body标签中添加如下HTML代码:
- <div>
- <table id="table1">
- </table>
- <div id="div1">
- </div>
- </div>
接着新建Scripts\Home文件夹,在该目录新建“Index.js”文件,并再视图中引用,代码如下:
- jQuery(document).ready(function () {
- //jqGrid初始化
- jQuery("#table1").jqGrid({
- url: '/Home/UserList',
- datatype: 'json',
- mtype: 'POST',
- colNames: ['登录名', '姓名', '年龄', '手机号', '邮箱地址', '操作'],
- colModel: [
- { name: 'UserId', index: 'UserId', width: 180, editable: true },
- { name: 'UserName', index: 'UserName', width: 200, editable: true },
- { name: 'Age', index: 'Age', width: 150, editable: true },
- { name: 'Tel', index: 'Tel', width: 150, editable: true },
- { name: 'Email', index: 'Email', width: 150, editable: true },
- { name: 'Edit', index: 'Edit', width: 150, editable: false, align: 'center' }
- ],
- pager: '#div1',
- postData: {},
- rowNum: 5,
- rowList: [5, 10, 20],
- sortable: true,
- caption: '用户信息管理',
- hidegrid: false,
- rownumbers: true,
- viewrecords: true
- }).navGrid('#div1', { edit: false, add: false, del: false })
- .navButtonAdd('#div1', {
- caption: "编辑",
- buttonicon: "ui-icon-add",
- onClickButton: function () {
- var id = $("#table1").getGridParam("selrow");
- if (id == null) {
- alert("请选择行!");
- return;
- }
- if (id == "newId") return;
- $("#table1").editRow(id);
- $("#table1").find("#" + id + "_UserId").attr("readonly","readOnly");
- $("#table1").setCell(id, "Edit", "<input id='Button1' type='button' value='提交' onclick='Update(\"" + id + "\")' /><input id='Button2' type='button' value='取消' onclick='Cancel(\"" + id + "\")' />");
- }
- }).navButtonAdd('#div1', {
- caption: "删除",
- buttonicon: "ui-icon-del",
- onClickButton: function () {
- var id = $("#table1").getGridParam("selrow");
- if (id == null) {
- alert("请选择行!");
- return;
- }
- Delete(id);
- }
- }).navButtonAdd('#div1', {
- caption: "新增",
- buttonicon: "ui-icon-add",
- onClickButton: function () {
- $("#table1").addRowData("newId", -1);
- $("#table1").editRow("newId");
- $("#table1").setCell("newId", "Edit", "<input id='Button1' type='button' value='提交' onclick='Add()' /><input id='Button2' type='button' value='取消' onclick='Cancel(\"newId\")' />");
- }
- });
- });
- //取消编辑状态
- function Cancel(id) {
- if (id == "newId") $("#table1").delRowData("newId");
- else $("#table1").restoreRow(id);
- }
- //向后台ajax请求新增数据
- function Add() {
- var UserId = $("#table1").find("#newId" + "_UserId").val();
- var UserName = $("#table1").find("#newId" + "_UserName").val();
- var Age = $("#table1").find("#newId" + "_Age").val();
- var Tel = $("#table1").find("#newId" + "_Tel").val();
- var Email = $("#table1").find("#newId" + "_Email").val();
- $.ajax({
- type: "POST",
- url: "/Home/Add",
- data: "UserId=" + UserId + "&UserName=" + UserName + "&Age=" + Age + "&Tel=" + Tel + "&Email=" + Email,
- success: function (msg) {
- alert("新增数据: " + msg);
- $("#table1").trigger("reloadGrid");
- }
- });
- }
- //向后台ajax请求更新数据
- function Update(id) {
- var UserId = $("#table1").find("#" + id + "_UserId").val();
- var UserName = $("#table1").find("#" + id + "_UserName").val();
- var Age = $("#table1").find("#" + id + "_Age").val();
- var Tel = $("#table1").find("#" + id + "_Tel").val();
- var Email = $("#table1").find("#" + id + "_Email").val();
- $.ajax({
- type: "POST",
- url: "/Home/Update",
- data: "UserId=" + UserId + "&UserName=" + UserName + "&Age=" + Age + "&Tel=" + Tel + "&Email=" + Email,
- success: function (msg) {
- alert("修改数据: " + msg);
- $("#table1").trigger("reloadGrid");
- }
- });
- }
- //向后台ajax请求删除数据
- function Delete(id) {
- var UserId = $("#table1").getCell(id, "UserId");
- $.ajax({
- type: "POST",
- url: "/Home/Delete",
- data: "UserId=" + UserId,
- success: function (msg) {
- alert("删除数据: " + msg);
- $("#table1").trigger("reloadGrid");
- }
- });
- }
二、实现控制层业务
在Controllers目录下新建控制器“HomeController.cs”,Index.js中产生了四个ajax请求,对应控制层也有四个业务方法。HomeController代码如下:
- public class HomeController : Controller
- {
- UserModel userModel = new UserModel();
- public ActionResult Index()
- {
- return View();
- }
- /// <summary>
- /// 获取全部用户列表,通过json将数据提供给jqGrid
- /// </summary>
- public JsonResult UserList(string sord, string sidx, string rows, string page)
- {
- var list = userModel.FindAll();
- int i = 0;
- var query = from u in list
- select new
- {
- id = i++,
- cell = new string[]{
- u["UserId"].ToString(),
- u["UserName"].ToString(),
- u["Age"].ToString(),
- u["Tel"].ToString(),
- u["Email"].ToString(),
- "-"
- }
- };
- var data = new
- {
- total = query.Count() / Convert.ToInt32(rows) + 1,
- page = Convert.ToInt32(page),
- records = query.Count(),
- rows = query.Skip(Convert.ToInt32(rows) * (Convert.ToInt32(page) - 1)).Take(Convert.ToInt32(rows))
- };
- return Json(data, JsonRequestBehavior.AllowGet);
- }
- /// <summary>
- /// 响应Js的“Add”ajax请求,执行添加用户操作
- /// </summary>
- public ContentResult Add(string UserId, string UserName, int Age, string Tel, string Email)
- {
- Document doc = new Document();
- doc["UserId"] = UserId;
- doc["UserName"] = UserName;
- doc["Age"] = Age;
- doc["Tel"] = Tel;
- doc["Email"] = Email;
- try
- {
- userModel.Add(doc);
- return Content("添加成功");
- }
- catch
- {
- return Content("添加失败");
- }
- }
- /// <summary>
- /// 响应Js的“Delete”ajax请求,执行删除用户操作
- /// </summary>
- public ContentResult Delete(string UserId)
- {
- try
- {
- userModel.Delete(UserId);
- return Content("删除成功");
- }
- catch
- {
- return Content("删除失败");
- }
- }
- /// <summary>
- /// 响应Js的“Update”ajax请求,执行更新用户操作
- /// </summary>
- public ContentResult Update(string UserId, string UserName, int Age, string Tel, string Email)
- {
- Document doc = new Document();
- doc["UserId"] = UserId;
- doc["UserName"] = UserName;
- doc["Age"] = Age;
- doc["Tel"] = Tel;
- doc["Email"] = Email;
- try
- {
- userModel.Update(doc);
- return Content("修改成功");
- }
- catch
- {
- return Content("修改失败");
- }
- }
- }
三、实现模型层数据访问
最后,我们在Models新建一个Home文件夹,添加模型“UserModel.cs”,实现MongoDB数据库访问代码如下:
MongoDB的集合(collection)可以看做关系型数据库的表,文档对象(document)可以看做关系型数据库的一条记录。但两者并不完全对等。表的结构是固定的,MongoDB集合并没有这个约束;另外,存入集合的文档对象甚至可以嵌入子文档,或者“子集合”。他们最终都可以用类似于BJSON的格式描述。我们今天就来分析MongoDB这一特性带来的独特数据管理方式。我们还是以samus驱动为例来分析,samus驱动支持两种方式访问数据库,基本方式和linq方式,基本方式在上篇以介绍过,linq方式我不想单独讲解应用实例,这篇我会用两种方式来对比介绍。 一、包含子文档的集合操作 有这么一个应用场景,某网站提供会员登录的功能,用户需要注册账号才能享受会员服务,但是注册者可能会因为用户资料表单输入项过大而放弃填写,因此用户信息分为主要资料和详细资料两项,初次注册只需要填写主要资料就行了。我们打算把详细信息设计为子文档存储。 1) linq方式实现 1. 新建数据描述类,描述用户信息
- public class UserModel
- {
- //链接字符串(此处三个字段值根据需要可为读配置文件)
- public string connectionString = "mongodb://localhost";
- //数据库名
- public string databaseName = "myDatabase";
- //集合名
- public string collectionName = "userCollection";
- private Mongo mongo;
- private MongoDatabase mongoDatabase;
- private MongoCollection<Document> mongoCollection;
- public UserModel()
- {
- mongo = new Mongo(connectionString);
- mongoDatabase = mongo.GetDatabase(databaseName) as MongoDatabase;
- mongoCollection = mongoDatabase.GetCollection<Document>(collectionName) as MongoCollection<Document>;
- mongo.Connect();
- }
- ~UserModel()
- {
- mongo.Disconnect();
- }
- /// <summary>
- /// 增加一条用户记录
- /// </summary>
- /// <param name="doc"></param>
- public void Add(Document doc)
- {
- mongoCollection.Insert(doc);
- }
- /// <summary>
- /// 删除一条用户记录
- /// </summary>
- public void Delete(string UserId)
- {
- mongoCollection.Remove(new Document { { "UserId", UserId } });
- }
- /// <summary>
- /// 更新一条用户记录
- /// </summary>
- /// <param name="doc"></param>
- public void Update(Document doc)
- {
- mongoCollection.FindAndModify(doc, new Document { { "UserId", doc["UserId"].ToString() } });
- }
- /// <summary>
- /// 查找所有用户记录
- /// </summary>
- /// <returns></returns>
- public IEnumerable<Document> FindAll()
- {
- return mongoCollection.FindAll().Documents;
- }
- }
- /// <summary>
- /// 用户主要资料
- /// </summary>
- public class UserInfo
- {
- public string UserId { get; set; }
- public string UserName { get; set; }
- public string PassWord { get; set; }
- public Detail Detail { get; set; }
- }
- /// <summary>
- /// 用户详细资料
- /// </summary>
- public class Detail
- {
- public string Address { get; set; }
- public int Age { get; set; }
- public string Email { get; set; }
- }
2. 我们要新建一个用户业务操作类“UserBLL”。这个时候要让驱动知道UserInfo类描述了“用户资料”的字段信息,在GetMongo()方法实现了配置步骤,UserBLL完整代码如下:
- public class UserBLL
- {
- public string connectionString = "mongodb://localhost";
- public string databaseName = "myDatabase";
- private Mongo mongo;
- private MongoDatabase mongoDatabase;
- //注意这里泛型类型为“UserInfo”
- private MongoCollection<UserInfo> mongoCollection;
- public UserBLL()
- {
- mongo = GetMongo();
- mongoDatabase = mongo.GetDatabase(databaseName) as MongoDatabase;
- mongoCollection = mongoDatabase.GetCollection<UserInfo>() as MongoCollection<UserInfo>;
- mongo.Connect();
- }
- ~UserBLL()
- {
- mongo.Disconnect();
- }
- /// <summary>
- /// 配置Mongo,将类UserInfo映射到集合
- /// </summary>
- private Mongo GetMongo()
- {
- var config = new MongoConfigurationBuilder();
- config.Mapping(mapping =>
- {
- mapping.DefaultProfile(profile =>
- {
- profile.SubClassesAre(t => t.IsSubclassOf(typeof(UserInfo)));
- });
- mapping.Map<UserInfo>();
- });
- config.ConnectionString(connectionString);
- return new Mongo(config.BuildConfiguration());
- }
- }
3. 接着,在“UserBLL”类中定义一个方法“InsertSomeData()”来插入一些数据:
- /// <summary>
- /// 插入一些数据
- /// </summary>
- public void InsertSomeData()
- {
- UserInfo userInfo1 = new UserInfo()
- {
- UserId = "1001",
- UserName = "张三",
- PassWord = "123456"
- };
- mongoCollection.Save(userInfo1);
- UserInfo userInfo2 = new UserInfo()
- {
- UserId = "1002",
- UserName = "李四",
- PassWord = "123456",
- Detail = new Detail()
- {
- Address = "湖北",
- Age = 20,
- Email = "[email protected]"
- }
- };
- mongoCollection.Save(userInfo2);
- UserInfo userInfo3 = new UserInfo()
- {
- UserId = "1003",
- UserName = "王五",
- PassWord = "123456",
- Detail = new Detail()
- {
- Address = "广东",
- Age = 20,
- Email = "[email protected]"
- }
- };
- mongoCollection.Save(userInfo3);
- UserInfo userInfo4 = new UserInfo()
- {
- UserId = "1004",
- UserName = "赵六",
- PassWord = "123456",
- Detail = new Detail()
- {
- Address = "湖北"
- }
- };
- mongoCollection.Save(userInfo4);
- }
4. 定义一个查找数据的方法“Select”,它将查找用户详细信息中,地址在湖北的全部用户:
- <summary>
- /// 查询详细资料地址为湖北的用户信息
- /// </summary>
- public List<UserInfo> Select()
- {
- return mongoCollection.Linq().Where(x => x.Detail.Address == "湖北").ToList();
- }
5. 还定义一个删除数据的方法,将删除集合全部数据:
- /// <summary>
- /// 删除全部用户信息
- /// </summary>
- public void DeleteAll()
- {
- mongoCollection.Remove(x => true);
- }
6. 在Main方法中添加如下代码:
- static void Main(string[] args)
- {
- UserBLL userBll = new UserBLL();
- userBll.InsertSomeData();
- var users = userBll.Select();
- foreach (var user in users)
- {
- Console.WriteLine(user.UserName + "是湖北人");
- };
- userBll.DeleteAll();
- }
7. 最后执行程序,打印如下信息:
李四是湖北人
赵六是湖北人
1) 普通实现
普通方式实现不想多讲,直接贴代码,看看与linq方式有什么区别:
- class Program
- {
- static void Main(string[] args)
- {
- UserBLL userBll = new UserBLL();
- userBll.InsertSomeData();
- var users = userBll.Select();
- foreach (var user in users)
- {
- Console.WriteLine(user["UserName"].ToString() + "是湖北人");
- };
- userBll.DeleteAll();
- Console.ReadLine();
- }
- }
- public class UserBLL
- {
- public string connectionString = "mongodb://localhost";
- public string databaseName = "myDatabase";
- public string collectionName = "UserInfo";
- private Mongo mongo;
- private MongoDatabase mongoDatabase;
- private MongoCollection<Document> mongoCollection;
- public UserBLL()
- {
- mongo = new Mongo(connectionString);
- mongoDatabase = mongo.GetDatabase(databaseName) as MongoDatabase;
- mongoCollection = mongoDatabase.GetCollection<Document>(collectionName) as MongoCollection<Document>;
- mongo.Connect();
- }
- ~UserBLL()
- {
- mongo.Disconnect();
- }
- /// <summary>
- /// 插入一些数据
- /// </summary>
- public void InsertSomeData()
- {
- Document userInfo1 = new Document();
- userInfo1["UserId"] = "1001";
- userInfo1["UserName"] = "张三";
- userInfo1["PassWord"] = "123456";
- mongoCollection.Save(userInfo1);
- Document userInfo2 = new Document();
- userInfo2["UserId"] = "1002";
- userInfo2["UserName"] = "李四";
- userInfo2["PassWord"] = "123456";
- //子文档
- var userInfo2Detail = new Document();
- userInfo2Detail["Address"] = "湖北";
- userInfo2Detail["Age"] = 20;
- userInfo2Detail["Email"] = "[email protected]";
- userInfo2["Detail"] = userInfo2Detail;
- mongoCollection.Save(userInfo2);
- Document userInfo3 = new Document();
- userInfo3["UserId"] = "1003";
- userInfo3["UserName"] = "王五";
- userInfo3["PassWord"] = "123456";
- var userInfo3Detail = new Document();
- userInfo3Detail["Address"] = "广东";
- userInfo3Detail["Age"] = 20;
- userInfo3Detail["Email"] = "[email protected]";
- userInfo3["Detail"] = userInfo3Detail;
- mongoCollection.Save(userInfo3);
- Document userInfo4 = new Document();
- userInfo4["UserId"] = "1004";
- userInfo4["UserName"] = "赵六";
- userInfo4["PassWord"] = "123456";
- var userInfo4Detail = new Document();
- userInfo4Detail["Address"] = "湖北";
- userInfo4["Detail"] = userInfo4Detail;
- mongoCollection.Save(userInfo4);
- }
- /// <summary>
- /// 查询详细资料地址为湖北的用户信息
- /// </summary>
- public IEnumerable<Document> Select()
- {
- return mongoCollection.Find(new Document { { "Detail.Address", "湖北" } }).Documents;
- }
- /// <summary>
- /// 删除全部用户信息
- /// </summary>
- public void DeleteAll()
- {
- mongoCollection.Remove(new Document { });
- }
- }
最后,我们通过这段代码输出全部用户资料信息的BJSON格式:
二、包含“子集合”的集合操作 同样举个例子:有一个学校人事管理系统要统计班级和学生的信息,现在定义了一个“班级集合”,这个集合里面的学生字段是一个“学生集合”,包含了本班全部学生。 1) linq方式实现 基础配置我就不多说了,数据类定义如下:
- /// <summary>
- /// 打印数据BJSON
- /// </summary>
- public void PrintBJSON()
- {
- string BJSON = string.Empty;
- foreach (var documet in mongoCollection.FindAll().Documents)
- {
- BJSON += documet.ToString();
- }
- Console.WriteLine(BJSON);
- }
- 结果如下:
- { "UserId": "1001", "UserName": "张三", "PassWord": "123456", "_id": "4d80ec1ab8a4731338000001" }
- { "UserId": "1002", "UserName": "李四", "PassWord": "123456", "Detail": { "Address": "湖北", "Age": 20, "Email": "[email protected]" }, "_id": "4d80ec1ab8a4731338000002" }
- { "UserId": "1003", "UserName": "王五", "PassWord": "123456", "Detail": { "Address": "广东", "Age": 20, "Email": "[email protected]" }, "_id": "4d80ec1ab8a4731338000003" }
- { "UserId": "1004", "UserName": "赵六", "PassWord": "123456", "Detail": { "Address": "湖北" }, "_id": "4d80ec1ab8a4731338000004" }
- /// <summary>
- /// 班级信息
- /// </summary>
- public class ClassInfo
- {
- public string ClassName { get; set; }
- public List<Student> Students { get; set; }
- }
- /// <summary>
- /// 学生信息
- /// </summary>
- public class Student
- {
- public string Name { get; set; }
- public int Age { get; set; }
- }
查询叫“张三”的学生在哪个班级,以及他的详细信息:
(这里其实是ToList后在内存中查的,linq方式直接查询好像驱动不支持。)
- public List<ClassInfo> Select()
- {
- return mongoCollection.Linq().ToList().Where(x => x.Students.Exists(s => s.Name == "张三")).ToList();
- }
1) 普通实现
查询叫“张三”的学生在哪个班级,以及他的详细信息:
- public List<Document> Select()
- {
- var mongocollection = mongoDatabase.GetCollection("ClassInfo");
- return mongocollection.Find(new Document { { "Students.Name", "张三" } }).Documents.ToList();
- }
打印数据的BJSON:
- { "_id": "4d814bae5c5f000000005f63", "ClassName": "1001", "Students": [ { "Name": "张三", "Age": 10 }, { "Name": "李四", "Age": 0 } ] }
- { "_id": "4d814bae5c5f000000005f64", "ClassName": "1002", "Students": [ ] }
- { "_id": "4d814bae5c5f000000005f65", "ClassName": "1003", "Students": [ { "Name": "王五", "Age": 11 }, { "Name": "赵六", "Age": 9 } ] }
由于MongoDB的文档结构为BJSON格式(BJSON全称:Binary JSON),而BJSON格式本身就支持保存二进制格式的数据,因此可以把文件的二进制格式的数据直接保存到MongoDB的文档结构中。但是由于一个BJSON的最大长度不能超过4M,所以限制了单个文档中能存入的最大文件不能超过4M。为了提供对大容量文件存取的支持,samus驱动提供了“GridFS”方式来支持,“GridFS”方式文件操作需要引入新的程序集“MongoDB.GridFS.dll”。下面我们分别用两种方式来实现。 一、在文档对象中存取文件 当文件大小较小的时候,直接存入文档对象实现起来更简洁。比如大量图片文件的存取等,一般图片文件都不会超过4M。我们先实现一个上传图片存入数据库,再取出来写回页面的例子: 1. 把图片存到BJSON中
- /// <summary>
- /// 把图片存到BJSON中
- /// </summary>
- public void SaveImgBJSON(byte[] byteImg)
- {
- Document doc = new Document();
- doc["ID"] = 1;
- doc["Img"] = byteImg;
- mongoCollection.Save(doc);
- }
2. 获取BJSON方式存储的图片字节数据
- /// <summary>
- /// 获取BJSON方式存储的图片字节数据
- /// </summary>
- public byte[] GetImgBJSON()
- {
- Document doc= mongoCollection.FindOne(new Document { { "ID", 1 } });
- return doc["Img"] as Binary;
- }
上面两段代码是在对MongoDB相关操作进行BLL封装类中添加的两个方法,封装方式查看上节内容。下面看看在webform中如何调用:
在界面拖出一个FileUpload控件和一个Button控件,页面cs类加如下方法:
- protected void Button1_Click(object sender, EventArgs e)
- {
- ImgBLL imgBll = new ImgBLL();
- imgBll.DeleteAll();
- imgBll.SaveImgBJSON(FileUpload1.FileBytes);
- Response.BinaryWrite(imgBll.GetImgBJSON());
- }
二、用GridFS方式存取文件
在实现GridFS方式前我先讲讲它的原理,为什么可以存大文件。驱动首先会在当前数据库创建两个集合:"fs.files"和"fs.chunks"集合,前者记录了文件名,文件创建时间,文件类型等基本信息;后者分块存储了文件的二进制数据(并支持加密这些二进制数据)。分块的意思是把文件按照指定大小分割,然后存入多个文档中。"fs.files"怎么知道它对应的文件二进制数据在哪些块呢?那是因为在"fs.chunks"中有个"files_id"键,它对应"fs.files"的"_id"。"fs.chunks"还有一个键(int型)"n",它表明这些块的先后顺序。这两个集合名中的"fs"也是可以通过参数自定义的。
如果你只是想知道怎么用,可以忽略上面这段话,下面将用法:
1. GridFS方式的文件新建,读取,删除
- private string GridFsSave(byte[] byteFile)
- {
- string filename = Guid.NewGuid().ToString();
- //这里GridFile构造函数有个重载,bucket参数就是用来替换那个创建集合名中默认的"fs"的。
- GridFile gridFile = new GridFile(mongoDatabase);
- using (GridFileStream gridFileStream = gridFile.Create(filename))
- {
- gridFileStream.Write(byteFile, 0, byteFile.Length);
- }
- return filename;
- }
- private byte[] GridFsRead(string filename)
- {
- GridFile gridFile = new GridFile(mongoDatabase);
- GridFileStream gridFileStream = gridFile.OpenRead(filename);
- byte[] bytes = new byte[gridFileStream.Length];
- gridFileStream.Read(bytes, 0, bytes.Length);
- return bytes;
- }
- private void GridFsDelete(string filename)
- {
- GridFile gridFile = new GridFile(mongoDatabase);
- gridFile.Delete(new Document("filename", filename));
- }
2. 再次封装GridFS操作,新文档只存储文件名称,相当于只是一个键,新文档还可以有除“文件名”之外其他的键。
MongoDB中的索引其实类似于关系型数据库,都是为了提高查询和排序的效率的,并且实现原理也基本一致。由于集合中的键(字段)可以是普通数据类型,也可以是子文档。MongoDB可以在各种类型的键上创建索引。下面分别讲解各种类型的索引的创建,查询,以及索引的维护等。 一、创建索引 1. 默认索引 MongoDB有个默认的“_id”的键,他相当于“主键”的角色。集合创建后系统会自动创建一个索引在“_id”键上,它是默认索引,索引名叫“_id_”,是无法被删除的。我们可以通过以下方式查看:
- /// <summary>
- /// 把图片存到GridFS中
- /// </summary>
- public void SaveImgGridFS(byte[] byteImg)
- {
- string filename = GridFsSave(byteImg);
- Document doc = new Document();
- doc["ID"] = 1;
- doc["filename"] = filename;
- mongoCollection.Save(doc);
- }
- /// <summary>
- /// 获取GridFS方式存储的图片
- /// </summary>
- public byte[] GetImgGridFS()
- {
- Document doc = mongoCollection.FindOne(new Document { { "ID", 1 } });
- string filename = doc["filename"].ToString();
- return GridFsRead(filename);
- }
- var _idIndex = mongoCollection.Metadata.Indexes.Single(x => x.Key == "_id_");
- Console.WriteLine(_idIndex);
2. 单列索引
在单个键上创建的索引就是单列索引,例如我们要在“UserInfo”集合上给“UserName”键创建一个单列索引,语法如下:(1表示正序,-1逆序)
- mongoCollection.Metadata.CreateIndex(new Document { { "UserName", 1 } }, false);
接着,我们用同样方法查找名为“_UserName_”的索引
- var _UserName_Index = mongoCollection.Metadata.Indexes.Single(x => x.Key == "_UserName_");
- Console.WriteLine(_UserName_Index);
3.组合索引
另外,我们还可以同时对多个键创建组合索引。如下代码创建了按照“UserId”正序,“UserName”逆序的组合索引:
- mongoCollection.Metadata.CreateIndex(new Document { { "UserId", 1 }, { "UserName", -1 } }, false);
4.子文档索引
我们可以对文档类型的键创建各种索引,例如单列索引,如下创建用户详细信息“Detail”的单列索引:
- mongoCollection.Metadata.CreateIndex(new Document { { "Detail", 1 } }, false);
对子文档的键创建组合索引:例如在“Detail.Address”和“Detail.Age”上创建组合索引:
- mongoCollection.Metadata.CreateIndex(new Document { { "Detail.Address", 1 }, { "Detail.Age", -1 } }, false);
5.唯一索引
唯一索引限制了对当前键添加值时,不能添加重复的信息。值得注意的是,当文档不存在指定键时,会被认为键值是“null”,所以“null”也会被认为是重复的,所以一般被作为唯一索引的键,最好都要有键值对。
对“UserId”创建唯一索引(这时候最后一个参数为“true”):
二、维护索引 1. 查询索引 通过索引名查询的方式已有介绍。但有时候,我们可能忘记了索引名,怎么查询呢? 下面提供一个遍历全部索引的方法,打印全部索引信息:
- mongoCollection.Metadata.CreateIndex(new Document { { "UserId", 1 } }, true);
- foreach (var index in mongoCollection.Metadata.Indexes)
- {
- Console.WriteLine(index.Value);
- }
输出结果示例:
- { "name": "_id_", "ns": "myDatabase.UserInfo", "key": { "_id": 1 } }
- { "name": "_UserId_unique_", "ns": "myDatabase.UserInfo", "key": { "UserId": 1 }, "unique": true, "_id": "4d8f406ab8a4730b78000005" }
- { "name": "_UserName_", "ns": "myDatabase.UserInfo", "key": { "UserName": 1 }, "unique": false, "_id": "4d8f406ab8a4730b78000006" }
- { "name": "_Detail.Address_Detail.Age_", "ns": "myDatabase.UserInfo", "key": { "Detail.Address": 1, "Detail.Age": -1 }, "unique": false, "_id": "4d8f406ab8a4730b78000007" }
- { "name": "_UserId_UserName_", "ns": "myDatabase.UserInfo", "key": { "UserId": 1, "UserName": -1 }, "unique": false, "_id": "4d8f406ab8a4730b78000008" }
- { "name": "_Detail_", "ns": "myDatabase.UserInfo", "key": { "Detail": 1 }, "unique": false, "_id": "4d8f406ab8a4730b78000009" }
可见,集合的索引也是通过一个集合来维护的。name表示索引名,ns表示索引属于哪个库哪个集合,key表示索引在哪个键上,正序还是逆序,unique表示是否为唯一索引,等等...
2. 删除索引
新手常陷入的误区是,认为集合被删除,索引就不存在了。关系型数据库中,表被删除了,索引也不会存在。在MongoDB中不存在删除集合的说法,就算集合数据清空,索引都是还在的,要移除索引还需要手工删除。
例如,删除名为“_UserName_”的索引:
- mongoCollection.Metadata.DropIndex("_UserName_");
下面提供删除除默认索引外其他全部索引的方法:
三、索引的效率 MongoDB的索引到底能不能提高查询效率呢?我们在这里通过一个例子来测试。比较同样的数据在无索引和有索引的情况下的查询速度。 首先,我们通过这样一个方法插入10W条数据:
- public void DropAllIndex()
- {
- var listIndexes = mongoCollection.Metadata.Indexes.ToList();
- for (int i = 0; i < listIndexes.Count; i++)
- {
- if (listIndexes[i].Key != "_id_")
- {
- mongoCollection.Metadata.DropIndex(listIndexes[i].Key);
- }
- }
- }
- public void InsertBigData()
- {
- var random = new Random();
- for (int i = 1; i < 100000; i++)
- {
- Document doc = new Document();
- doc["ID"] = i;
- doc["Data"] = "data" + random.Next(100000);
- mongoCollection.Save(doc);
- }
- Console.WriteLine("当前有" + mongoCollection.FindAll().Documents.Count() + "条数据");
- }
然后,实现一个方法用来创建索引:
- public void CreateIndexForData()
- {
- mongoCollection.Metadata.CreateIndex(new Document { { "Data", 1 } }, false);
- }
还有排序的方法:
- public void SortForData()
- {
- mongoCollection.FindAll().Sort(new Document { { "Data", 1 } });
- }
运行测试代码如下:
- static void Main(string[] args)
- {
- IndexBLL indexBll = new IndexBLL();
- indexBll.DropAllIndex();
- indexBll.DeleteAll();
- indexBll.InsertBigData();
- Stopwatch watch1 = new Stopwatch();
- watch1.Start();
- for (int i = 0; i < 1; i++) indexBll.SortForData();
- Console.WriteLine("无索引排序执行时间:" + watch1.Elapsed);
- indexBll.CreateIndexForData();
- Stopwatch watch2 = new Stopwatch();
- watch2.Start();
- for (int i = 0; i < 1; i++) indexBll.SortForData();
- Console.WriteLine("有索引排序执行时间:" + watch2.Elapsed);
- }
最后执行程序查看结果:
多次测试表明在有索引的情况下,查询效率要高于无索引的效率。