NoSQL是泛指非關係型的資料庫,現今在我們的項目中也多有使用,其獨特的優點為我們的項目架構帶來了不少亮點,而我們這裡的主角(MongoDB)則是NoSQL資料庫家族中的一種。事實上,NoSQL資料庫的應用場景有很多,其最主要的目的就是為了能解決大規模數據集合多重數據種類帶來的困難,及大數據應用的難... ...
NoSQL是泛指非關係型的資料庫,現今在我們的項目中也多有使用,其獨特的優點為我們的項目架構帶來了不少亮點,而我們這裡的主角(MongoDB)則是NoSQL資料庫家族中的一種。事實上,NoSQL資料庫的應用場景有很多,其最主要的目的就是為了能解決大規模數據集合多重數據種類帶來的困難,及大數據應用的難題。
MongoDB
MongoDB是一個開源的文檔型資料庫,由C++語言編寫,採用分散式的文件存儲方案,而文件的存儲格式為BSON。MongoDB支持的數據類型有很多種,如:String、Int、Float、Timestamp、Binary、Object、Date、Arrays等。而MongoDB的特點也有很多,如:強大的查詢語言、支持索引、支持自動處理碎片、支持JAVA/C#/Python等多種開發語言、支持面向集合存儲等。關於MongoDB的具體描述也可自行到其官網查看,我這裡就不再過多啰嗦了,下麵貼出與其幾個相關的網址吧:
MongoDB官網:https://www.mongodb.com/
MongoDB官方的.NET API文檔:http://api.mongodb.com/csharp/current/html/R_Project_CSharpDriverDocs.htm
MongoDB官方的github網址:https://github.com/mongodb/mongo
MongoDB可視化管理工具NoSQL Manager for MongoDB的官網:https://www.mongodbmanager.com/
背景
在大部分的項目中都會遇到要將短消息類型的數據(比如企業系統內的用戶操作行為的“操作日記”數據、聊天通訊系統內的“對話記錄”等數據)做持久化,而這種數據的特征就是關係簡單、數據量龐大、大部分只有讀寫操作等。基於這些業務需要與應用場景,那麼我們就可以考率使用MongoDB來做數據的持久化存儲與管理了。
在這裡,我們將模擬一個基於Saas平臺下的企業系統內“操作日記”的業務場景(項目將以Saas服務提供給各個企業使用,並將所有用戶在系統上的操作行為以日記方式存儲到資料庫去),並使用MongoDB來做數據的持久化存儲與管理(包含對數據的增加與查詢操作)。這裡我們為了簡便則將代碼的結構層次分為兩層(實際項目中各位按需分層級),那麼項目結構將是這個樣子的:
1、Lezhima.Web:接受來自客戶端的請求,及服務端響應的出入口。由一個基於ASP.NET Core的MVC項目組成。
2、Lezhima.Data:直接跟MongoDB進行通訊交互,實現對DB的增、查等操作。由一個基於.NET Core的類庫組成。
業務規則:
基於上述的應用場景所悉,我們將面向的是企業客戶,且是以Saas服務運行在雲上的,則我們操作管理MongoDB時將面臨如下幾個規則(基於海量數據的讀寫考率):
1、希望數據能按企業ID分庫,即各個企業的數據歸檔到其對應的獨立庫中。
2、能根據日期每天分表存儲數據。
3、分庫能按企業ID自動完成,分表能按日期自動完成。
與MongoDB通訊的API類庫:
我們將使用由MongoDB官方提供的API類庫來與MongoDB進行通訊與交互,在Lezhima.Data項目中可通過NuGet管理器引用如下兩個類庫:
1、MongoDB.Bson
2、MongoDB.Driver
實現代碼
通過上面的介紹,我們清楚了兩個分層之間的功能與關係,那麼接下來我們就分別來看看它們具體的代碼,及操作MongoDB的簡便酷爽吧。
1、我們先看看Lezhima.Data層的代碼,首先定義一個名為“MongoDbContext”類,用於管理MongoDB的上下文,代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Runtime.InteropServices; 5 using System.Security.Authentication; 6 using System.Text.RegularExpressions; 7 using System.Threading.Tasks; 8 using MongoDB.Bson; 9 using MongoDB.Driver; 10 11 namespace Lezhima.Data.Context 12 { 13 /// <summary> 14 /// MongoDB對象的上下文 15 /// </summary> 16 public class MongoDbContext 17 { 18 19 /// <summary> 20 /// 緩存指定庫中表對象(集合) 21 /// </summary> 22 public static Dictionary<string, object> _collectionsMongoDb; 23 24 25 /// <summary> 26 /// Mongo上下文 27 /// </summary> 28 public IMongoDatabase DbContext { get; } 29 30 31 /// <summary> 32 /// 初始化MongoDb數據上下文 33 /// 將資料庫名傳遞進來 34 /// </summary> 35 public MongoDbContext(string dbName) 36 { 37 //連接字元串,如:"mongodb://username:password@host:port/[DatabaseName]?ssl=true" 38 //建議放在配置文件中 39 var connectionString = "mongodb://root:[email protected]:27017"; 40 try 41 { 42 var mongoClient = new MongoClient(connectionString); 43 //資料庫如果不存在,會自動創建 44 DbContext = mongoClient.GetDatabase(dbName); 45 } 46 catch (Exception e) 47 { 48 Log.WriteLogByError("構建MongoDbContext出錯", e); 49 throw; 50 } 51 } 52 53 /// <summary> 54 /// 非同步獲取表(集合) 55 /// </summary> 56 /// <typeparam name="TEntity"></typeparam> 57 /// <param name="datetime"></param> 58 /// <returns></returns> 59 public async Task<IMongoCollection<TEntity>> GetCollectionAsync<TEntity>(string tableName="") where TEntity : class 60 { 61 // 集合緩存如果為空,那麼創建一個 62 if (_collectionsMongoDb == null) 63 { 64 _collectionsMongoDb = new Dictionary<string, object>(); 65 } 66 67 var dt = DateTime.Now.ToString("yyyy -MM-dd"); 68 69 if (!string.IsNullOrEmpty(tableName)) 70 { 71 72 dt = tableName; 73 } 74 75 // 獲取集合名稱,使用的標準是在實體類型名後添加日期 76 var collectionName = dt; 77 78 // 如果集合不存在,那麼創建集合 79 if (false == await IsCollectionExistsAsync<TEntity>(collectionName)) 80 { 81 await DbContext.CreateCollectionAsync(collectionName); 82 } 83 84 // 如果緩存中沒有該集合,那麼加入緩存 85 if (!_collectionsMongoDb.ContainsKey(collectionName)) 86 { 87 _collectionsMongoDb[collectionName] = DbContext.GetCollection<TEntity>(collectionName); 88 } 89 90 // 從緩存中取出集合返回 91 return (IMongoCollection<TEntity>)_collectionsMongoDb[collectionName]; 92 } 93 94 95 /// <summary> 96 /// 集合是否存在 97 /// </summary> 98 /// <typeparam name="TEntity"></typeparam> 99 /// <returns></returns> 100 public async Task<bool> IsCollectionExistsAsync<TEntity>(string name) 101 { 102 var filter = new BsonDocument("name", name); 103 // 通過集合名稱過濾 104 var collections = await DbContext.ListCollectionsAsync(new ListCollectionsOptions { Filter = filter }); 105 // 檢查是否存在 106 return await collections.AnyAsync(); 107 } 108 } 109 }
2、在Lezhima.Data層增加一個名為“IMongoRepository”介面,用於封裝向業務層提供操作MongoDB的操作方法,代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Linq.Expressions; 5 using System.Text; 6 using System.Threading.Tasks; 7 using MongoDB.Bson; 8 using MongoDB.Driver; 9 10 namespace Lezhima.Data.Interface 11 { 12 public interface IMongoRepository<T> where T : class 13 { 14 /// <summary> 15 /// 從指定的庫與表中獲取指定條件的數據 16 /// </summary> 17 /// <returns></returns> 18 Task<List<T>> GetListAsync(Expression<Func<T, bool>> predicate, string dbName, string tableName = ""); 19 20 /// <summary> 21 /// 對指定的庫與表中新增數據 22 /// </summary> 23 /// <returns></returns> 24 Task<bool> Add(List<T> list, string dbName, string tableName = ""); 25 } 26 } 27
3、在Lezhima.Data層再增加一個名為“MongoRepository”類,實現“IMongoRepository”介面,代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Linq.Expressions; 5 using System.Threading.Tasks; 6 using System.Web; 7 using Lezhima.Data.Context; 8 using Lezhima.Data.Interface; 9 using MongoDB.Bson; 10 using MongoDB.Driver; 11 12 13 namespace Lezhima.Data 14 { 15 /// <summary> 16 /// 封裝向業務層提供操作MongoDB的操作方法 17 /// </summary> 18 /// <typeparam name="T"></typeparam> 19 public class MongoRepository<T> : IMongoRepository<T> where T : class 20 { 21 /// <summary> 22 /// 從指定的庫與表中獲取指定條件的數據 23 /// </summary> 24 /// <returns></returns> 25 public async Task<List<T>> GetListAsync(Expression<Func<T, bool>> predicate, string dbName, string tableName) 26 { 27 var dbContext = new MongoDbContext(dbName); 28 var collection = await dbContext.GetCollectionAsync<T>(tableName); 29 return collection.AsQueryable<T>().Where(predicate).ToList(); 30 } 31 32 33 /// <summary> 34 /// 對指定的庫與表中新增數據 35 /// </summary> 36 /// <returns></returns> 37 public async Task<bool> Add(List<T> list, string dbName, string tableName = "") 38 { 39 var dbContext = new MongoDbContext(dbName); 40 var collection = await dbContext.GetCollectionAsync<T>(tableName); 41 await collection.InsertManyAsync(list); 42 return true; 43 } 44 } 45 } 46
4、在Lezhima.Web層再增加一個名為“TestController”的控制器,用於向用戶提供測試讀寫MongoDB操作的出入口,代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Threading.Tasks; 5 using Lezhima.Core; 6 using Lezhima.Data.Interface; 7 using Microsoft.AspNetCore.Mvc; 8 9 namespace Lezhima.Web.Controllers 10 { 11 [Route("api/[controller]")] 12 public class TestController : Controller 13 { 14 private readonly IMongoRepository<ActionLog> _IMongoRepository; 15 16 public TestController(IMongoRepository<ActionLog> __IMongoRepository) 17 { 18 _IMongoRepository = __IMongoRepository; 19 } 20 21 /// <summary> 22 /// 測試新增數據方法 23 /// </summary> 24 /// <returns></returns> 25 [HttpGet] 26 public async Task<string> Add() 27 { 28 //創建兩個不同企業ID的實體數據 29 var model1 = new ActionLog(); 30 model1.CompanyId = Guid.Parse("B29BC831-A974-4114-90E2-0001E03FBCAF"); 31 model1.ActionLogId = Guid.NewGuid(); 32 model1.Context = "測試企業1"; 33 model1.CreateTime = DateTime.Now; 34 model1.UpdateTime = DateTime.Now; 35 36 var model2 = new ActionLog(); 37 model2.CompanyId = Guid.Parse("651bbe49-a4c8-4514-babb-897dad7065e3"); 38 model2.ActionLogId = Guid.NewGuid(); 39 model2.Context = "測試企業2"; 40 model2.CreateTime = DateTime.Now; 41 model2.UpdateTime = DateTime.Now; 42 43 44 var list = new List<ActionLog>(); 45 list.Add(model1); 46 list.Add(model2); 47 48 var group_list = list.GroupBy(p => p.CompanyId); 49 var tableName = "ActionLog_" + DateTime.Now.ToString("yyyy-MM-dd"); 50 foreach (var group in group_list) 51 { 52 var dbName = "ActionLog_" + group.FirstOrDefault().CompanyId.ToString(); 53 54 await _IMongoRepository.Add(group.ToList(), dbName, tableName); 55 } 56 57 return "value1"; 58 } 59 60 /// <summary> 61 /// 測試查詢方法 62 /// </summary> 63 /// <param name="companyId"></param> 64 /// <returns></returns> 65 [HttpGet("{companyId}")] 66 public async Task<List<ActionLog>> Get(Guid companyId) 67 { 68 var dbName = "ActionLog_" + companyId.ToString(); 69 var tableName = "ActionLog_" + DateTime.Now.ToString("yyyy-MM-dd"); 70 var list = await _IMongoRepository.GetListAsync(p => p.Context.IndexOf("測試企業") > -1, dbName, tableName); 71 return list; 72 } 73 74 } 75 } 76
總結
1、MongoDB是開源的文檔型非關係型資料庫,支持JAVA/C#/Python等多種開發語言。
2、通過由MongoDB官方提供的兩個API類庫實現跟MongoDB通訊交互。
3、MongoDB不需要提前創建資料庫與表結構,其會通過傳遞進去的數據結構自動判斷是否需要維護庫與表的結構,這個機制對我們項目的大部分場景都很實用。
4、最後再通過對Lezhima.Data層簡單的封裝後,使得面向業務層的調用非常簡便。
聲明
本文為作者原創,轉載請備註出處與保留原文地址,謝謝。如文章能給您帶來幫助,請點下推薦或關註,感謝您的支持!