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 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:a123@192.168.1.6: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 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 87 return DbContext.GetCollection<TEntity>(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层简单的封装后,使得面向业务层的调用非常简便。
声明
本文为作者原创,转载请备注出处与保留原文地址,谢谢。如文章能给您带来帮助,请点下推荐或关注,感谢您的支持!