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);
        }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM