在前面的系列博客中,我曾經介紹過,MongoDB資料庫的C#驅動已經全面支持非同步的處理介面,並且介面的定義幾乎是重寫了。本篇主要介紹MongoDB資料庫的C#驅動的最新介面使用,介紹基於新介面如何實現基礎的增刪改查及分頁等處理,以及如何利用非同步介面實現基類相關的非同步操作。
在前面的系列博客中,我曾經介紹過,MongoDB資料庫的C#驅動已經全面支持非同步的處理介面,並且介面的定義幾乎是重寫了。本篇主要介紹MongoDB資料庫的C#驅動的最新介面使用,介紹基於新介面如何實現基礎的增刪改查及分頁等處理,以及如何利用非同步介面實現基類相關的非同步操作。
MongoDB資料庫驅動在2.2版本(或者是從2.0開始)好像完全改寫了API的介面,因此目前這個版本同時支持兩個版本的API處理,一個是基於MongoDatabase的對象介面,一個是IMongoDatabase的對象介面,前者中規中矩,和我們使用Shell裡面的命令名稱差不多,後者IMongoDatabase的介面是基於非同步的,基本上和前者差別很大,而且介面都提供了非同步的處理操作。
1、MongoDB資料庫C#驅動的新介面
新介面也還是基於資料庫,集合,文檔這樣的處理概念進行封裝,只是它們的介面不再一樣了,我們還是按照前面的做法,定義一個資料庫訪問的基類,對MongoDB資料庫的相關操作封裝在基類裡面,方便使用,同時基類利用泛型對象,實現更強類型的約束及支持,如基類BaseDAL的定義如下所示。
/// <summary> /// 數據訪問層的基類 /// </summary> public partial class BaseDAL<T> where T : BaseEntity, new()
利用泛型的方式,把數據訪問層的介面提出來,並引入了數據訪問層的基類進行實現和重用介面,如下所示。
基於新介面,如獲取資料庫對象的操作,則利用了IMongoDatabase的介面了,如下所示。
var client = new MongoClient(connectionString); var database = client.GetDatabase(new MongoUrl(connectionString).DatabaseName);
相對以前的常規介面,MongoClient對象已經沒有了GetServer的介面了。如果對創建資料庫對象的操作做更好的封裝,可以利用配置文件進行指定的話,那麼方法可以封裝如下所示。
/// <summary> /// 根據資料庫配置信息創建MongoDatabase對象,如果不指定配置信息,則從預設信息創建 /// </summary> /// <param name="databaseName">資料庫名稱,預設空為local</param> /// <returns></returns> protected virtual IMongoDatabase CreateDatabase() { string connectionString = null; if (!string.IsNullOrEmpty(dbConfigName)) { //從配置文件中獲取對應的連接信息 connectionString = ConfigurationManager.ConnectionStrings[dbConfigName].ConnectionString; } else { connectionString = defaultConnectionString; } var client = new MongoClient(connectionString); var database = client.GetDatabase(new MongoUrl(connectionString).DatabaseName); return database; }
根據IMongoDatabase 介面,那麼其獲取集合對象的操作如下所示,它使用了另外一個定義IMongoCollection了。
/// <summary> /// 獲取操作對象的IMongoCollection集合,強類型對象集合 /// </summary> /// <returns></returns> public virtual IMongoCollection<T> GetCollection() { var database = CreateDatabase(); return database.GetCollection<T>(this.entitysName); }
2、查詢單個對象實現封裝處理
基於新介面的查詢處理,已經沒有FindOne的方法定義了,只是使用了Find的方法,而且也沒有了Query的對象可以作為條件進行處理,而是採用了新的定義對象FilterDefinition,例如對於根據ID查詢單個對象,介面的實現如下所示。
/// <summary> /// 查詢資料庫,檢查是否存在指定ID的對象 /// </summary> /// <param name="key">對象的ID值</param> /// <returns>存在則返回指定的對象,否則返回Null</returns> public virtual T FindByID(string id) { ArgumentValidation.CheckForEmptyString(id, "傳入的對象id為空"); IMongoCollection<T> collection = GetCollection(); return collection.Find(s=> s.Id == id).FirstOrDefault(); }
對於利用FilterDefinition進行查詢的操作,如下所示。
/// <summary> /// 根據條件查詢資料庫,如果存在返回第一個對象 /// </summary> /// <param name="filter">條件表達式</param> /// <returns>存在則返回指定的第一個對象,否則返回預設值</returns> public virtual T FindSingle(FilterDefinition<T> filter) { IMongoCollection<T> collection = GetCollection(); return collection.Find(filter).FirstOrDefault(); }
我們可以看到,這些都是利用Find方法的不同重載實現不同條件的處理的。
對於這個新介面,非同步是一個重要的改變,那麼它的非同步處理是如何的呢,我們看看上面兩個非同步的實現操作,具體代碼如下所示。
/// <summary> /// 查詢資料庫,檢查是否存在指定ID的對象(非同步) /// </summary> /// <param name="key">對象的ID值</param> /// <returns>存在則返回指定的對象,否則返回Null</returns> public virtual async Task<T> FindByIDAsync(string id) { ArgumentValidation.CheckForEmptyString(id, "傳入的對象id為空"); IMongoCollection<T> collection = GetCollection(); return await collection.FindAsync(s=>s.Id == id).Result.FirstOrDefaultAsync(); } /// <summary> /// 根據條件查詢資料庫,如果存在返回第一個對象(非同步) /// </summary> /// <param name="query">條件表達式</param> /// <returns>存在則返回指定的第一個對象,否則返回預設值</returns> public virtual async Task<T> FindSingleAsync(FilterDefinition<T> query) { return await GetQueryable(query).SingleOrDefaultAsync(); }
我們看到,上面的Collection或者GetQueryable(query)返回的對象,都提供給了以Async結尾的非同步方法,因此對非同步的封裝也是非常方便的,上面的GetQueryable(query)是另外一個公共的實現方法,具體代碼如下所示。
/// <summary> /// 返回可查詢的記錄源 /// </summary> /// <param name="query">查詢條件</param> /// <returns></returns> public virtual IFindFluent<T, T> GetQueryable(FilterDefinition<T> query) { return GetQueryable(query, this.SortPropertyName, this.IsDescending); }
/// <summary> /// 根據條件表達式返回可查詢的記錄源 /// </summary> /// <param name="query">查詢條件</param> /// <param name="sortPropertyName">排序表達式</param> /// <param name="isDescending">如果為true則為降序,否則為升序</param> /// <returns></returns> public virtual IFindFluent<T,T> GetQueryable(FilterDefinition<T> query, string sortPropertyName, bool isDescending = true) { IMongoCollection<T> collection = GetCollection(); IFindFluent<T, T> queryable = collection.Find(query); var sort = this.IsDescending ? Builders<T>.Sort.Descending(this.SortPropertyName) : Builders<T>.Sort.Ascending(this.SortPropertyName); return queryable.Sort(sort); }
我們可以看到,它返回了IFindFluent<T, T>的對象,這個和以前返回的IMongoQuery對象又有不同,基本上,使用最新的介面,所有的實現都不太一樣,這也是因為MongoDB還在不停變化之中有關。
3、GetQueryable幾種方式
為了簡化代碼,方便使用,我們對獲取MongoDB的LINQ方式的處理做了簡單的封裝,提供了幾個GetQueryable的方式,具體代碼如下所示。
/// <summary> /// 返回可查詢的記錄源 /// </summary> /// <returns></returns> public virtual IQueryable<T> GetQueryable() { IMongoCollection<T> collection = GetCollection(); IQueryable<T> query = collection.AsQueryable(); return query.OrderBy(this.SortPropertyName, this.IsDescending); }
/// <summary> /// 根據條件表達式返回可查詢的記錄源 /// </summary> /// <param name="match">查詢條件</param> /// <param name="orderByProperty">排序表達式</param> /// <param name="isDescending">如果為true則為降序,否則為升序</param> /// <returns></returns> public virtual IQueryable<T> GetQueryable<TKey>(Expression<Func<T, bool>> match, Expression<Func<T, TKey>> orderByProperty, bool isDescending = true) { IMongoCollection<T> collection = GetCollection(); IQueryable<T> query = collection.AsQueryable(); if (match != null) { query = query.Where(match); } if (orderByProperty != null) { query = isDescending ? query.OrderByDescending(orderByProperty) : query.OrderBy(orderByProperty); } else { query = query.OrderBy(this.SortPropertyName, isDescending); } return query; }
以及基於FilterDefinition的條件處理,並返回IFindFluent<T,T>介面對象的代碼如下所示。
/// <summary> /// 根據條件表達式返回可查詢的記錄源 /// </summary> /// <param name="query">查詢條件</param> /// <param name="sortPropertyName">排序表達式</param> /// <param name="isDescending">如果為true則為降序,否則為升序</param> /// <returns></returns> public virtual IFindFluent<T,T> GetQueryable(FilterDefinition<T> query, string sortPropertyName, bool isDescending = true) { IMongoCollection<T> collection = GetCollection(); IFindFluent<T, T> queryable = collection.Find(query); var sort = this.IsDescending ? Builders<T>.Sort.Descending(this.SortPropertyName) : Builders<T>.Sort.Ascending(this.SortPropertyName); return queryable.Sort(sort); }
4、集合的查詢操作封裝處理
基於上面的封裝,對結合的查詢,也是基於不同的條件進行處理,返回對應的列表的處理方式, 最簡單的是利用GetQueryable方式進行處理,代碼如下所示。
/// <summary> /// 根據條件查詢資料庫,並返回對象集合 /// </summary> /// <param name="match">條件表達式</param> /// <returns>指定對象的集合</returns> public virtual IList<T> Find(Expression<Func<T, bool>> match) { return GetQueryable(match).ToList(); }
或者如下所示
/// <summary> /// 根據條件查詢資料庫,並返回對象集合 /// </summary> /// <param name="match">條件表達式</param> /// <returns>指定對象的集合</returns> public virtual IList<T> Find(FilterDefinition<T> query) { return GetQueryable(query).ToList(); }
以及對排序欄位,以及升降序的處理操作如下所示。
/// <summary> /// 根據條件查詢資料庫,並返回對象集合 /// </summary> /// <param name="match">條件表達式</param> /// <param name="orderByProperty">排序表達式</param> /// <param name="isDescending">如果為true則為降序,否則為升序</param> /// <returns></returns> public virtual IList<T> Find<TKey>(Expression<Func<T, bool>> match, Expression<Func<T, TKey>> orderByProperty, bool isDescending = true) { return GetQueryable<TKey>(match, orderByProperty, isDescending).ToList(); } /// <summary> /// 根據條件查詢資料庫,並返回對象集合 /// </summary> /// <param name="query">條件表達式</param> /// <param name="orderByProperty">排序欄位</param> /// <param name="isDescending">如果為true則為降序,否則為升序</param> /// <returns></returns> public virtual IList<T> Find<TKey>(FilterDefinition<T> query, string orderByProperty, bool isDescending = true) { return GetQueryable(query, orderByProperty, isDescending).ToList(); }
以及利用這些條件進行分頁的處理代碼如下所示。
/// <summary> /// 根據條件查詢資料庫,並返回對象集合(用於分頁數據顯示) /// </summary> /// <param name="match">條件表達式</param> /// <param name="info">分頁實體</param> /// <returns>指定對象的集合</returns> public virtual IList<T> FindWithPager(Expression<Func<T, bool>> match, PagerInfo info) { int pageindex = (info.CurrenetPageIndex < 1) ? 1 : info.CurrenetPageIndex; int pageSize = (info.PageSize <= 0) ? 20 : info.PageSize; int excludedRows = (pageindex - 1) * pageSize; IQueryable<T> query = GetQueryable(match); info.RecordCount = query.Count(); return query.Skip(excludedRows).Take(pageSize).ToList(); } /// <summary> /// 根據條件查詢資料庫,並返回對象集合(用於分頁數據顯示) /// </summary> /// <param name="query">條件表達式</param> /// <param name="info">分頁實體</param> /// <returns>指定對象的集合</returns> public virtual IList<T> FindWithPager(FilterDefinition<T> query, PagerInfo info) { int pageindex = (info.CurrenetPageIndex < 1) ? 1 : info.CurrenetPageIndex; int pageSize = (info.PageSize <= 0) ? 20 : info.PageSize; int excludedRows = (pageindex - 1) * pageSize; var find = GetQueryable(query); info.RecordCount = (int)find.Count(); return find.Skip(excludedRows).Limit(pageSize).ToList(); }
對於非同步的封裝處理,基本上也和上面的操作差不多,例如對於基礎的查詢,非同步操作封裝如下所示。
/// <summary> /// 根據條件查詢資料庫,並返回對象集合 /// </summary> /// <param name="match">條件表達式</param> /// <returns>指定對象的集合</returns> public virtual async Task<IList<T>> FindAsync(Expression<Func<T, bool>> match) { return await Task.FromResult(GetQueryable(match).ToList()); } /// <summary> /// 根據條件查詢資料庫,並返回對象集合 /// </summary> /// <param name="query">條件表達式</param> /// <returns>指定對象的集合</returns> public virtual async Task<IList<T>> FindAsync(FilterDefinition<T> query) { return await GetQueryable(query).ToListAsync(); }
複雜一點的分頁處理操作代碼封裝如下所示。
/// <summary> /// 根據條件查詢資料庫,並返回對象集合(用於分頁數據顯示) /// </summary> /// <param name="match">條件表達式</param> /// <param name="info">分頁實體</param> /// <returns>指定對象的集合</returns> public virtual async Task<IList<T>> FindWithPagerAsync(Expression<Func<T, bool>> match, PagerInfo info) { int pageindex = (info.CurrenetPageIndex < 1) ? 1 : info.CurrenetPageIndex; int pageSize = (info.PageSize <= 0) ? 20 : info.PageSize; int excludedRows = (pageindex - 1) * pageSize; IQueryable<T> query = GetQueryable(match); info.RecordCount = query.Count(); var result = query.Skip(excludedRows).Take(pageSize).ToList(); return await Task.FromResult(result); } /// <summary> /// 根據條件查詢資料庫,並返回對象集合(用於分頁數據顯示) /// </summary> /// <param name="query">條件表達式</param> /// <param name="info">分頁實體</param> /// <returns>指定對象的集合</returns> public virtual async Task<IList<T>> FindWithPagerAsync(FilterDefinition<T> query, PagerInfo info) { int pageindex = (info.CurrenetPageIndex < 1) ? 1 : info.CurrenetPageIndex; int pageSize = (info.PageSize <= 0) ? 20 : info.PageSize; int excludedRows = (pageindex - 1) * pageSize; var queryable = GetQueryable(query); info.RecordCount = (int)queryable.Count(); return await queryable.Skip(excludedRows).Limit(pageSize).ToListAsync(); }
5、增刪改方法封裝處理
對於常規的增刪改操作,在新的MongoDB資料庫驅動裡面也修改了名稱,使用的時候也需要進行調整處理了。
/// <summary> /// 插入指定對象到資料庫中 /// </summary> /// <param name="t">指定的對象</param> public virtual void Insert(T t) { ArgumentValidation.CheckForNullReference(t, "傳入的對象t為空"); IMongoCollection<T> collection = GetCollection(); collection.InsertOne(t); }
非同步的操作實現如下所示。
/// <summary> /// 插入指定對象到資料庫中 /// </summary> /// <param name="t">指定的對象</param> public virtual async Task InsertAsync(T t) { ArgumentValidation.CheckForNullReference(t, "傳入的對象t為空"); IMongoCollection<T> collection = GetCollection(); await collection.InsertOneAsync(t); }
批量插入記錄的操作如下所示。
/// <summary> /// 插入指定對象集合到資料庫中 /// </summary> /// <param name="list">指定的對象集合</param> public virtual void InsertBatch(IEnumerable<T> list) { ArgumentValidation.CheckForNullReference(list, "傳入的對象list為空"); IMongoCollection<T> collection = GetCollection(); collection.InsertMany(list); }
對應的非同步操作處理如下所示,這些都是利用原生支持的非同步處理介面實現的。
/// <summary> /// 插入指定對象集合到資料庫中 /// </summary> /// <param name="list">指定的對象集合</param> public virtual async Task InsertBatchAsync(IEnumerable<T> list) { ArgumentValidation.CheckForNullReference(list, "傳入的對象list為空"); IMongoCollection<T> collection = GetCollection(); await collection.InsertManyAsync(list); }
更新操作,有一種整個替換更新,還有一個是部分更新,它們兩者是有區別的,如對於替換更新的操作,它的介面封裝處理如下所示
/// <summary> /// 更新對象屬性到資料庫中 /// </summary> /// <param name="t">指定的對象</param> /// <param name="id">主鍵的值</param> /// <returns>執行成功返回<c>true</c>,否則為<c>false</c></returns> public virtual bool Update(T t, string id) { ArgumentValidation.CheckForNullReference(t, "傳入的對象t為空"); ArgumentValidation.CheckForEmptyString(id, "傳入的對象id為空"); bool result = false; IMongoCollection<T> collection = GetCollection(); //使用 IsUpsert = true ,如果沒有記錄則寫入 var update = collection.ReplaceOne(s => s.Id == id, t, new UpdateOptions() { IsUpsert = true }); result = update != null && update.ModifiedCount > 0; return result; }
如果對於部分欄位的更新,那麼操作如下所示 ,主要是利用UpdateDefinition對象來指定需要更新那些欄位屬性及值等信息。
/// <summary> /// 封裝處理更新的操作(部分欄位更新) /// </summary> /// <param name="id">主鍵的值</param> /// <param name="update">更新對象</param> /// <returns>執行成功返回<c>true</c>,否則為<c>false</c></returns> public virtual bool Update(string id, UpdateDefinition<T> update) { ArgumentValidation.CheckForNullReference(update, "傳入的對象update為空"); ArgumentValidation.CheckForEmptyString(id, "傳入的對象id為空"); IMongoCollection<T> collection = GetCollection(); var result = collection.UpdateOne(s => s.Id == id, update, new UpdateOptions() { IsUpsert = true }); return result != null && result.ModifiedCount > 0; }
上面的非同步更新操作如下所示。
/// <summary> /// 封裝處理更新的操作(部分欄位更新) /// </summary> /// <param name="id">主鍵的值</param> /// <param name="update">更新對象</param> /// <returns>執行成功返回<c>true</c>,否則為<c>false</c></returns> public virtual async Task<bool> UpdateAsync(string id, UpdateDefinition<T> update) { ArgumentValidation.CheckForNullReference(update, "傳入的對象update為空"); ArgumentValidation.CheckForEmptyString(id, "傳入的對象id為空"); IMongoCollection<T> collection = GetCollection(); var result = await collection.UpdateOneAsync(s => s.Id == id, update, new UpdateOptions() { IsUpsert = true }); var sucess = result != null && result.ModifiedCount > 0; return await Task.FromResult(sucess); }
刪除的操作也是類似的了,基本上和上面的處理方式接近,順便列出來供參考學習。
/// <summary> /// 根據指定對象的ID,從資料庫中刪除指定對象 /// </summary> /// <param name="id">對象的ID</param> /// <returns>執行成功返回<c>true</c>,否則為<c>false</c>。</returns> public virtual bool Delete(string id) { ArgumentValidation.CheckForEmptyString(id, "傳入的對象id為空"); IMongoCollection<T> collection = GetCollection(); var result = collection.DeleteOne(s=> s.Id == id); return result != null && result.DeletedCount > 0; } /// <summary> /// 根據指定對象的ID,從資料庫中刪除指定指定的對象 /// </summary> /// <param name="idList">對象的ID集合</param> /// <returns>執行成功返回<c>true</c>,否則為<c>false</c>。</returns> public virtual bool DeleteBatch(List<string> idList) { ArgumentValidation.CheckForNullReference(idList, "傳入的對象idList為空"); IMongoCollection<T> collection = GetCollection(); var query = Query.In("_id", new BsonArray(idList)); var result = collection.DeleteMany(s => idList.Contains(s.Id)); return result != null && result.DeletedCount > 0; }
如果根據條件的刪除,也可以利用條件定義的兩種方式,具體代碼如下所示。
/// <summary> /// 根據指定條件,從資料庫中刪除指定對象 /// </summary> /// <param name="match">條件表達式</param> /// <returns>執行成功返回<c>true</c>,否則為<c>false</c>。</returns> public virtual bool DeleteByExpression(Expression<Func<T, bool>> match) { IMongoCollection<T> collection = GetCollection(); collection.AsQueryable().Where(match).ToList().ForEach(s => collection.DeleteOne(t => t.Id == s.Id)); return true; } /// <summary> /// 根據指定條件,從資料庫中刪除指定對象 /// </summary> /// <param name="match">條件表達式</param> /// <returns>執行成功返回<c>true</c>,否則為<c>false</c>。</returns> public virtual bool DeleteByQ