SqlSugar的開發框架本身主要是基於常規關係型資料庫設計的框架,支援多種資料庫型別的接入,如SqlServer、MySQL、Oracle、PostgreSQL、SQLite等資料庫,非關係型資料庫的MongoDB資料庫也可以作為擴充套件整合到開發框架裡面,通過基礎類別的繼承關係很好的封裝了相關的基礎操作功能,極大的減少相關處理MongoDB的程式碼,並提供很好的開發效率。本篇隨筆介紹如何在SqlSugar的開發框架整合MongoDB資料庫的開發。
MongoDB是一款由C++編寫的高效能、開源、無模式的常用非關係型資料庫產品,是非關聯式資料庫當中功能最豐富、最像關聯式資料庫的資料庫。它擴充套件了關係型資料庫的眾多功能,例如:輔助索引、範圍查詢、排序等。
MongoDB 是一個介於關聯式資料庫和非關聯式資料庫之間的產品,是非關聯式資料庫當中功能最豐富,最像關聯式資料庫的。它支援的資料結構非常鬆散,是類似Json的Bson格式,因此可以儲存比較複雜的資料型別。
MongoDB 最大的特點是它支援的查詢語言非常強大,其語法有點類似於物件導向的查詢語言,幾乎可以實現類似關聯式資料庫單表查詢的絕大部分功能,而且還支援對資料建立索引。並且MongoDB-4.2版本開始已經支援分散式事務功能。
MongoDB資料庫有幾個簡單的概念需要了解一下。
1)MongoDB中的 database
有著和我們熟知的"資料庫"一樣的概念 (對 Oracle 來說就是 schema)。一個 MongoDB 範例中,可以有零個或多個資料庫,每個都作為一個高等容器,用於儲存資料。
2)資料庫中可以有零個或多個 collections
(集合)。集合和傳統意義上的 table 基本一致,可以簡單的把兩者看成是一樣的東西。
3)集合是由零個或多個 documents
(檔案)組成。同樣,一個檔案可以看成是一 row
。
4)檔案是由零個或多個 fields
(欄位)組成。,對應的就是關聯式資料庫的 columns
。
5)Indexes
(索引)在 MongoDB 中扮演著和它們在 RDBMS 中一樣的角色,都是為了提高查詢的效率。
6)Cursors
(遊標)和上面的五個概念都不一樣,但是它非常重要,並且經常被忽視,其中最重要的你要理解的一點是,遊標是當你問 MongoDB 拿資料的時候,它會給你返回一個結果集的指標而不是真正的資料,這個指標我們叫它遊標,我們可以拿遊標做我們想做的任何事情,比如說計數或者跨行之類的,而無需把真正的資料拖下來,在真正的資料上操作。
它們的對比關係圖如下所示。
我們框架基於C#開發,使用的時候,安裝MongoDB的C#的驅動 MongoDB.Driver 即可。
在MongoDB資料庫的集合裡面,都要求檔案有一個_id欄位,這個是強制性的,而且這個欄位的儲存型別為ObjectId型別,這個值考慮了分散式的因素,綜合了機器碼,程序,時間戳等等方面的內容,它的構造如下所示。
其中MyCrudService裡面封裝了很多CRUD以及常用的處理方法。類似的處理方式,我們專門為MongoDB資料庫的存取操作,設計了一個功能強大的基礎類別即可。
在資料庫表的實體對應關係上,我們依舊遵循則相應的設計規則,基礎類別實體採用IEntity<string>的介面型別,因此他們具有一個字串的Id型別。其他業務物件繼承該基礎類別物件即可。
/// <summary> /// 基於MongoDB的實體類基礎類別 /// </summary> public class BaseMongoEntity : Entity<string> { [BsonId] [BsonRepresentation(BsonType.ObjectId)] public override string Id { get; set; } }
相應的,我們根據常規資料庫的基礎類別介面名稱,在處理MongoDB資料庫的操作介面的時候,名稱保持一致性。
其中TEntity為強型別實體型別,而TGetListInput 是定義的一個分頁介面。定義的基礎類別介面程式碼如下所示。
其中介面物件 CurrentApiUser是我們使用者上下文的資訊,包含一些駐留在ClainPrincipal中的資訊,用於記錄存取介面的使用者資訊的。
其他介面定義類似的處理即可。
基礎類別介面的實現類,就是我們需要設計的MongoDB資料庫操作類了,初始化類的程式碼如下所示。
/// <summary> /// MongoDB基礎倉儲實現 /// </summary> /// <typeparam name="TEntity"></typeparam> public abstract class BaseMongoService<TEntity, TGetListInput> : IBaseMongoService<TEntity, TGetListInput> where TEntity : class, IEntity<string>, new() where TGetListInput : IPagedAndSortedResultRequest { protected readonly IMongoDBContext mongoContext = NullMongoDBContext.Instance;//空實現 protected IMongoCollection<TEntity> collection; //強型別物件集合 protected IMongoCollection<BsonDocument> bsonCollection; //弱型別集合BsonDocument集合 /// <summary> /// 當前Api使用者資訊 /// </summary> public IApiUserSession CurrentApiUser { get; set; } = NullApiUserSession.Instance;//空實現 /// <summary> /// 建構函式 /// </summary> protected BaseMongoService() { //如果SerivcePovider已經設定值,則獲得注入的介面物件 if (ServiceLocator.SerivcePovider != null) { CurrentApiUser = ServiceLocator.GetService<IApiUserSession>(); mongoContext = ServiceLocator.GetService<IMongoDBContext>(); collection = mongoContext.GetCollection<TEntity>(typeof(TEntity).Name);//強型別物件集合 bsonCollection = mongoContext.GetCollection<BsonDocument>(typeof(TEntity).Name);//弱型別集合BsonDocument集合 } } /// <summary> /// 獲取所有記錄 /// </summary> /// <returns></returns> public virtual async Task<ListResultDto<TEntity>> GetAllAsync() { var all = await collection.FindAsync(Builders<TEntity>.Filter.Empty); var list = await all.ToListAsync(); return new ListResultDto<TEntity>() { Items = list }; }
我們通過構建對應的強型別Collection和弱型別Collection,來操作實體類和BsonDocument的相關操作的。其中的上下文物件,參考隨筆《NoSQL – MongoDB Repository Implementation in .NET Core with Unit Testing example》進行的處理。
/// <summary> /// MongoDB 上下文物件 /// </summary> public class MongoDBContext : IMongoDBContext { private IMongoDatabase _db { get; set; } private MongoClient _mongoClient { get; set; } public IClientSessionHandle Session { get; set; } public MongoDBContext(IOptions<Mongosettings> configuration) { _mongoClient = new MongoClient(configuration.Value.Connection); _db = _mongoClient.GetDatabase(configuration.Value.DatabaseName); } /// <summary> /// 獲取強型別集合物件 /// </summary> /// <typeparam name="T">物件型別</typeparam> /// <param name="name"></param> /// <returns></returns> public IMongoCollection<T> GetCollection<T>(string name) where T : class, new() { return _db.GetCollection<T>(name); } } public interface IMongoDBContext { IMongoCollection<T> GetCollection<T>(string name) where T : class, new(); }
通過IOptions 方式我們注入對應的MongoDB資料庫設定資訊,在appsettings.json中新增根節點內容。
"MongoSettings": { "Connection": "mongodb://localhost:27017/", //MongoDB連線字串 "DatabaseName": "iqidi" //MongoDB資料庫名稱 },
我們在啟動Web API的時候,在Program.cs 程式碼中設定好就可以了。
//MongoDB設定 builder.Services.Configure<Mongosettings>(builder.Configuration.GetSection("MongoSettings"));
預設初始化的IMongoDBContext是一個空介面,我們可以在Web API啟動的時候,指定一個具體的實現就可以了
//新增IMongoContext實現類 builder.Services.AddSingleton<IMongoDBContext, MongoDBContext>();
對於基礎類別介面,分頁查詢獲取對應列表資料,是常規的處理方式,預設需要排序、分頁,返回對應的資料結構,如下程式碼所示。
/// <summary> /// 根據條件獲取列表 /// </summary> /// <param name="input">分頁查詢條件</param> /// <returns></returns> public virtual async Task<PagedResultDto<TEntity>> GetListAsync(TGetListInput input) { var query = CreateFilteredQueryAsync(input); var totalCount = await query.CountAsync(); //排序處理 query = ApplySorting(query, input); //分頁處理 query = ApplyPaging(query, input); //獲取列表 var list = await query.ToListAsync(); return new PagedResultDto<TEntity>( totalCount, list ); }
其中PagedResultDto 是我們SqlSugar開發框架參照ABP框架定義一個資料結構,包含一個TotalCount數量和一個Items的物件集合。而其中 CreateFilteredQueryAsync 是定義的一個可供業務子類重寫的函數,用來處理具體的查詢條件。在基礎類別BaseMongoService中只是提供一個預設的可查詢物件。
/// <summary> /// 留給子類實現過濾條件的處理 /// </summary> /// <returns></returns> protected virtual IMongoQueryable<TEntity> CreateFilteredQueryAsync(TGetListInput input) { return collection.AsQueryable(); }
例如,對於一個具體的業務物件操作類,CustomerService的定義如下所示,並且具體化查詢的條件處理,如下程式碼所示。
namespace SugarProject.Core.MongoDB { /// <summary> /// 基於MongoDB資料庫的應用層服務介面實現 /// </summary> public class CustomerService : BaseMongoService<CustomerInfo, CustomerPagedDto>, ICustomerService { /// <summary> /// 建構函式 /// </summary> public CustomerService() { } /// <summary> /// 自定義條件處理 /// </summary> /// <param name="input">查詢條件Dto</param> /// <returns></returns> protected override IMongoQueryable<CustomerInfo> CreateFilteredQueryAsync(CustomerPagedDto input) { var query = base.CreateFilteredQueryAsync(input); query = query .Where(t=> !input.ExcludeId.IsNullOrWhiteSpace() && t.Id != input.ExcludeId) //不包含排除ID .Where(t=> !input.Name.IsNullOrWhiteSpace() && t.Name.Contains(input.Name)) //如需要精確匹配則用Equals //年齡區間查詢 .Where(t=> input.AgeStart.HasValue && t.Age >= input.AgeStart.Value) .Where(t => input.AgeEnd.HasValue && t.Age <= input.AgeEnd.Value) //建立日期區間查詢 .Where(t => input.CreateTimeStart.HasValue && t.CreateTime >= input.CreateTimeStart.Value) .Where(t => input.CreateTimeEnd.HasValue && t.CreateTime <= input.CreateTimeEnd.Value) ; return query; }
這個處理方式類似於常規關係型資料庫的處理方式,就是對條件的判斷處理。而具體的業務物件模型,和常規框架的實體類很類似。
/// <summary> /// 客戶資訊 /// 繼承自BaseMongoEntity,擁有Id主鍵屬性 /// </summary> public class CustomerInfo : BaseMongoEntity { /// <summary> /// 預設建構函式(需要初始化屬性的在此處理) /// </summary> public CustomerInfo() { this.CreateTime = System.DateTime.Now; } #region Property Members /// <summary> /// 姓名 /// </summary> public virtual string Name { get; set; } /// <summary> /// 年齡 /// </summary> public virtual int Age { get; set; } /// <summary> /// 建立人 /// </summary> public virtual string Creator { get; set; } /// <summary> /// 建立時間 /// </summary> public virtual DateTime CreateTime { get; set; } #endregion }
對於插入和更新操作等常規操作,我們呼叫普通的Collection操作處理就可以了
/// <summary> /// 建立物件 /// </summary> /// <param name="input">實體物件</param> /// <returns></returns> public virtual async Task InsertAsync(TEntity input) { SetObjectIdIfEmpty(input);//如果Id為空,設定為ObjectId的值 await collection.InsertOneAsync(input); } /// <summary> /// 更新記錄 /// </summary> public virtual async Task<bool> UpdateAsync(TEntity input) { SetObjectIdIfEmpty(input);//如果Id為空,設定為ObjectId的值 //await _dbSet.ReplaceOneAsync(Builders<TEntity>.Filter.Eq("_id", input.Id), input); //要修改的欄位 var list = new List<UpdateDefinition<TEntity>>(); foreach (var item in input.GetType().GetProperties()) { if (item.Name.ToLower() == "id") continue; list.Add(Builders<TEntity>.Update.Set(item.Name, item.GetValue(input))); } var updatefilter = Builders<TEntity>.Update.Combine(list); var update = await collection.UpdateOneAsync(Builders<TEntity>.Filter.Eq("_id", input.Id), updatefilter); var result = update != null && update.ModifiedCount > 0; return result; }
更新操作,有一種整個替換更新,還有一個是部分更新,它們兩者是有區別的。如果對於部分欄位的更新,那麼操作如下所示 ,主要是利用UpdateDefinition物件來指定需要更新那些欄位屬性及值等資訊。
/// <summary> /// 封裝處理更新的操作(部分欄位更新) /// </summary> /// <example> /// var update = Builders<UserInfo>.Update.Set(s => s.Name, newName); /// </example> public virtual async Task<bool> UpdateAsync(string id, UpdateDefinition<TEntity> update) { var result = await collection.UpdateOneAsync(s => s.Id == id, update, new UpdateOptions() { IsUpsert = true }); return result != null && result.ModifiedCount > 0; }
根據MongoDB資料庫的特性,我們儘量細化對資料庫操作的基礎類別介面,定義所需的介面函數即可。
對於Web API的控制器設計,我們在之前的隨筆也有介紹,為常規授權處理的BaseApiController,為常規業務CRUD等介面處理的BusinessController,如下所示。
其中ControllerBase是.net core Web API中的標準控制器基礎類別,我們由此派生一個LoginController用於登入授權,而BaseApiController則處理常規介面使用者身份資訊,而BusinessController則是對標準的增刪改查等基礎介面進行的封裝,我們實際開發的時候,只需要開發編寫類似CustomerController基礎類別即可。
而對於 MongoDB的Web API控制器,我們為了方便開發,也設計了同型別的Web API 控制器基礎類別。
其中MongoBaseController基礎類別具有常規的CRUD的介面定義處理,只要繼承它就可以了,而如果只是繼承BaseApiController這需要自定義控制器介面的方法。
最後我們啟動Swagger進行測試對應的介面即可,實際還可以整合在UI中進行測試處理。我們安裝MongoDB資料庫的管理工具後,可以在MongoDBCompass 中進行查詢對應資料庫的資料。
/// <summary> /// 客戶資訊的控制器物件(基於MongoDB),基於BaseApiController,需要自定義介面處理 /// </summary> [ApiController] [Route("api/MongoCustomer")] public class MongoCustomerController : BaseApiController { private ICustomerService _service; /// <summary> /// 建構函式,並注入基礎介面物件 /// </summary> /// <param name="service"></param> public MongoCustomerController(ICustomerService service) { this._service = service; } /// <summary> /// 獲取所有記錄 /// </summary> [HttpGet] [Route("all")] public virtual async Task<ListResultDto<CustomerInfo>> GetAllAsync() { //檢查使用者是否有許可權,否則丟擲MyDenyAccessException異常 base.CheckAuthorized(AuthorizeKey.ListKey); return await _service.GetAllAsync(); }
而如果繼承自MongoBaseController ,那麼就會具有基礎類別MongoBaseController 公開的所有控制器方法。
/// <summary> /// 客戶資訊的控制器物件(基於MongoDB),基於MongoBaseController,具有常規CRUD操作介面 /// </summary> [ApiController] [Route("api/MongoCustomer2")] public class MongoCustomer2Controller : MongoBaseController<CustomerInfo, CustomerPagedDto> { /// <summary> /// 建構函式,並注入基礎介面物件 /// </summary> /// <param name="service"></param> public MongoCustomer2Controller(ICustomerService service) : base(service) { } }
早幾年前曾經也介紹過該資料庫的相關使用,隨筆如下所示,有需要也可以瞭解下。