黄东红从军无悔 發表於 2016-5-3 10:53:00

MongoDB在实际项目中的使用

<h1 id="mongodb简介">MongoDB简介</h1>
<p>MongoDB是近些年来流行起来的NoSql的代表,和传统数据库最大的区别是支持文档型数据库。<br>
当然,现在的一些数据库通过自定义复合类型,可变长数组等手段也可以模拟文档型数据库。<br>
例如在PostgreSQL中,以下是一个复合类型的例子</p>
<pre><code class="language-sql">CREATE TYPE complex AS (
    r       double precision,
    i       double precision
);

CREATE TYPE inventory_item AS (
    name            text,
    supplier_id   integer,
    price         numeric
);
</code></pre>
<p>数组的定义如下</p>
<pre><code class="language-sql">array            --一维数组
array[,]--二维数组
</code></pre>
<p>当然,MongoDB生来就是文档型数据库,自然在应用层面对数据操作非常友好。</p>
<ul>
<li>使用了一套聚合框架来进行专业的聚合操作,和SQL语言相比,可以支持更加细致的操作</li>
<li>可以使用JavaScript编写MapReduce函数进行数据统计操作,在分布式框架下适合处理大数据</li>
</ul>
<p>当然,你也可以将MongoDB当做普通的关系型数据库那样使用。但是这样就无法定义View(如果要使用View这样的功能,还是老老实实将MongoDB当做文档型数据库来使用吧)<br>
在 http://codesnippet.info/ 建站过程中,有些基础数据是简单的关系型数据,有些是缓存用文档型数据。</p>
<h1 id="mongodb的管理工具">MongoDB的管理工具</h1>
<p>这里我就推荐自己开发的工具MongoCola了。<br>
MongoCola项目Github地址<br>
这个项目从2011年到现在已经断断续续维持了5年了,从MongoDB1.0到MongoDB3.2,这个工具和MongoDB一起成长起来的。将近200个Star,最近又有两个两个朋友贡献了代码(现在使用C#开发Winform的人真的不多了),让我感到很欣慰。<br>
期间进行了一次比较大的重构(由于自己对于软件设计的理解的提高,以及从盲目的追求快速添加功能到追求整个项目代码的合理,才下决心进行了一次伤筋动骨的重构,当然现在再看,这次重构很重要,但是代码仍然还是有问题的,绝非完美。)<br>
在开发 www.codesnippet.info 的过程中,整个MONGODB数据库的查看都使用了这个工具,同时在使用中发现了一些BUG,也进行了一些改善。当然我现在也不敢保证BUG全部都清除干净了。如果发现BUG,请和我联系。<br>
原本打算使用Mono进行跨平台的,但是Mono对于Winform的支持并不好,所以虽然这个工具可以在Mac的OSX上运行,但是最好还是老老实实在Windows下运行比较好。发一张工具的界面图片,在OSX上截WIndows远程桌面的图。<br>
![](http://codesnippet.info/FileSystem/Thumbnail?filename=00000001_20160501212726_屏幕快照 2016-05-01 下午9.24.42.png)</p>
<h1 id="c驱动程序的再包装">C#驱动程序的再包装</h1>
<p>虽然官方的C#已经和不错了,虽然MongoDB原生支持ORM的。文档型对象就是OOP对象了。<br>
但是资深码农都会自己再写一些操作数据库的Helper方法。为自己定制的一套从EntityBase到ORM的方法。<br>
(关于时区的设定,其实可以在系列化设定中完成)</p>
<pre><code class="language-csharp">using System;
using MongoDB.Bson.Serialization.Attributes;

namespace InfraStructure.DataBase
{
    /// &lt;summary&gt;
    ///   实体
    ///   没有物理删除,所以自增SN是安全的
    /// &lt;/summary&gt;
    public abstract class EntityBase
    {
      /// &lt;summary&gt;
      ///   创建时间
      /// &lt;/summary&gt;
       public DateTime CreateDateTime;

      /// &lt;summary&gt;
      ///   创建者
      ///   [这里可以是用户名,亦可是账号]
      /// &lt;/summary&gt;
      public string CreateUser;

      /// &lt;summary&gt;
      ///   删除标记
      /// &lt;/summary&gt;
      public bool IsDel;

      /// &lt;summary&gt;
      ///   序列号
      /// &lt;/summary&gt;
       public string Sn;

      /// &lt;summary&gt;
      ///   更新时间
      /// &lt;/summary&gt;
       public DateTime UpdateDateTime;

      /// &lt;summary&gt;
      ///   更新者
      /// &lt;/summary&gt;
      public string UpdateUser;

      /// &lt;summary&gt;
      ///   获得表名称
      /// &lt;/summary&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public abstract string GetCollectionName();

      /// &lt;summary&gt;
      ///   获得主键前缀
      /// &lt;/summary&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public abstract string GetPrefix();

      /// &lt;summary&gt;
      /// 序列号格式
      /// &lt;/summary&gt;
      public const string SnFormat = "D8";

    }
}
</code></pre>
<p>ORM的增删改也无非就是将驱动的数据库操作重新定制了一下而已。<br>
具体代码可以在本网站的开源项目中找到<br>
数据库操作辅助类</p>
<h1 id="mongodb的序列化设定-干货">MongoDB的序列化设定 (干货)</h1>
<ul>
<li>场景一:由于类定义变更,某个字段不需要了,但是如果不进行设定的话,发序列化会出错,某个数据库字段没有配置的实体字段(IgnoreExtraElementsConvention)</li>
<li>场景二:在序列化的时候,为了节省空间,希望字段为空的时候,不进行序列化。(IgnoreIfNullConvention)</li>
<li>场景三:日期型的数据,序列化的时候,希望可以指定时区(RegisterSerializer)</li>
</ul>
<pre><code class="language-csharp">                //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 =&gt; { return true; });
                //DateTime Localize   
                BsonSerializer.RegisterSerializer(typeof(DateTime), new DateTimeSerializer(DateTimeKind.Local));
                return true;
</code></pre>
<p>当然,你也可以对某个日期型字段单独指定时区,或者将某个字段定义为主键。详细请参考上文提到的 和 特性。</p>
<h1 id="filestorage">FileStorage</h1>
<p>MongoDB虽然可以使用FileSystem,但是由于是内存型数据库,可能会大量消耗内存资源。</p>
<p>这里贴一下FileSystemHelper,但是不建议大型项目在没有足够资源的情况下使用!!</p>
<pre><code class="language-csharp">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
    {
      /// &lt;summary&gt;
      ///   服务器
      /// &lt;/summary&gt;
      private static MongoServer _innerServer;
      /// &lt;summary&gt;
      ///   链接字符串
      /// &lt;/summary&gt;
      private static readonly string Connectionstring = @"mongodb://localhost:";

      /// &lt;summary&gt;
      ///   初始化MongoDB
      /// &lt;/summary&gt;
      /// &lt;param name="dbList"&gt;除去Logger以外&lt;/param&gt;
      /// &lt;param name="defaultDbName"&gt;&lt;/param&gt;
      /// &lt;param name="port"&gt;&lt;/param&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public static bool Init(string port = "28030")
      {
            try
            {
                _innerServer = new MongoClient(Connectionstring + port).GetServer();
                _innerServer.Connect();
                return true;
            }
            catch (Exception)
            {
                return false;
            }
      }
      /// &lt;summary&gt;
      ///   保存文件
      /// &lt;/summary&gt;
      /// &lt;param name="file"&gt;&lt;/param&gt;
      /// &lt;param name="ownerId"&gt;&lt;/param&gt;
      /// &lt;param name="databaseType"&gt;&lt;/param&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      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;
      }
      /// &lt;summary&gt;
      ///   保存文件
      /// &lt;/summary&gt;
      /// &lt;param name="file"&gt;&lt;/param&gt;
      /// &lt;param name="ownerId"&gt;&lt;/param&gt;
      /// &lt;param name="databaseType"&gt;&lt;/param&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public static string InsertFile(HttpPostedFile file, string ownerId, string databaseType)
      {
            return InsertFile(new HttpPostedFileWrapper(file) as HttpPostedFileBase, ownerId, databaseType);
      }
      /// &lt;summary&gt;
      ///
      /// &lt;/summary&gt;
      /// &lt;param name="file"&gt;&lt;/param&gt;
      /// &lt;param name="fileName"&gt;&lt;/param&gt;
      /// &lt;param name="ownerId"&gt;&lt;/param&gt;
      /// &lt;param name="databaseType"&gt;&lt;/param&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      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;
      }
      /// &lt;summary&gt;
      /// 保存流为固定文件名
      /// &lt;/summary&gt;
      /// &lt;param name="file"&gt;&lt;/param&gt;
      /// &lt;param name="fixedFilename"&gt;&lt;/param&gt;
      /// &lt;param name="databaseType"&gt;&lt;/param&gt;
      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);
      }


      /// &lt;summary&gt;
      ///   获得文件
      /// &lt;/summary&gt;
      /// &lt;param name="stream"&gt;&lt;/param&gt;
      /// &lt;param name="filename"&gt;&lt;/param&gt;
      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);
            }
      }
      /// &lt;summary&gt;
      ///   用户上传图片
      /// &lt;/summary&gt;
      /// &lt;param name="file"&gt;&lt;/param&gt;
      /// &lt;param name="ownerId"&gt;&lt;/param&gt;
      /// &lt;param name="weight"&gt;&lt;/param&gt;
      /// &lt;param name="height"&gt;&lt;/param&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      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;
      }
      /// &lt;summary&gt;
      /// Mongo文件结构
      /// &lt;/summary&gt;
      public struct DbFileInfo
      {
            /// &lt;summary&gt;
            /// 文件名
            /// &lt;/summary&gt;
            public string FileName;
            /// &lt;summary&gt;
            /// 数据库文件名
            /// &lt;/summary&gt;
            public string DbFileName;
      }
      /// &lt;summary&gt;
      /// 文件备份
      /// &lt;/summary&gt;
      /// &lt;param name="fileList"&gt;文件列表&lt;/param&gt;
      /// &lt;param name="path"&gt;备份路径。注意以斜线结尾&lt;/param&gt;
      /// &lt;param name="databaseType"&gt;数据库名称&lt;/param&gt;
      public static void BackUpFiles(List&lt;DbFileInfo&gt; 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);
            }
      }
    }
}

</code></pre>
<h1 id="全文检索">全文检索</h1>
<p>如果您的项目是英文项目,可以不需要第三方库,直接在MongoDB中完成全文检索。前提条件是为数据集设定文本索引<br>
如果您的项目是中文项目,必须使用企业版的MongoDB,加上第三方的分词库,才能实现全文检索<br>
http://codesnippet.info/ 使用的是ElasticSearch进行全文检索的。<br>
使用NEST操作ElasticSearch进行全文检索<br>
全文检索的索引设定和使用:</p>
<pre><code class="language-csharp">      /// &lt;summary&gt;
      ///   设置Text索引
      /// &lt;/summary&gt;
      /// &lt;param name="collectionName"&gt;&lt;/param&gt;
      /// &lt;param name="FieldName"&gt;&lt;/param&gt;
      /// &lt;param name="database"&gt;&lt;/param&gt;
      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);
      }

      /// &lt;summary&gt;
      /// 全文检索
      /// &lt;/summary&gt;
      /// &lt;param name="collectionName"&gt;&lt;/param&gt;
      /// &lt;param name="key"&gt;&lt;/param&gt;
      /// &lt;param name="caseSensitive"&gt;&lt;/param&gt;
      /// &lt;param name="limit"&gt;&lt;/param&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public static List&lt;BsonDocument&gt; 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&lt;BsonDocument&gt;(textSearchQuery);
            var resultDocumentList = result.SetLimit(limit).ToList();
            return resultDocumentList;
      }
</code></pre>
<h1 id="内置的聚合操作">内置的聚合操作</h1>
<p>MongoDB提供了一些内置的聚合函数,通过驱动程序可以直接使用</p>
<pre><code class="language-csharp">      /// &lt;summary&gt;
      /// Distinct
      /// &lt;/summary&gt;
      /// &lt;param name="collectionName"&gt;&lt;/param&gt;
      /// &lt;param name="FieldName"&gt;&lt;/param&gt;
      /// &lt;param name="query"&gt;&lt;/param&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public static List&lt;string&gt; Distinct(string collectionName, string FieldName, IMongoQuery query = null)
      {
            MongoCollection col = GetDatabaseByType(_defaultDatabaseName).GetCollection(collectionName);
            var DistinctResult = col.Distinct(FieldName, query);
            var result = new List&lt;string&gt;();
            foreach (BsonValue item in DistinctResult)
            {
                result.Add(item.AsString);
            }
            return result;
      }
</code></pre>
<h1 id="使用javascript进行聚合操作">使用Javascript进行聚合操作</h1>
<p>MongoDB是可以使用js进行聚合操作的。由于MongoDB内置了Google的V8引擎,所以可以通过运行自定义的Js片段来进行一些聚合操作。</p>
<pre><code class="language-csharp">      /// &lt;summary&gt;
      /// GroupByCount
      /// &lt;/summary&gt;
      /// &lt;param name="collectionName"&gt;&lt;/param&gt;
      /// &lt;param name="FieldName"&gt;&lt;/param&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public static Dictionary&lt;string, int&gt; 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&lt;string, int&gt;();
            foreach (BsonDocument item in GroupResult)
            {
                result.Add(item.GetElement(FieldName).Value.ToString(), (int)item.GetElement("count").Value.AsDouble);
            }
            return result;
      }
</code></pre>
<h1 id="ttl索引">TTL索引</h1>
<p>使用MongoDB的TTL(TimeToLive)索引,可以实现定时缓存功能,数据经过指定时间后就自动从数据库里面删除。<br>
很多缓存数据现在就是用TTL索引实现15分钟自动清除操作的。</p>
<pre><code class="language-csharp">      /// &lt;summary&gt;
      ///   设定数据缓存时间(以创建时间为基础)
      /// &lt;/summary&gt;
      /// &lt;param name="collectionName"&gt;&lt;/param&gt;
      /// &lt;param name="ExpiresMinute"&gt;&lt;/param&gt;
      /// &lt;param name="database"&gt;&lt;/param&gt;
      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);
      }
</code></pre><br><br>
来源:https://www.cnblogs.com/TextEditor/p/5454168.html
頁: [1]
查看完整版本: MongoDB在实际项目中的使用