在上篇博客《基於C#的MongoDB資料庫開發應用(1)--MongoDB資料庫的基礎知識和使用》裡面,我總結了MongoDB資料庫的一些基礎信息,併在最後面部分簡單介紹了資料庫C#驅動的開發 ,本文繼續這個主體,重點介紹MongoDB資料庫C#方面的使用和封裝處理過程,利用泛型和基類對象針對數據訪...
在上篇博客《基於C#的MongoDB資料庫開發應用(1)--MongoDB資料庫的基礎知識和使用》裡面,我總結了MongoDB資料庫的一些基礎信息,併在最後面部分簡單介紹了資料庫C#驅動的開發 ,本文繼續這個主題,重點介紹MongoDB資料庫C#方面的使用和封裝處理過程,利用泛型和基類對象針對數據訪問層進行的封裝處理。
前面介紹到,當前2.2版本的資料庫C#驅動的API,支持兩種不同的開發介面,一個是基於MongoDatabase的對象介面,一個是IMongoDatabase的對象介面,前者中規中矩,和我們使用Shell裡面的命令名稱差不多;後者IMongoDatabase的介面是基於非同步的,基本上和前者差別很大,而且介面都提供了非同步的處理操作。
本文主要介紹基於MongoDatabase的對象介面的封裝處理設置。
1、數據訪問層的設計
在結合MongoDB資料庫的C#驅動的特點,使用泛型和繼承關係,把常規的處理介面做了抽象性的封裝,以便封裝適合更多業務的介面,減少子類代碼及統一API的介面名稱。
首先我們來看看大概的設計思路,我們把實體類抽象一個實體基類,方便使用。
我們知道,在MongoDB資料庫的集合裡面,都要求文檔有一個_id欄位,這個是強制性的,而且這個欄位的存儲類型為ObjectId類型,這個值考慮了分散式的因素,綜合了機器碼,進程,時間戳等等方面的內容,它的構造如下所示。
ObjectId是一個12位元組的 BSON 類型字元串。按照位元組順序,依次代表:
- 4位元組:UNIX時間戳
- 3位元組:表示運行MongoDB的機器
- 2位元組:表示生成此_id的進程
- 3位元組:由一個隨機數開始的計數器生成的值
實體基類BaseEntity包含了一個屬性Id,這個是一個字元串型的對象(也可以使用ObjectId類型,但是為了方便,我們使用字元型,並聲明為ObjectId類型即可),由於我們聲明瞭該屬性對象為ObjectId類型,那麼我們就可以在C#代碼裡面使用字元串的ID類型了,代碼如下所示。
/// <summary> /// MongoDB實體類的基類 /// </summary> public class BaseEntity { /// <summary> /// 基類對象的ID,MongoDB要求每個實體類必須有的主鍵 /// </summary> [BsonRepresentation(BsonType.ObjectId)] public string Id { get; set; } }
然後利用泛型的方式,把數據訪問層的介面提出來,並引入了數據訪問層的基類進行實現和重用介面,如下所示。
其中,上面幾個類的定義如下所示。
數據訪問層基類BaseDAL的類定義如下所示,主要就是針對上面的IBaseDAL<T>介面進行實現。
有了這些基類的實現,我們對於實體類的處理就非常方便、統一了,基本上不需要在複製大量的代碼來實現基礎的增刪改查分頁實現了。
例如上面的User集合(表對象)的數據訪問類定義如下所示,在對象的定義的時候,指定對應的實體類,併在構造函數裡面指定對應的集合名稱就可以實例化一個數據訪問類了。
/// <summary> /// 數據表User對應的具體數據訪問類 /// </summary> public class User : BaseDAL<UserInfo>, IBaseDAL<UserInfo> { /// <summary> /// 預設構造函數 /// </summary> public User() { this.entitysName = "users";//對象在資料庫的集合名稱 } .................
2、基類各種介面的實現
前面小節我們介紹了實體基類,數據訪問層基類介面和基類實現,以及具體集合對象的實現類的定義關係,通過泛型和繼承關係,我們很好的抽象了各種對象的增刪改查、分頁等操作,子類繼承了BaseDAL基類後,就自然而然的具有了非常強大的介面處理功能了。下麵我們來繼續詳細介紹基於C#驅動的MongoDB資料庫是如何進行各種增刪改查等封裝的。
1)構造MongoDatabase對象
首先我們需要利用連接字元串來構建MongoDatabase對象,因為所有的介面都是基於這個對象進行處理的,代碼如下所示。
/// <summary> /// 根據資料庫配置信息創建MongoDatabase對象,如果不指定配置信息,則從預設信息創建 /// </summary> /// <param name="databaseName">資料庫名稱,預設空為local</param> /// <returns></returns> protected virtual MongoDatabase CreateDatabase() { string connectionString = null; if (!string.IsNullOrEmpty(dbConfigName)) { //從配置文件中獲取對應的連接信息 connectionString = ConfigurationManager.ConnectionStrings[dbConfigName].ConnectionString; } else { connectionString = defaultConnectionString; } var client = new MongoClient(connectionString); var database = client.GetServer().GetDatabase(new MongoUrl(connectionString).DatabaseName); return database; }
2)構建MongoCollection對象
上面構建了MongoDatabase對象後,我們需要基於這個基礎上再創建一個對象的MongoCollection對象,這個就是類似我們關係資料庫裡面的表對象的原型了。
/// <summary> /// 獲取操作對象的MongoCollection集合,強類型對象集合 /// </summary> /// <returns></returns> protected virtual MongoCollection<T> GetCollection() { MongoDatabase database = CreateDatabase(); return database.GetCollection<T>(this.entitysName); }
3)查詢單個對象
利用MongoCollection對象,我們可以通過API介面獲取對應的對象,單個對象的介面為FindOneById(也可以用FindOne介面,如註釋部分的代碼),我們具體的處理代碼如下所示
/// <summary> /// 查詢資料庫,檢查是否存在指定ID的對象 /// </summary> /// <param name="key">對象的ID值</param> /// <returns>存在則返回指定的對象,否則返回Null</returns> public virtual T FindByID(string id) { ArgumentValidation.CheckForEmptyString(id, "傳入的對象id為空"); MongoCollection<T> collection = GetCollection(); return collection.FindOneById(new ObjectId(id)); //FindOne(Query.EQ("_id", new ObjectId(id))); }
如果基於條件的單個記錄查詢,我們可以使用Expression<Func<T, bool>>和IMongoQuery的參數進行處理,如下代碼所示。
/// <summary> /// 根據條件查詢資料庫,如果存在返回第一個對象 /// </summary> /// <param name="match">條件表達式</param> /// <returns>存在則返回指定的第一個對象,否則返回預設值</returns> public virtual T FindSingle(Expression<Func<T, bool>> match) { MongoCollection<T> collection = GetCollection(); return collection.AsQueryable().Where(match).FirstOrDefault(); } /// <summary> /// 根據條件查詢資料庫,如果存在返回第一個對象 /// </summary> /// <param name="query">條件表達式</param> /// <returns>存在則返回指定的第一個對象,否則返回預設值</returns> public virtual T FindSingle(IMongoQuery query) { MongoCollection<T> collection = GetCollection(); return collection.FindOne(query); }
4)IQueryable的介面利用
使用過EF的實體框架的話,我們對其中的IQueryable<T>印象很深刻,它可以給我提供很好的LINQ語法獲取對應的信息,它可以通過使用Expression<Func<T, bool>>和IMongoQuery的參數來進行條件的查詢操作,MongoCollection對象有一個AsQueryable()的API進行轉換,如下所示。
/// <summary> /// 返回可查詢的記錄源 /// </summary> /// <returns></returns> public virtual IQueryable<T> GetQueryable() { MongoCollection<T> collection = GetCollection(); IQueryable<T> query = collection.AsQueryable(); return query.OrderBy(this.SortPropertyName, this.IsDescending); }
如果是通過使用Expression<Func<T, bool>>和IMongoQuery的參數,那麼處理的介面代碼如下所示。
/// <summary> /// 根據條件表達式返回可查詢的記錄源 /// </summary> /// <param name="match">查詢條件</param> /// <param name="sortPropertyName">排序表達式</param> /// <param name="isDescending">如果為true則為降序,否則為升序</param> /// <returns></returns> public virtual IQueryable<T> GetQueryable(Expression<Func<T, bool>> match, string sortPropertyName, bool isDescending = true) { MongoCollection<T> collection = GetCollection(); IQueryable<T> query = collection.AsQueryable(); if (match != null) { query = query.Where(match); } return query.OrderBy(sortPropertyName, isDescending); }
/// <summary> /// 根據條件表達式返回可查詢的記錄源 /// </summary> /// <param name="query">查詢條件</param> /// <param name="sortPropertyName">排序表達式</param> /// <param name="isDescending">如果為true則為降序,否則為升序</param> /// <returns></returns> public virtual IQueryable<T> GetQueryable(IMongoQuery query, string sortPropertyName, bool isDescending = true) { MongoCollection<T> collection = GetCollection(); IQueryable<T> queryable = collection.Find(query).AsQueryable(); return queryable.OrderBy(sortPropertyName, isDescending); }
5)集合的查詢處理
通過利用上面的IQueryable<T>對象,以及使用Expression<Func<T, bool>>和IMongoQuery的參數,我們很好的進行集合的查詢處理操作的了,具體代碼如下所示
/// <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="query">條件表達式</param> /// <returns>指定對象的集合</returns> public virtual IList<T> Find(IMongoQuery query) { MongoCollection<T> collection = GetCollection(); return collection.Find(query).ToList(); }
對於分頁,我們是非常需要的,首先在大數據的集合裡面,我們不可能一股腦的把所有的數據全部返回,因此根據分頁參數返回有限數量的集合處理就是我們應該做的,分頁的操作代碼和上面很類似,只是利用了Skip和Take的介面,返回我們需要的記錄數量就可以了。
/// <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(IMongoQuery query, 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> queryable = GetQueryable(query); info.RecordCount = queryable.Count(); return queryable.Skip(excludedRows).Take(pageSize).ToList(); }
6)對象的寫入操作
對象的寫入可以使用save,它是根據_id的來決定插入還是更新的,如下代碼所示。
/// <summary> /// 保存指定對象到資料庫中,根據Id的值,決定是插入還是更新 /// </summary> /// <param name="t">指定的對象</param> /// <returns>執行成功指定對象信息</returns> public virtual T Save(T t) { ArgumentValidation.CheckForNullReference(t, "傳入的對象t為空"); MongoCollection<T> collection = GetCollection(); var result = collection.Save(t); return t; }
插入記錄就可以利用insert方法進行處理的,代碼如下所示。
/// <summary> /// 插入指定對象到資料庫中 /// </summary> /// <param name="t">指定的對象</param> /// <returns>執行成功返回<c>true</c>,否則為<c>false</c></returns> public virtual bool Insert(T t) { ArgumentValidation.CheckForNullReference(t, "傳入的對象t為空"); MongoCollection<T> collection = GetCollection(); var result = collection.Insert(t); return result != null && result.DocumentsAffected > 0; }
如果是批量插入,可以利用它的insertBatch的方法進行處理,具體代碼如下所示。
/// <summary> /// 插入指定對象集合到資料庫中 /// </summary> /// <param name="list">指定的對象集合</param> /// <returns>執行成功返回<c>true</c>,否則為<c>false</c></returns> public virtual bool InsertBatch(IEnumerable<T> list) { ArgumentValidation.CheckForNullReference(list, "傳入的對象list為空"); MongoCollection<T> collection = GetCollection(); var result = collection.InsertBatch(list); return result.Any(s => s != null && s.DocumentsAffected > 0); //部分成功也返回true }
7)對象的更新操作
更新操作分為了兩個不同的部分,一個是全部的記錄更新,也就是整個JSON的替換操作了,一般我們是在原來的基礎上進行更新的,如下代碼所示。
/// <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; MongoCollection<T> collection = GetCollection(); var existing = FindByID(id); if (existing != null) { var resultTmp = collection.Save(t); result = resultTmp != null && resultTmp.DocumentsAffected > 0; } return result; }
還有一種方式是部分更新,也就是更新裡面的指定一個或幾個欄位,不會影響其他欄位,也就不會全部替換掉其他內容的操作了。這裡利用了一個UpdateBuilder<T>的對象,用來指定那些欄位需要更新,以及這些欄位的值內容的,具體的更新代碼如下所示。
/// <summary> /// 封裝處理更新的操作 /// </summary> /// <param name="id">主鍵的值</param> /// <param name="update">更新對象</param> /// <returns>執行成功返回<c>true</c>,否則為<c>false</c></returns> public virtual bool Update(string id, UpdateBuilder<T> update) { ArgumentValidation.CheckForNullReference(update, "傳入的對象update為空"); ArgumentValidation.CheckForEmptyString(id, "傳入的對象id為空"); var query = Query.EQ("_id", new ObjectId(id)); MongoCollection<T> collection = GetCollection(); var result = collection.Update(query, update); return result != null && result.DocumentsAffected > 0; }
部分更新,可以結合使用Inc和Set方法來進行處理,如下是我在子類裡面利用到上面的Update部分更新的API進行處理個別欄位的更新操作。
/// <summary> /// 為用戶增加歲數 /// </summary> /// <param name="id">記錄ID</param> /// <param name="addAge">待增加的歲數</param> /// <returns></returns> public bool IncreaseAge(string id, int addAge) { //增加指定的歲數 var query = Query<UserInfo>.EQ(s => s.Id, id); var update = Update<UserInfo>.Inc(s => s.Age, addAge); var collection = GetCollection(); var result = collection.Update(query, update); return result != null && result.DocumentsAffected > 0; } /// <summary> /// 單獨修改用戶的名稱 /// </summary> /// <param name="id">記錄ID</param> /// <param name="newName">用戶新名稱</param> /// <returns></returns> public bool UpdateName(string id, string newName) { //增加指定的歲數 var query = Query<UserInfo>.EQ(s => s.Id, id); var update = Update<UserInfo>.Set(s => s.Name, newName); var collection = GetCollection(); var result = collection.Update(query, update); return result != null && result.DocumentsAffected > 0; }
8)對象的刪除操作
對象的刪除,一般可以利用條件進行刪除,如單個刪除可以使用_id屬性進行處理,也可以利用批量刪除的介面進行刪除操作,代碼如下所示。
/// <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為空"); MongoCollection<T> collection = GetCollection(); //var result = collection.Remove(Query<T>.EQ(s => s.Id, id)); var result = collection.Remove(Query.EQ("_id", new ObjectId(id))); return result != null && result.DocumentsAffected > 0; }
其中上面註釋的var result = collection.Remove(Query<T>.EQ(s => s.Id, id));代碼,就是利用了強類型的對象屬性和值進行移除,一樣可以的。
對於批量刪除,可以利用Query的不同進行處理。
/// <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為空"); MongoCollection<T> collection = GetCollection(); var query = Query.In("_id", new BsonArray(idList)); var result = collection.Remove(query); return result != null && result.DocumentsAffected > 0; }
或者基於IMongoQuery的條件進行處理。
/// <summary> /// 根據指定條件,從資料庫中刪除指定對象 /// </summary> /// <param name="match">條件表達式</param> /// <returns>執行成功返回<c>true</c>,否則為<c>false</c>。</returns> public virtual bool DeleteByQuery(IMongoQuery query) { MongoCollection<T> collection = GetCollection(); var result = collection.Remove(query); return result != null && result.DocumentsAffected > 0; }
9)其他相關介面
一般除了上面的介面,還有一些其他的介面,如獲取記錄的總數、判斷條件的記錄是否存在等也是很常見的,他們的代碼封裝如下所示。
/// <summary> /// 獲取表的所有記錄數量 /// </summary> /// <returns></returns> public virtual int GetRecordCount() { return GetQueryable().Count(); }
/// <summary> /// 根據查詢條件,判斷是否存在記錄 /// </summary> /// <param name="match">條件表達式</param> /// <returns></returns> public virtual bool IsExistRecord(Expression<Func<T, bool>> match) { return GetQueryable(match).Any();//.Count() > 0 } /// <summary> /// 根據查詢條件,判斷是否存在記錄 /// </summary> /// <param name="query">條件表達式</param> /// <returns></returns> public virtual bool IsExistRecord(IMongoQuery query) { return GetQueryable(query).Any();//.Count() > 0 }
非常感謝您的詳細閱讀,以上基本上就是我對整個MongoDB資料庫的各個介面的基類封裝處理了,其中已經覆蓋到了基礎的增刪改查、分頁等操作介面,以及一些特殊的條件處理介面的擴展,我們利用這些封裝好的基類很好的簡化了子類的代碼,而且可以更方便的在基類的基礎上進行特殊功能的擴展處理。
當然,以上介紹的都不是最新的介面,是2.0(或2.2)版本之前的介面實現,雖然在2.2裡面也還可以利用上面的MongoDatabase對象介面,但是IMongoDatabase最新的介面已經全面相容非同步的操作,但也是一個很大的跳躍,基本上引入了不同的介面命名和處理方式,利用非同步可以支持更好的處理體驗,但是也基本上是需要對所有的介面進行了全部的重寫了。
下一篇我會專門介紹一下基於最新的非同步介面如何實現這些常規增刪改查、分頁等的基類實現封裝處理。