h0945634 发表于 2015-7-5 13:13:49

使用MongoDB作为后台数据库的尝试

  MongoDB作为一个阶层型数据库,在很短的时间里面是不可能被大面积推广使用的,
  本文作为一个实验性的课题,探讨一下MongoDB作为网站数据库的可能性。
  1.MongoDB作为代替关系型数据库的可能性。
  2.MongoDB作为代替文件服务器的可能性。
  通过探讨来加强对于MongoDB的认识
  
  环境准备
  技术选型
  1.由于是验证性质的课题,这里没有使用MVC5/6.如果有人对MVC6有兴趣,可以另开一个课题讨论。这里使用的是传统的WebForm。
  2.使用MongoDB最新版本作为数据库
  3.MongoDB的GUI使用本人自己开发的工具
  4.项目Host在阿里云上
  截图1.
  阿里云上的MongoDB数据库,使用本人开发工具看到的结果图

  
  5.启用meishiyouji.com 域名
  6.UI使用的是UIKit这个库,进行响应式布局

  
  7.使用官方的MongoDB的C#驱动程序
  
  开发实践
  1.MongoDB作为阶层型数据库的代表,其最大特点就是Free的存储方式,一个Collection(相当于数据表)里面可以存储各种结构的文档。
  这个特性,如果用得好,非常有帮助,如果用的不好,容易造成混乱。例如我们可以将继承同一个基类的子类实例放在同一个Collection里面,
  这个是传统数据库无法做的,不同的子类的字段数会不一样,无法放到同一个表里面去,但是MongoDB是可以做到的。
  
  例子:
  基类如下
  例如一个行程的详细信息的基类如下:



1 using Common.Database;
2 using Common.Misc;
3 using MongoDB.Bson.Serialization.Attributes;
4 using MongoDB.Driver.Builders;
5 using System.Collections.Generic;
6
7 namespace Common.Schedule
8 {
9   
10   public class DetailScheduleInfoBase : EntityBase
11   {
12         ///
13         /// 数据集名称
14         ///
15         public static string CollectionName = "DetailScheduleInfo";
16         ///
17         /// 序列号前缀
18         ///
19         public const string Prefix = "DI";
20         ///
21         ///
22         ///
23         public static string NewSN = Prefix + 0.ToString("D8");
24         ///
25         /// OverviewSN
26         ///
27         public string OverviewSN = string.Empty;
28         ///
29         /// DiarySchduleSN
30         ///
31         public string DiarySchduleSN = string.Empty;
32         ///
33         /// 说明
34         ///
35         public string Description = string.Empty;
36         ///
37         /// Visit
38         ///
39         public string Location = string.Empty;
40         ///
41         /// 移动开始时间
42         ///
43         public string FromTime = string.Empty;
44         ///
45         /// 移动终止时间
46         ///
47         public string ToTime = string.Empty;
48         ///
49         /// 分类
50         ///
51         public SchduleTypeEnum SchduleType = SchduleTypeEnum.Other;
52         ///
53         /// 图片
54         ///
55         public string UploadImgUrl = string.Empty;
56         ///
57         /// 分类中文
58         ///
59         ///
60         ///
61         public static string GetSchduleTypeEnumChinese(SchduleTypeEnum c)
62         {
63             string SchduleTypeEnumChinese = string.Empty;
64             switch (c)
65             {
66               case SchduleTypeEnum.Traffic:
67                     SchduleTypeEnumChinese = "交通";
68                     break;
69               case SchduleTypeEnum.Visit:
70                     SchduleTypeEnumChinese = "参观";
71                     break;
72               case SchduleTypeEnum.Shopping:
73                     SchduleTypeEnumChinese = "购物";
74                     break;
75               case SchduleTypeEnum.Catering:
76                     SchduleTypeEnumChinese = "饮食";
77                     break;
78               //case SchduleTypeEnum.Rest:
79               //    SchduleTypeEnumChinese = "休息";
80               //    break;
81               //case SchduleTypeEnum.Certificates:
82               //    SchduleTypeEnumChinese = "手续";
83               //    break;
84               case SchduleTypeEnum.Other:
85                     SchduleTypeEnumChinese = "其他";
86                     break;
87               default:
88                     break;
89             }
90             return SchduleTypeEnumChinese;
91         }
92         ///
93         /// 分类枚举
94         ///
95         public enum SchduleTypeEnum
96         {
97             ///
98             /// 交通
99             ///
100             Traffic,
101             ///
102             /// 参观
103             ///
104             Visit,
105             ///
106             /// 饮食
107             ///
108             Catering,
109             ///
110             /// 购物
111             ///
112             Shopping,
113             /////
114             ///// 休息
115             /////
116             //Rest,
117             /////
118             ///// 手续
119             /////
120             //Certificates,
121             ///
122             /// 其他
123             ///
124             Other
125         }
126         ///
127         /// 备注
128         ///
129         public string Comments = string.Empty;
130         ///
131         /// 是否为一个备选方案
132         ///
133         public bool IsBack = false;
134         ///
135         /// 消费记录
136         ///
137         public List ConsumptionList = new List();
138         ///
139         /// 添加详细行程
140         ///
141         ///
142         public static void Insert(DetailScheduleInfoBase info, string CreateUserName)
143         {
144             Operater.InsertRec(Prefix, CollectionName, info, CreateUserName);
145         }
146         ///
147         /// 通过序列号获得概要对象
148         ///
149         ///
150         public static DetailScheduleInfoBase GetDetailScheduleBySN(string DetailScheduleSN)
151         {
152             var query = Query.EQ("_id", DetailScheduleSN);
153             return Operater.GetFirstRec(CollectionName, query);
154         }
155         ///
156         /// 更新概要对象
157         ///
158         ///
159         public static void Update(DetailScheduleInfoBase newobj, string UserName)
160         {
161             Operater.UpdateRec(CollectionName, newobj, UserName);
162         }
163   }
164 }
  某一个子类的代码如下



1 namespace Common.Schedule
2 {
3   public class Traffic : DetailScheduleInfoBase
4   {
5         public Traffic()
6         {
7             SchduleType = SchduleTypeEnum.Traffic;
8         }
9         ///
10         /// 移动开始地址
11         ///
12         public string MoveFromLocation = string.Empty;
13         ///
14         /// 移动终止地址
15         ///
16         public string MoveToLocation = string.Empty;
17         ///
18         /// 交通手段
19         ///
20         public TrafficTypeEnum TrafficType = TrafficTypeEnum.Walk;
21         ///
22         /// 交通手段枚举
23         ///
24         public enum TrafficTypeEnum
25         {
26             ///
27             /// 步行
28             ///
29             Walk,
30             ///
31             /// 出租
32             ///
33             Taxi,
34             ///
35             /// 巴士
36             ///
37             Bus,
38             ///
39             /// 捷运
40             ///
41             JieYun,
42             ///
43             /// 台铁
44             ///
45             Train,
46             ///
47             /// 高铁
48             ///
49             SpeedTrain,
50             ///
51             /// 飞机
52             ///
53             AirPlane,
54             ///
55             /// 轮船
56             ///
57             Ship
58         }
59         ///
60         /// 交通手段中文
61         ///
62         ///
63         ///
64         public static string GetTrafficTypeEnumChinese(TrafficTypeEnum c)
65         {
66             string CurrencyEnumChinese = string.Empty;
67             switch (c)
68             {
69               case TrafficTypeEnum.Walk:
70                     CurrencyEnumChinese = "步行";
71                     break;
72               case TrafficTypeEnum.Taxi:
73                     CurrencyEnumChinese = "出租";
74                     break;
75               case TrafficTypeEnum.Bus:
76                     CurrencyEnumChinese = "巴士";
77                     break;
78               case TrafficTypeEnum.JieYun:
79                     CurrencyEnumChinese = "捷运";
80                     break;
81               case TrafficTypeEnum.Train:
82                     CurrencyEnumChinese = "台铁";
83                     break;
84               case TrafficTypeEnum.SpeedTrain:
85                     CurrencyEnumChinese = "高铁";
86                     break;
87               case TrafficTypeEnum.AirPlane:
88                     CurrencyEnumChinese = "飞机";
89                     break;
90               case TrafficTypeEnum.Ship:
91                     CurrencyEnumChinese = "轮船";
92                     break;
93               default:
94                     break;
95             }
96             return CurrencyEnumChinese;
97         }
98   }
99 }
  
  基类和子类可以存放在同一个数据集里面
  1.注意 _t 字段,这里保存着基类的类型,如果是子类的话,这个字段是空的。
  2.编码的时候请注意,子类和基类 保存/序列化 的时候不会出现什么问题,但是读取/反序列化的时候,一定会出现错误。
  请在基类上增加这样的特性标签



1
  这样的话,实际上是为系统注册了基类和子类关系,就可以正确的反序列化了。

  
  我们是否要将一个巨大的对象保存为一个数据文档(数据文档在MongoDB里面是一条记录的意思)
  一个巨大的对象,往往是一个树型结构的数据。
  例如,一个旅行行程表,它包含了一个行程概要,一个礼物列表,一个[每日行程列表],一个事前准备列表。
  每一个[每日行程列表] 又包含了 【详细行程】列表,【详细行程】中又包含了【消费明细】等等。
  

  
  这样的话,一个行程就是一个巨大的对象(巨大的树形结构数据),当然,MongoDB是支持将整棵树作为一个数据文档,一下子保存到数据库中去的。
  (每一个数据文档的大小是有限制的,但是那个限制也是一个天文数字,我想没有人会把整部三国演义作为一个对象放到数据库里面去的吧)
  不过,这样对于数据库的管理将是一个巨大的灾难,这个灾难不仅仅是在数据进行更新时候,更新命令将会变得很复杂,而且会在各种聚合操作的时候,将会遍历所有的数据对象。
  这里使用了一种折衷的办法,将一个3层或者4层的结构,拆分为两个数据表,一个里面包含上面两层数据,然后做一个摘要结构,包含着下两层信息的一个简报。一些简单的信息直接从简版里面读取。
  下两层则放置在另一个数据库中。只有在需要详细数据的时候才读取出来呈现在界面上。

  
  这里的设计也就是一个数据存储的粒度的设计,粒度太小,就退化为关系型数据库。反之则每次更新将涉及整棵文档树。粒度设计是MongoDB的一个难点。
  
  2.将MongoDB作为文件服务器使用
  MongoDB的Grid File System特性就是一个内置的文件存储空间。你可以像管理数据一样管理文件。

  对于文件的读取,其实很简单
  C#驱动的Download和Upload方法提供了读取和保存文件的方法



1         ///
2         /// 保存文件
3         ///
4         ///
5         ///
6         ///
7         public static string InsertFile(HttpPostedFile file, string Username)
8         {
9             string Mongofilename = Username + "_" + System.DateTime.Now.ToString("yyyyMMddHHmmss") + "_" + file.FileName;
10             MongoGridFS gfs = innerFileServer.GetGridFS(new MongoGridFSSettings());
11             gfs.Upload(file.InputStream, Mongofilename);
12             return Mongofilename;
13         }
14         ///
15         /// 获得文件
16         ///
17         ///
18         ///
19         public static void GetFile(Stream stream,string filename)
20         {
21             MongoGridFS gfs = innerFileServer.GetGridFS(new MongoGridFSSettings());
22             gfs.Download(stream,filename);
23         }
  
  当然,我们想做的事情是让IIS请求图片的时候,不走静态文件这条路,而是从数据库里面读取文件。
  这里我们要自定义 http Handler
  a.修改 webconfig
  注意,经典模式和集成模式是不同的
  我们让系统对于图片文件,用我们自定义的方法进行处理,JPG,GIF,PNG的响应使用ImageServer类来处理










  b.ImageServer



using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace TaiWanTripPlanSite
{
public class ImageServer : IHttpHandler
{
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
var url = context.Request.Url.LocalPath.Split("/".ToCharArray());
var filename = url.Last();
///设置为最大时间,图片都是统一的文件名字,不会重复
context.Response.Expires = int.MaxValue;
Common.Database.Operater.GetFile(context.Response.OutputStream, filename);
}
}
}
  这里需要注意的是,一定要设定过期时间,不然的话,浏览器将不能使用缓存图片,每次访问同样的资源将进行全量下载。
  这里将过期时间设定为最大值,图片资源将永不过期。
  
  总结
  这篇文章只是提供了一个使用MongoDB作为数据库和文件服务的方案,对于性能没有任何的测试。
  阶层型数据库需要在粒度上进行总体的规划和设计。对于MongoDB,可以使用内置的文件服务功能,他对于同名文件,支持版本管理功能。
  如果有人愿意,可以进行一个压力测试,看一下MongoDB的性能如何。
  MongoDB的强大在于分布式,分片,副本等高级功能。这里只是抛砖引玉。
  欢迎和大家一起学习MongoDB,讨论MongoDB
  (本人已经接受英国某出版社的委托,作为志愿者Review MongoDB的视频课程)
  本文网站: 笔者花了一个星期的作品,使用UIKit组件,webform,mongodb数据库,仅供参考,让前端大牛见笑了。
  www.meishiyouji.com
  
  广告时间:
  如果你对我的技术认可,并且有志于从事前端开发,愿意和我一起共事,能否投个简历到上海中和 软件公司。
  Email:   mynightelfplayer@hotmail.com
  或者在 拉钩网 搜索   中和软件 的职位。
  http://www.lagou.com/gongsi/37348.html
  欢迎有2-5年前端经验的人应聘,我将会亲自带你们学习知识,提高技能,让你们实现自我价值。
页: [1]
查看完整版本: 使用MongoDB作为后台数据库的尝试