zoukankan      html  css  js  c++  java
  • MongoDB在实际项目中的使用

    MongoDB简介

    MongoDB是近些年来流行起来的NoSql的代表,和传统数据库最大的区别是支持文档型数据库。
    当然,现在的一些数据库通过自定义复合类型,可变长数组等手段也可以模拟文档型数据库。
    例如在PostgreSQL中,以下是一个复合类型的例子

    CREATE TYPE complex AS (
        r       double precision,
        i       double precision
    );
    
    CREATE TYPE inventory_item AS (
        name            text,
        supplier_id     integer,
        price           numeric
    );
    

    数组的定义如下

    array[1,2]            --一维数组
    array[[1,2],[3,5]]  --二维数组
    

    当然,MongoDB生来就是文档型数据库,自然在应用层面对数据操作非常友好。

    • 使用了一套聚合框架来进行专业的聚合操作,和SQL语言相比,可以支持更加细致的操作
    • 可以使用JavaScript编写MapReduce函数进行数据统计操作,在分布式框架下适合处理大数据

    当然,你也可以将MongoDB当做普通的关系型数据库那样使用。但是这样就无法定义View(如果要使用View这样的功能,还是老老实实将MongoDB当做文档型数据库来使用吧)
    http://codesnippet.info/ 建站过程中,有些基础数据是简单的关系型数据,有些是缓存用文档型数据。

    MongoDB的管理工具

    这里我就推荐自己开发的工具MongoCola了。
    MongoCola项目Github地址
    这个项目从2011年到现在已经断断续续维持了5年了,从MongoDB1.0到MongoDB3.2,这个工具和MongoDB一起成长起来的。将近200个Star,最近又有两个两个朋友贡献了代码(现在使用C#开发Winform的人真的不多了),让我感到很欣慰。
    期间进行了一次比较大的重构(由于自己对于软件设计的理解的提高,以及从盲目的追求快速添加功能到追求整个项目代码的合理,才下决心进行了一次伤筋动骨的重构,当然现在再看,这次重构很重要,但是代码仍然还是有问题的,绝非完美。)
    在开发 www.codesnippet.info 的过程中,整个MONGODB数据库的查看都使用了这个工具,同时在使用中发现了一些BUG,也进行了一些改善。当然我现在也不敢保证BUG全部都清除干净了。如果发现BUG,请和我联系。
    原本打算使用Mono进行跨平台的,但是Mono对于Winform的支持并不好,所以虽然这个工具可以在Mac的OSX上运行,但是最好还是老老实实在Windows下运行比较好。发一张工具的界面图片,在OSX上截WIndows远程桌面的图。
    ![](http://codesnippet.info/FileSystem/Thumbnail?filename=00000001_20160501212726_屏幕快照 2016-05-01 下午9.24.42.png)

    C#驱动程序的再包装

    虽然官方的C#已经和不错了,虽然MongoDB原生支持ORM的。文档型对象就是OOP对象了。
    但是资深码农都会自己再写一些操作数据库的Helper方法。为自己定制的一套从EntityBase到ORM的方法。
    (关于时区的设定,其实可以在系列化设定中完成)

    using System;
    using MongoDB.Bson.Serialization.Attributes;
    
    namespace InfraStructure.DataBase
    {
        /// <summary>
        ///     实体
        ///     没有物理删除,所以自增SN是安全的
        /// </summary>
        public abstract class EntityBase
        {
            /// <summary>
            ///     创建时间
            /// </summary>
            [BsonDateTimeOptions(Kind = DateTimeKind.Local)] public DateTime CreateDateTime;
    
            /// <summary>
            ///     创建者
            ///     [这里可以是用户名,亦可是账号]
            /// </summary>
            public string CreateUser;
    
            /// <summary>
            ///     删除标记
            /// </summary>
            public bool IsDel;
    
            /// <summary>
            ///     序列号
            /// </summary>
            [BsonId] public string Sn;
    
            /// <summary>
            ///     更新时间
            /// </summary>
            [BsonDateTimeOptions(Kind = DateTimeKind.Local)] public DateTime UpdateDateTime;
    
            /// <summary>
            ///     更新者
            /// </summary>
            public string UpdateUser;
    
            /// <summary>
            ///     获得表名称
            /// </summary>
            /// <returns></returns>
            public abstract string GetCollectionName();
    
            /// <summary>
            ///     获得主键前缀
            /// </summary>
            /// <returns></returns>
            public abstract string GetPrefix();
    
            /// <summary>
            /// 序列号格式
            /// </summary>
            public const string SnFormat = "D8";
    
        }
    }
    

    ORM的增删改也无非就是将驱动的数据库操作重新定制了一下而已。
    具体代码可以在本网站的开源项目中找到
    数据库操作辅助类

    MongoDB的序列化设定 (干货)

    • 场景一:由于类定义变更,某个字段不需要了,但是如果不进行设定的话,发序列化会出错,某个数据库字段没有配置的实体字段(IgnoreExtraElementsConvention)
    • 场景二:在序列化的时候,为了节省空间,希望字段为空的时候,不进行序列化。(IgnoreIfNullConvention)
    • 场景三:日期型的数据,序列化的时候,希望可以指定时区(RegisterSerializer)
                    //http://mongodb.github.io/mongo-csharp-driver/1.10/serialization/
                    var pack = new ConventionPack();
                    pack.Add(new IgnoreExtraElementsConvention(true));
                    pack.Add(new IgnoreIfNullConvention(true));
                    ConventionRegistry.Register("CustomElementsConvention", pack, t => { return true; });
                    //DateTime Localize    
                    BsonSerializer.RegisterSerializer(typeof(DateTime), new DateTimeSerializer(DateTimeKind.Local));
                    return true;
    

    当然,你也可以对某个日期型字段单独指定时区,或者将某个字段定义为主键。详细请参考上文提到的 [BsonId] 和 [BsonDateTimeOptions(Kind = DateTimeKind.Local)] 特性。

    FileStorage

    MongoDB虽然可以使用FileSystem,但是由于是内存型数据库,可能会大量消耗内存资源。

    这里贴一下FileSystemHelper,但是不建议大型项目在没有足够资源的情况下使用!!

    using System;
    using System.Collections.Generic;
    using System.Web;
    using MongoDB.Driver.GridFS;
    using System.IO;
    using MongoDB.Driver;
    using System.Drawing.Imaging;
    using System.Drawing;
    
    namespace InfraStructure.Storage
    {
        public static class MongoStorage
        {
            /// <summary>
            ///     服务器
            /// </summary>
            private static MongoServer _innerServer;
            /// <summary>
            ///     链接字符串
            /// </summary>
            private static readonly string Connectionstring = @"mongodb://localhost:";
    
            /// <summary>
            ///     初始化MongoDB
            /// </summary>
            /// <param name="dbList">除去Logger以外</param>
            /// <param name="defaultDbName"></param>
            /// <param name="port"></param>
            /// <returns></returns>
            public static bool Init(string port = "28030")
            {
                try
                {
                    _innerServer = new MongoClient(Connectionstring + port).GetServer();
                    _innerServer.Connect();
                    return true;
                }
                catch (Exception)
                {
                    return false;
                }
            }
            /// <summary>
            ///     保存文件
            /// </summary>
            /// <param name="file"></param>
            /// <param name="ownerId"></param>
            /// <param name="databaseType"></param>
            /// <returns></returns>
            public static string InsertFile(HttpPostedFileBase file, string ownerId, string databaseType)
            {
                var mongofilename = ownerId + "_" + DateTime.Now.ToString("yyyyMMddHHmmss") + "_" + file.FileName;
                var innerFileServer = _innerServer.GetDatabase(databaseType);
                var gfs = innerFileServer.GetGridFS(new MongoGridFSSettings());
                gfs.Upload(file.InputStream, mongofilename);
                return mongofilename;
            }
            /// <summary>
            ///     保存文件
            /// </summary>
            /// <param name="file"></param>
            /// <param name="ownerId"></param>
            /// <param name="databaseType"></param>
            /// <returns></returns>
            public static string InsertFile(HttpPostedFile file, string ownerId, string databaseType)
            {
                return InsertFile(new HttpPostedFileWrapper(file) as HttpPostedFileBase, ownerId, databaseType);
            }
            /// <summary>
            /// 
            /// </summary>
            /// <param name="file"></param>
            /// <param name="fileName"></param>
            /// <param name="ownerId"></param>
            /// <param name="databaseType"></param>
            /// <returns></returns>
            public static string InsertFile(Stream file, string fileName, string ownerId, string databaseType)
            {
                var mongofilename = ownerId + "_" + DateTime.Now.ToString("yyyyMMddHHmmss") + "_" + fileName;
                var innerFileServer = _innerServer.GetDatabase(databaseType);
                var gfs = innerFileServer.GetGridFS(new MongoGridFSSettings());
                gfs.Upload(file, mongofilename);
                return mongofilename;
            }
            /// <summary>
            /// 保存流为固定文件名
            /// </summary>
            /// <param name="file"></param>
            /// <param name="fixedFilename"></param>
            /// <param name="databaseType"></param>
            public static void InsertStreamWithFixFileName(Stream file, string fixedFilename, string databaseType)
            {
                var innerFileServer = _innerServer.GetDatabase(databaseType);
                var gfs = innerFileServer.GetGridFS(new MongoGridFSSettings());
                gfs.Upload(file, fixedFilename);
            }
    
    
            /// <summary>
            ///     获得文件
            /// </summary>
            /// <param name="stream"></param>
            /// <param name="filename"></param>
            public static void GetFile(Stream stream, string filename, string databaseType)
            {
                var innerFileServer = _innerServer.GetDatabase(databaseType);
                var gfs = innerFileServer.GetGridFS(new MongoGridFSSettings());
                if (gfs.Exists(filename))
                {
                    gfs.Download(stream, filename);
                }
            }
            /// <summary>
            ///     用户上传图片
            /// </summary>
            /// <param name="file"></param>
            /// <param name="ownerId"></param>
            /// <param name="weight"></param>
            /// <param name="height"></param>
            /// <returns></returns>
            public static string InsertImage(HttpPostedFileBase file, string ownerId, int weight = 64, int height = 64)
            {
                var fileName = file.FileName;
                var originalImage = Image.FromStream(file.InputStream);
                var thumbImage = originalImage.GetThumbnailImage(weight, height, null, IntPtr.Zero);
                using (var ms = new MemoryStream())
                {
                    thumbImage.Save(ms, ImageFormat.Jpeg);
                    //必须将位置重置
                    ms.Position = 0;
                    fileName = InsertFile(ms, fileName, ownerId, string.Empty);
                }
                return fileName;
            }
            /// <summary>
            /// Mongo文件结构
            /// </summary>
            public struct DbFileInfo
            {
                /// <summary>
                /// 文件名
                /// </summary>
                public string FileName;
                /// <summary>
                /// 数据库文件名
                /// </summary>
                public string DbFileName;
            }
            /// <summary>
            /// 文件备份
            /// </summary>
            /// <param name="fileList">文件列表</param>
            /// <param name="path">备份路径。注意以斜线结尾</param>
            /// <param name="databaseType">数据库名称</param>
            public static void BackUpFiles(List<DbFileInfo> fileList, string path, string databaseType)
            {
                var innerFileServer = _innerServer.GetDatabase(databaseType);
                foreach (var item in fileList)
                {
                    var gfs = innerFileServer.GetGridFS(new MongoGridFSSettings());
                    gfs.Download(path + item.FileName, item.DbFileName);
                }
            }
        }
    }
    
    

    全文检索

    如果您的项目是英文项目,可以不需要第三方库,直接在MongoDB中完成全文检索。前提条件是为数据集设定文本索引
    如果您的项目是中文项目,必须使用企业版的MongoDB,加上第三方的分词库,才能实现全文检索
    http://codesnippet.info/ 使用的是ElasticSearch进行全文检索的。
    使用NEST操作ElasticSearch进行全文检索
    全文检索的索引设定和使用:

            /// <summary>
            ///     设置Text索引
            /// </summary>
            /// <param name="collectionName"></param>
            /// <param name="FieldName"></param>
            /// <param name="database"></param>
            public static void SetTextIndex(string collectionName, string FieldName, string database = "")
            {
                if (string.IsNullOrEmpty(database)) database = _defaultDatabaseName;
                MongoCollection col = GetDatabaseByType(database).GetCollection(collectionName);
                if (col.IndexExistsByName(FieldName))
                {
                    return;
                }
                var option = new IndexOptionsBuilder();
                option.SetName(FieldName);
                var indexkeys = new IndexKeysBuilder();
                indexkeys.Text(new string[] { FieldName });
                col.CreateIndex(indexkeys, option);
            }
    
            /// <summary>
            /// 全文检索
            /// </summary>
            /// <param name="collectionName"></param>
            /// <param name="key"></param>
            /// <param name="caseSensitive"></param>
            /// <param name="limit"></param>
            /// <returns></returns>
            public static List<BsonDocument> SearchText(string collectionName, string key, bool caseSensitive, int limit, IMongoQuery query = null)
            {
                //检索关键字
                var textSearchOption = new TextSearchOptions();
                textSearchOption.CaseSensitive = caseSensitive;
                var textSearchQuery = Query.Text(key, textSearchOption);
                if (query != null)
                {
                    textSearchQuery = Query.And(textSearchQuery, query);
                }
                MongoCollection col = GetDatabaseByType(_defaultDatabaseName).GetCollection(collectionName);
                var result = col.FindAs<BsonDocument>(textSearchQuery);
                var resultDocumentList = result.SetLimit(limit).ToList();
                return resultDocumentList;
            }
    

    内置的聚合操作

    MongoDB提供了一些内置的聚合函数,通过驱动程序可以直接使用

            /// <summary>
            /// Distinct
            /// </summary>
            /// <param name="collectionName"></param>
            /// <param name="FieldName"></param>
            /// <param name="query"></param>
            /// <returns></returns>
            public static List<string> Distinct(string collectionName, string FieldName, IMongoQuery query = null)
            {
                MongoCollection col = GetDatabaseByType(_defaultDatabaseName).GetCollection(collectionName);
                var DistinctResult = col.Distinct(FieldName, query);
                var result = new List<string>();
                foreach (BsonValue item in DistinctResult)
                {
                    result.Add(item.AsString);
                }
                return result;
            }
    

    使用Javascript进行聚合操作

    MongoDB是可以使用js进行聚合操作的。由于MongoDB内置了Google的V8引擎,所以可以通过运行自定义的Js片段来进行一些聚合操作。

          /// <summary>
            /// GroupByCount
            /// </summary>
            /// <param name="collectionName"></param>
            /// <param name="FieldName"></param>
            /// <returns></returns>
            public static Dictionary<string, int> GroupCount(string collectionName, string FieldName, IMongoQuery query = null)
            {
                MongoCollection col = GetDatabaseByType(_defaultDatabaseName).GetCollection(collectionName);
                GroupArgs g = new GroupArgs();
                var groupdoc = new GroupByDocument();
                groupdoc.Add(FieldName, true);
                g.KeyFields = groupdoc;
                g.ReduceFunction = new BsonJavaScript("function(obj,prev){ prev.count++;}");
                g.Initial = new BsonDocument().Add("count", 0);
                if (query != null)
                {
                    g.Query = query;
                }
                var GroupResult = col.Group(g);
                var result = new Dictionary<string, int>();
                foreach (BsonDocument item in GroupResult)
                {
                    result.Add(item.GetElement(FieldName).Value.ToString(), (int)item.GetElement("count").Value.AsDouble);
                }
                return result;
            }
    

    TTL索引

    使用MongoDB的TTL(TimeToLive)索引,可以实现定时缓存功能,数据经过指定时间后就自动从数据库里面删除。
    很多缓存数据现在就是用TTL索引实现15分钟自动清除操作的。

            /// <summary>
            ///     设定数据缓存时间(以创建时间为基础)
            /// </summary>
            /// <param name="collectionName"></param>
            /// <param name="ExpiresMinute"></param>
            /// <param name="database"></param>
            public static void SetCacheTime(string collectionName, int ExpiresMinute, string database = "")
            {
                if (string.IsNullOrEmpty(database)) database = _defaultDatabaseName;
                MongoCollection col = GetDatabaseByType(database).GetCollection(collectionName);
                if (col.IndexExistsByName("Cache"))
                {
                    col.DropIndexByName("Cache");
                }
                var option = new IndexOptionsBuilder();
                option.SetTimeToLive(new TimeSpan(0, ExpiresMinute, 0));
                option.SetName("Cache");
                var indexkeys = new IndexKeysBuilder();
                indexkeys.Ascending(new string[] { nameof(EntityBase.CreateDateTime) });
                col.CreateIndex(indexkeys, option);
            }
    
  • 相关阅读:
    Elastic的should + bool 多字段完全匹配或查找
    MySQL慢日志
    Elastic的IN查找
    Elastic的字符串查找
    JavaScript获取当前时间戳
    原码, 反码, 补码学习笔记
    PHP渲染压缩HTML
    JavaScript的深拷贝
    JavaScript的变量的let const var的区别
    关于一个值该不该default null的判定
  • 原文地址:https://www.cnblogs.com/TextEditor/p/5454168.html
Copyright © 2011-2022 走看看