zoukankan      html  css  js  c++  java
  • 使用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     [BsonKnownTypes(typeof(Traffic), typeof(Visit), typeof(Shopping), typeof(Catering))]
     10     public class DetailScheduleInfoBase : EntityBase
     11     {
     12         /// <summary>
     13         /// 数据集名称
     14         /// </summary>
     15         public static string CollectionName = "DetailScheduleInfo";
     16         /// <summary>
     17         /// 序列号前缀
     18         /// </summary>
     19         public const string Prefix = "DI";
     20         /// <summary>
     21         /// 
     22         /// </summary>
     23         public static string NewSN = Prefix + 0.ToString("D8");
     24         /// <summary>
     25         /// OverviewSN
     26         /// </summary>
     27         public string OverviewSN = string.Empty;
     28         /// <summary>
     29         /// DiarySchduleSN
     30         /// </summary>
     31         public string DiarySchduleSN = string.Empty;
     32         /// <summary>
     33         /// 说明
     34         /// </summary>
     35         public string Description = string.Empty;
     36         /// <summary>
     37         /// Visit
     38         /// </summary>
     39         public string Location = string.Empty;
     40         /// <summary>
     41         /// 移动开始时间
     42         /// </summary>
     43         public string FromTime = string.Empty;
     44         /// <summary>
     45         /// 移动终止时间
     46         /// </summary>
     47         public string ToTime = string.Empty;
     48         /// <summary>
     49         /// 分类
     50         /// </summary>
     51         public SchduleTypeEnum SchduleType = SchduleTypeEnum.Other;
     52         /// <summary>
     53         /// 图片
     54         /// </summary>
     55         public string UploadImgUrl = string.Empty;
     56         /// <summary>
     57         /// 分类中文
     58         /// </summary>
     59         /// <param name="c"></param>
     60         /// <returns></returns>
     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         /// <summary>
     93         /// 分类枚举
     94         /// </summary>
     95         public enum SchduleTypeEnum
     96         {
     97             /// <summary>
     98             /// 交通
     99             /// </summary>
    100             Traffic,
    101             /// <summary>
    102             /// 参观
    103             /// </summary>
    104             Visit,
    105             /// <summary>
    106             /// 饮食
    107             /// </summary>
    108             Catering,
    109             /// <summary>
    110             /// 购物
    111             /// </summary>
    112             Shopping,
    113             ///// <summary>
    114             ///// 休息
    115             ///// </summary>
    116             //Rest,
    117             ///// <summary>
    118             ///// 手续
    119             ///// </summary>
    120             //Certificates,
    121             /// <summary>
    122             /// 其他
    123             /// </summary>
    124             Other
    125         }
    126         /// <summary>
    127         /// 备注
    128         /// </summary>
    129         public string Comments = string.Empty;
    130         /// <summary>
    131         /// 是否为一个备选方案
    132         /// </summary>
    133         public bool IsBack = false;
    134         /// <summary>
    135         /// 消费记录
    136         /// </summary>
    137         public List<Consumption.ConsumptionAbstract> ConsumptionList = new List<Consumption.ConsumptionAbstract>();
    138         /// <summary>
    139         /// 添加详细行程
    140         /// </summary>
    141         /// <param name="info"></param>
    142         public static void Insert(DetailScheduleInfoBase info, string CreateUserName)
    143         {
    144             Operater.InsertRec(Prefix, CollectionName, info, CreateUserName);
    145         }
    146         /// <summary>
    147         /// 通过序列号获得概要对象
    148         /// </summary>
    149         /// <param name="SN"></param>
    150         public static DetailScheduleInfoBase GetDetailScheduleBySN(string DetailScheduleSN)
    151         {
    152             var query = Query.EQ("_id", DetailScheduleSN);
    153             return Operater.GetFirstRec<DetailScheduleInfoBase>(CollectionName, query);
    154         }
    155         /// <summary>
    156         /// 更新概要对象
    157         /// </summary>
    158         /// <param name="newobj"></param>
    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         /// <summary>
    10         /// 移动开始地址
    11         /// </summary>
    12         public string MoveFromLocation = string.Empty;
    13         /// <summary>
    14         /// 移动终止地址
    15         /// </summary>
    16         public string MoveToLocation = string.Empty;
    17         /// <summary>
    18         /// 交通手段
    19         /// </summary>
    20         public TrafficTypeEnum TrafficType = TrafficTypeEnum.Walk;
    21         /// <summary>
    22         /// 交通手段枚举
    23         /// </summary>
    24         public enum TrafficTypeEnum
    25         {
    26             /// <summary>
    27             /// 步行
    28             /// </summary>
    29             Walk,
    30             /// <summary>
    31             /// 出租
    32             /// </summary>
    33             Taxi,
    34             /// <summary>
    35             /// 巴士
    36             /// </summary>
    37             Bus,
    38             /// <summary>
    39             /// 捷运
    40             /// </summary>
    41             JieYun,
    42             /// <summary>
    43             /// 台铁
    44             /// </summary>
    45             Train,
    46             /// <summary>
    47             /// 高铁
    48             /// </summary>
    49             SpeedTrain,
    50             /// <summary>
    51             /// 飞机
    52             /// </summary>
    53             AirPlane,
    54             /// <summary>
    55             /// 轮船
    56             /// </summary>
    57             Ship
    58         }
    59         /// <summary>
    60         /// 交通手段中文
    61         /// </summary>
    62         /// <param name="c"></param>
    63         /// <returns></returns>
    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  [BsonKnownTypes(typeof(Traffic), typeof(Visit), typeof(Shopping), typeof(Catering))]

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

    我们是否要将一个巨大的对象保存为一个数据文档(数据文档在MongoDB里面是一条记录的意思)

    一个巨大的对象,往往是一个树型结构的数据。

    例如,一个旅行行程表,它包含了一个行程概要,一个礼物列表,一个[每日行程列表],一个事前准备列表。

    每一个  [每日行程列表] 又包含了 【详细行程】列表,【详细行程】中又包含了【消费明细】等等。

    这样的话,一个行程就是一个巨大的对象(巨大的树形结构数据),当然,MongoDB是支持将整棵树作为一个数据文档,一下子保存到数据库中去的。

    (每一个数据文档的大小是有限制的,但是那个限制也是一个天文数字,我想没有人会把整部三国演义作为一个对象放到数据库里面去的吧)

    不过,这样对于数据库的管理将是一个巨大的灾难,这个灾难不仅仅是在数据进行更新时候,更新命令将会变得很复杂,而且会在各种聚合操作的时候,将会遍历所有的数据对象。

     这里使用了一种折衷的办法,将一个3层或者4层的结构,拆分为两个数据表,一个里面包含上面两层数据,然后做一个摘要结构,包含着下两层信息的一个简报。一些简单的信息直接从简版里面读取。

    下两层则放置在另一个数据库中。只有在需要详细数据的时候才读取出来呈现在界面上。

    这里的设计也就是一个数据存储的粒度的设计,粒度太小,就退化为关系型数据库。反之则每次更新将涉及整棵文档树。粒度设计是MongoDB的一个难点。

    2.将MongoDB作为文件服务器使用

    MongoDB的Grid File System特性就是一个内置的文件存储空间。你可以像管理数据一样管理文件。

    对于文件的读取,其实很简单

    C#驱动的Download和Upload方法提供了读取和保存文件的方法

     1         /// <summary>
     2         /// 保存文件
     3         /// </summary>
     4         /// <param name="file"></param>
     5         /// <param name="Username"></param>
     6         /// <returns></returns>
     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         /// <summary>
    15         /// 获得文件
    16         /// </summary>
    17         /// <param name="stream"></param>
    18         /// <param name="filename"></param>
    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类来处理

      <system.webServer>
        <handlers>
          <add name="ImageServerJPG" path="*.jpg" verb="GET" type="TaiWanTripPlanSite.ImageServer" />
          <add name="ImageServerGIF" path="*.gif" verb="GET" type="TaiWanTripPlanSite.ImageServer" />
          <add name="ImageServerPNG" path="*.png" verb="GET" type="TaiWanTripPlanSite.ImageServer" />
        </handlers>
      </system.webServer>

    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年前端经验的人应聘,我将会亲自带你们学习知识,提高技能,让你们实现自我价值。

  • 相关阅读:
    mysql主从只同步部分库或表
    springboot~aspect通过@annotation进行拦截
    ELK~fluentd将日志文件增量读到es里
    怎么自学 Java ?和盘托出
    一个后端开发的 Vue 笔记【入门级】
    企业级数据大屏设计如何实现
    Vue.js官方中文教程
    vue学习笔记 ---- 系列文章
    基于Asp.net core Kestrel的超迷你http服务器 ---- AServer
    Docker学习—概念及基本应用
  • 原文地址:https://www.cnblogs.com/TextEditor/p/4002262.html
Copyright © 2011-2022 走看看