近期工作中有使用到 MongoDb作為日志持久化對象,需要實作對MongoDb的增、删、改、查,但由于MongoDb的版本比較新,是2.4以上版本的,網上已有的一些MongoDb Helper類都是基于之前MongoDb舊的版本,無法适用于新版本的MongoDb,故我基于MongoDb官方C#驅動重新封裝了MongoDbCsharpHelper類(CRUD類),完整代碼如下:
using MongoDB;
using MongoDB.Bson;
using MongoDB.Driver;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Web;
namespace Zuowj.Utils
{
/// <summary>
/// MongoDbCsharpHelper:MongoDb基于C#語言操作幫助類
/// Author:Zuowenjun
/// Date:2017/11/16
/// </summary>
public class MongoDbCsharpHelper
{
private readonly string connectionString = null;
private readonly string databaseName = null;
private MongoDB.Driver.IMongoDatabase database = null;
private readonly bool autoCreateDb = false;
private readonly bool autoCreateCollection = false;
static MongoDbCsharpHelper()
{
BsonDefaults.GuidRepresentation = GuidRepresentation.Standard;
}
public MongoDbCsharpHelper(string mongoConnStr, string dbName, bool autoCreateDb = false, bool autoCreateCollection = false)
{
this.connectionString = mongoConnStr;
this.databaseName = dbName;
this.autoCreateDb = autoCreateDb;
this.autoCreateCollection = autoCreateCollection;
}
#region 私有方法
private MongoClient CreateMongoClient()
{
return new MongoClient(connectionString);
}
private MongoDB.Driver.IMongoDatabase GetMongoDatabase()
{
if (database == null)
{
var client = CreateMongoClient();
if (!DatabaseExists(client, databaseName) && !autoCreateDb)
{
throw new KeyNotFoundException("此MongoDB名稱不存在:" + databaseName);
}
database = CreateMongoClient().GetDatabase(databaseName);
}
return database;
}
private bool DatabaseExists(MongoClient client, string dbName)
{
try
{
var dbNames = client.ListDatabases().ToList().Select(db => db.GetValue("name").AsString);
return dbNames.Contains(dbName);
}
catch //如果連接配接的賬号不能枚舉出所有DB會報錯,則預設為true
{
return true;
}
}
private bool CollectionExists(IMongoDatabase database, string collectionName)
{
var options = new ListCollectionsOptions
{
Filter = Builders<BsonDocument>.Filter.Eq("name", collectionName)
};
return database.ListCollections(options).ToEnumerable().Any();
}
private MongoDB.Driver.IMongoCollection<TDoc> GetMongoCollection<TDoc>(string name, MongoCollectionSettings settings = null)
{
var mongoDatabase = GetMongoDatabase();
if (!CollectionExists(mongoDatabase, name) && !autoCreateCollection)
{
throw new KeyNotFoundException("此Collection名稱不存在:" + name);
}
return mongoDatabase.GetCollection<TDoc>(name, settings);
}
private List<UpdateDefinition<TDoc>> BuildUpdateDefinition<TDoc>(object doc, string parent)
{
var updateList = new List<UpdateDefinition<TDoc>>();
foreach (var property in typeof(TDoc).GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
var key = parent == null ? property.Name : string.Format("{0}.{1}", parent, property.Name);
//非空的複雜類型
if ((property.PropertyType.IsClass || property.PropertyType.IsInterface) && property.PropertyType != typeof(string) && property.GetValue(doc) != null)
{
if (typeof(IList).IsAssignableFrom(property.PropertyType))
{
#region 集合類型
int i = 0;
var subObj = property.GetValue(doc);
foreach (var item in subObj as IList)
{
if (item.GetType().IsClass || item.GetType().IsInterface)
{
updateList.AddRange(BuildUpdateDefinition<TDoc>(doc, string.Format("{0}.{1}", key, i)));
}
else
{
updateList.Add(Builders<TDoc>.Update.Set(string.Format("{0}.{1}", key, i), item));
}
i++;
}
#endregion
}
else
{
#region 實體類型
//複雜類型,導航屬性,類對象和集合對象
var subObj = property.GetValue(doc);
foreach (var sub in property.PropertyType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
updateList.Add(Builders<TDoc>.Update.Set(string.Format("{0}.{1}", key, sub.Name), sub.GetValue(subObj)));
}
#endregion
}
}
else //簡單類型
{
updateList.Add(Builders<TDoc>.Update.Set(key, property.GetValue(doc)));
}
}
return updateList;
}
private void CreateIndex<TDoc>(IMongoCollection<TDoc> col, string[] indexFields, CreateIndexOptions options = null)
{
if (indexFields == null)
{
return;
}
var indexKeys = Builders<TDoc>.IndexKeys;
IndexKeysDefinition<TDoc> keys = null;
if (indexFields.Length > 0)
{
keys = indexKeys.Descending(indexFields[0]);
}
for (var i = 1; i < indexFields.Length; i++)
{
var strIndex = indexFields[i];
keys = keys.Descending(strIndex);
}
if (keys != null)
{
col.Indexes.CreateOne(keys, options);
}
}
#endregion
public void CreateCollectionIndex<TDoc>(string collectionName, string[] indexFields, CreateIndexOptions options = null)
{
CreateIndex(GetMongoCollection<TDoc>(collectionName), indexFields, options);
}
public void CreateCollection<TDoc>(string[] indexFields = null, CreateIndexOptions options = null)
{
string collectionName = typeof(TDoc).Name;
CreateCollection<TDoc>(collectionName, indexFields, options);
}
public void CreateCollection<TDoc>(string collectionName, string[] indexFields = null, CreateIndexOptions options = null)
{
var mongoDatabase = GetMongoDatabase();
mongoDatabase.CreateCollection(collectionName);
CreateIndex(GetMongoCollection<TDoc>(collectionName), indexFields, options);
}
public List<TDoc> Find<TDoc>(Expression<Func<TDoc, bool>> filter, FindOptions options = null)
{
string collectionName = typeof(TDoc).Name;
return Find<TDoc>(collectionName, filter, options);
}
public List<TDoc> Find<TDoc>(string collectionName, Expression<Func<TDoc, bool>> filter, FindOptions options = null)
{
var colleciton = GetMongoCollection<TDoc>(collectionName);
return colleciton.Find(filter, options).ToList();
}
public List<TDoc> FindByPage<TDoc, TResult>(Expression<Func<TDoc, bool>> filter, Expression<Func<TDoc, TResult>> keySelector, int pageIndex, int pageSize, out int rsCount)
{
string collectionName = typeof(TDoc).Name;
return FindByPage<TDoc, TResult>(collectionName, filter, keySelector, pageIndex, pageSize, out rsCount);
}
public List<TDoc> FindByPage<TDoc, TResult>(string collectionName, Expression<Func<TDoc, bool>> filter, Expression<Func<TDoc, TResult>> keySelector, int pageIndex, int pageSize, out int rsCount)
{
var colleciton = GetMongoCollection<TDoc>(collectionName);
rsCount = colleciton.AsQueryable().Where(filter).Count();
int pageCount = rsCount / pageSize + ((rsCount % pageSize) > 0 ? 1 : 0);
if (pageIndex > pageCount) pageIndex = pageCount;
if (pageIndex <= 0) pageIndex = 1;
return colleciton.AsQueryable(new AggregateOptions { AllowDiskUse = true }).Where(filter).OrderByDescending(keySelector).Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
}
public void Insert<TDoc>(TDoc doc, InsertOneOptions options = null)
{
string collectionName = typeof(TDoc).Name;
Insert<TDoc>(collectionName, doc, options);
}
public void Insert<TDoc>(string collectionName, TDoc doc, InsertOneOptions options = null)
{
var colleciton = GetMongoCollection<TDoc>(collectionName);
colleciton.InsertOne(doc, options);
}
public void InsertMany<TDoc>(IEnumerable<TDoc> docs, InsertManyOptions options = null)
{
string collectionName = typeof(TDoc).Name;
InsertMany<TDoc>(collectionName, docs, options);
}
public void InsertMany<TDoc>(string collectionName, IEnumerable<TDoc> docs, InsertManyOptions options = null)
{
var colleciton = GetMongoCollection<TDoc>(collectionName);
colleciton.InsertMany(docs, options);
}
public void Update<TDoc>(TDoc doc, Expression<Func<TDoc, bool>> filter, UpdateOptions options = null)
{
string collectionName = typeof(TDoc).Name;
var colleciton = GetMongoCollection<TDoc>(collectionName);
List<UpdateDefinition<TDoc>> updateList = BuildUpdateDefinition<TDoc>(doc, null);
colleciton.UpdateOne(filter, Builders<TDoc>.Update.Combine(updateList), options);
}
public void Update<TDoc>(string collectionName, TDoc doc, Expression<Func<TDoc, bool>> filter, UpdateOptions options = null)
{
var colleciton = GetMongoCollection<TDoc>(collectionName);
List<UpdateDefinition<TDoc>> updateList = BuildUpdateDefinition<TDoc>(doc, null);
colleciton.UpdateOne(filter, Builders<TDoc>.Update.Combine(updateList), options);
}
public void Update<TDoc>(TDoc doc, Expression<Func<TDoc, bool>> filter, UpdateDefinition<TDoc> updateFields, UpdateOptions options = null)
{
string collectionName = typeof(TDoc).Name;
Update<TDoc>(collectionName, doc, filter, updateFields, options);
}
public void Update<TDoc>(string collectionName, TDoc doc, Expression<Func<TDoc, bool>> filter, UpdateDefinition<TDoc> updateFields, UpdateOptions options = null)
{
var colleciton = GetMongoCollection<TDoc>(collectionName);
colleciton.UpdateOne(filter, updateFields, options);
}
public void UpdateMany<TDoc>(TDoc doc, Expression<Func<TDoc, bool>> filter, UpdateOptions options = null)
{
string collectionName = typeof(TDoc).Name;
UpdateMany<TDoc>(collectionName, doc, filter, options);
}
public void UpdateMany<TDoc>(string collectionName, TDoc doc, Expression<Func<TDoc, bool>> filter, UpdateOptions options = null)
{
var colleciton = GetMongoCollection<TDoc>(collectionName);
List<UpdateDefinition<TDoc>> updateList = BuildUpdateDefinition<TDoc>(doc, null);
colleciton.UpdateMany(filter, Builders<TDoc>.Update.Combine(updateList), options);
}
public void Delete<TDoc>(Expression<Func<TDoc, bool>> filter, DeleteOptions options = null)
{
string collectionName = typeof(TDoc).Name;
Delete<TDoc>(collectionName, filter, options);
}
public void Delete<TDoc>(string collectionName, Expression<Func<TDoc, bool>> filter, DeleteOptions options = null)
{
var colleciton = GetMongoCollection<TDoc>(collectionName);
colleciton.DeleteOne(filter, options);
}
public void DeleteMany<TDoc>(Expression<Func<TDoc, bool>> filter, DeleteOptions options = null)
{
string collectionName = typeof(TDoc).Name;
DeleteMany<TDoc>(collectionName, filter, options);
}
public void DeleteMany<TDoc>(string collectionName, Expression<Func<TDoc, bool>> filter, DeleteOptions options = null)
{
var colleciton = GetMongoCollection<TDoc>(collectionName);
colleciton.DeleteMany(filter, options);
}
public void ClearCollection<TDoc>(string collectionName)
{
var colleciton = GetMongoCollection<TDoc>(collectionName);
var inddexs = colleciton.Indexes.List();
List<IEnumerable<BsonDocument>> docIndexs = new List<IEnumerable<BsonDocument>>();
while (inddexs.MoveNext())
{
docIndexs.Add(inddexs.Current);
}
var mongoDatabase = GetMongoDatabase();
mongoDatabase.DropCollection(collectionName);
if (!CollectionExists(mongoDatabase, collectionName))
{
CreateCollection<TDoc>(collectionName);
}
if (docIndexs.Count > 0)
{
colleciton = mongoDatabase.GetCollection<TDoc>(collectionName);
foreach (var index in docIndexs)
{
foreach (IndexKeysDefinition<TDoc> indexItem in index)
{
try
{
colleciton.Indexes.CreateOne(indexItem);
}
catch
{ }
}
}
}
}
}
}
對上述代碼中幾個特别的點進行簡要說明:
1.由于MongoClient.GetDatabase 擷取DB、MongoClient.GetCollection<TDoc> 擷取文檔(也可稱為表)的方法 都有一個特點,即:如果指定的DB名稱、Collection名稱不存在,則會直接建立,但有的時候可能是因為DB名稱、Collection名稱寫錯了導緻誤建立了的DB或Collection,那就引起不必要的麻煩,故在MongoDbCsharpHelper類類内部封裝了兩個私有的方法:DatabaseExists(判斷DB是否存在,如是連接配接的賬号沒有檢索DB的權限可能會報錯,故代碼中加了直接傳回true)、CollectionExists(判斷Collection是否存在);
2.每個CRUD方法,我都分别重載了兩個方法,一個是無需指定Collection名稱,一個是需要指定Collection名稱,為什麼這麼做呢?原因很簡單,因為有時Collection的結構是相同的但又是不同的Collection,這時TDoc是同一個實體類,但collectionName卻是不同的;
3.分頁查詢的時候如果Collection的資料量比較大,那麼就會報類似錯誤:exception: Sort exceeded memory limit of 104857600 bytes, but did not opt in to external sorting. Aborting operation. Pass allowDiskUse:true,根據報錯提示,我們在查詢大資料量時增加AggregateOptions對象,如: colleciton.AsQueryable(new AggregateOptions { AllowDiskUse = true })
4.ClearCollection清除Collection的所有資料,如果Collection的資料量非常大,那麼直接使用colleciton.DeleteMany可能需要很久,有沒有類似SQL SERVER 的truncate table的方法呢?經過多方論證,很遺憾并沒有找到同類功能的方法,隻有DropCollection方法,而這個DropCollection方法是直接删除Collection,當然包括Collection的所有資料,效率也非常高,但是由于是Drop,Collection就不存在了,如果再通路有可能會報Collection不存在的錯誤,那有沒有好的辦法解決了,當然有,那就是先DropCollection 然後再CreateCollection,最後别忘了把原有的索引插入到新建立的Collection中,這樣就實作了truncate 初始化表的作用,當然在建立索引的時候,有的時候可能報報錯(如:_id),因為_id預設就會被建立索引,再建立可能就會報錯,故colleciton.Indexes.CreateOne外我加了try catch,如果報錯則忽略。
5.CreateCollection(建立集合)、CreateCollectionIndex(建立集合索引)因為有的時候我們需要明确的去建立一個Collection或對已有的Collection建立索引,如果通過shell指令會非常不友善,故在此封裝了一下。
使用示例如下:
var mongoDbHelper = new MongoDbCsharpHelper("MongoDbConnectionString", "LogDB");
mongoDbHelper.CreateCollection<SysLogInfo>("SysLog1",new[]{"LogDT"});
mongoDbHelper.Find<SysLogInfo>("SysLog1", t => t.Level == "Info");
int rsCount=0;
mongoDbHelper.FindByPage<SysLogInfo, SysLogInfo>("SysLog1",t=>t.Level=="Info",t=>t,1,20,out rsCount);
mongoDbHelper.Insert<SysLogInfo>("SysLog1",new SysLogInfo { LogDT = DateTime.Now, Level = "Info", Msg = "測試消息" });
mongoDbHelper.Update<SysLogInfo>("SysLog1",new SysLogInfo { LogDT = DateTime.Now, Level = "Error", Msg = "測試消息2" },t => t.LogDT==new DateTime(1900,1,1));
mongoDbHelper.Delete<SysLogInfo>(t => t.Level == "Info");
mongoDbHelper.ClearCollection<SysLogInfo>("SysLog1");