最近花了點時間玩了下MongoDB.Driver,進行封裝了工具庫,日常也會常常用到MongoDB,所以寫一篇文章梳理知識同時把本身的成果分享給你們。html
本篇會設計到Lambda表達式的解析,有興趣的同窗也看看我以前寫的《表達式樹的解析》。node
文章最後會給出源碼下載地址。git
MongoDB是一個基於分佈式文件存儲的非關係型數據庫,相比於其餘NoSql它支持複雜的查詢。github
文本是相似JSON的BSON格式,BSON是在JSON的基礎上進化:更快的遍歷、操做更簡易、更多的數據類型。所以MongoDB能夠存儲比較複雜的數據類型,一樣也支持創建索引。mongodb
MongoDB的概念有:數據庫
擁有高效的存儲的特色,讓MongoDB用在操做日誌記錄是很是流行的作法。express
隨着版本的升級提供更增強大的功能,產品逐漸成熟用在主業務也不少,例如電商行業的訂單系統與包裹跟蹤模塊,海量的主訂單與訂單明細,包裹的狀態變動信息。數組
然而由於BSON文檔的存儲方式,使日常的開發的思惟模式有所變動。舉個栗子,傳統用關係型數據庫,訂單模塊就會分主訂單表和訂單明細表,建立訂單就會用事務同時添加兩表的數據,查找訂單也會經過兩表關聯查詢出來。可是使用MongoDB,主訂單表與其明細,將會以一個完整的對象保存爲文檔。分佈式
也由於不支持事務、表關聯的緣由,它更加適合用做於一個完整的業務模塊。ide
部分朋友會帶着一個問題,非關係型數據庫和關係型數據庫哪一個更好。我認爲,誰都沒法代替誰,通常狀況下,非關係型數據庫更多的做爲關係型數據庫擴展,用好了效果甚佳,濫用了只會步履維艱。
原本想寫的,相應的文章在園子太多了,借用一位仁兄的博文,傳送門
MongoDB下載地址:https://www.mongodb.com/download-center#community
管理工具:Robomongo,傳送門
建立一個控制檯,到Nuget下載MongoDB.Driver。寫入如下代碼:
1 using System; 2 using FrameWork.MongoDB.MongoDbConfig; 3 using MongoDB.Bson.Serialization.Attributes; 4 using MongoDB.Driver; 5 6 namespace FrameWork.MongoDb.Demo 7 { 8 class Program 9 { 10 static void Main(string[] args) 11 { 12 var database = "testdatabase"; 13 var collection = "TestMongo"; 14 var db = new MongoClient("您的地址").GetDatabase(database); 15 var coll = db.GetCollection<TestMongo>(collection); 16 17 var entity = new TestMongo 18 { 19 Name = "SkyChen", 20 Amount = 100, 21 CreateDateTime = DateTime.Now 22 }; 23 24 coll.InsertOneAsync(entity).ConfigureAwait(false); 25 26 } 27 } 28 29 public class TestMongo : MongoEntity 30 { 31 32 [BsonDateTimeOptions(Kind = DateTimeKind.Local)] 33 public DateTime CreateDateTime { get; set; } 34 35 public decimal Amount { get; set; } 36 37 public string Name { get; set; } 38 39 } 40 }
第一個demo:添加數據就完成了。F12能夠看到IMongoCollection這個接口,增刪改查都有,注意分One和Many。基礎的使用就不扯過多,在文章尾部的代碼已經提供增刪改查的封裝。
增刪查的封裝相對簡單,可是MongoDB.Driver提供的update的稍微比較特殊。經過Builders<T>.Update.Set(_fieldname, value)更新指定字段名,有多個字段名須要修改,就要經過new UpdateDefinitionBuilder<T>().Combine(updateDefinitionList)去完成
然而,這種方式並不適用於咱們實際開發,所以須要對Update方法進行 實體更新封裝和Lambda更新封裝。
經過ID做爲過濾條件更新整個實體在實際工做中是常有的。既然經過ID做爲條件,那麼只能經過UpdateOneAsync進行約束更新一條數據。更新的字段能夠經過反射實體對象進行遍歷屬性。下邊是實現代碼:
/// <summary> /// mongodb擴展方法 /// </summary> internal static class MongoDbExtension { /// <summary> /// 獲取更新信息 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="entity"></param> /// <returns></returns> internal static UpdateDefinition<T> GetUpdateDefinition<T>(this T entity) { var properties = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public); var updateDefinitionList = GetUpdateDefinitionList<T>(properties, entity); var updateDefinitionBuilder = new UpdateDefinitionBuilder<T>().Combine(updateDefinitionList); return updateDefinitionBuilder; } /// <summary> /// 獲取更新信息 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="propertyInfos"></param> /// <param name="entity"></param> /// <returns></returns> internal static List<UpdateDefinition<T>> GetUpdateDefinitionList<T>(PropertyInfo[] propertyInfos, object entity) { var updateDefinitionList = new List<UpdateDefinition<T>>(); propertyInfos = propertyInfos.Where(a => a.Name != "_id").ToArray(); foreach (var propertyInfo in propertyInfos) { if (propertyInfo.PropertyType.IsArray || typeof(IList).IsAssignableFrom(propertyInfo.PropertyType)) { var value = propertyInfo.GetValue(entity) as IList; var filedName = propertyInfo.Name; updateDefinitionList.Add(Builders<T>.Update.Set(filedName, value)); } else { var value = propertyInfo.GetValue(entity); if (propertyInfo.PropertyType == typeof(decimal)) value = value.ToString(); var filedName = propertyInfo.Name; updateDefinitionList.Add(Builders<T>.Update.Set(filedName, value)); } } return updateDefinitionList; } }
曾經用過其餘ORM都清楚Lambda表達式使用是很是頻繁的,MongoDB.Driver已經支持Lambda表達式的過濾條件,但沒支持部分字段更新,所以由咱們本身來寫解析。下邊是現實代碼:
#region Mongo更新字段表達式解析 /// <summary> /// Mongo更新字段表達式解析 /// </summary> /// <typeparam name="T"></typeparam> public class MongoDbExpression<T> : ExpressionVisitor { #region 成員變量 /// <summary> /// 更新列表 /// </summary> internal List<UpdateDefinition<T>> UpdateDefinitionList = new List<UpdateDefinition<T>>(); private string _fieldname; #endregion #region 獲取更新列表 /// <summary> /// 獲取更新列表 /// </summary> /// <param name="expression"></param> /// <returns></returns> public static List<UpdateDefinition<T>> GetUpdateDefinition(Expression<Func<T, T>> expression) { var mongoDb = new MongoDbExpression<T>(); mongoDb.Resolve(expression); return mongoDb.UpdateDefinitionList; } #endregion #region 解析表達式 /// <summary> /// 解析表達式 /// </summary> /// <param name="expression"></param> private void Resolve(Expression<Func<T, T>> expression) { Visit(expression); } #endregion #region 訪問對象初始化表達式 /// <summary> /// 訪問對象初始化表達式 /// </summary> /// <param name="node"></param> /// <returns></returns> protected override Expression VisitMemberInit(MemberInitExpression node) { var bingdings = node.Bindings; foreach (var item in bingdings) { var memberAssignment = (MemberAssignment)item; _fieldname = item.Member.Name; if (memberAssignment.Expression.NodeType == ExpressionType.MemberInit) { var lambda = Expression.Lambda<Func<object>>(Expression.Convert(memberAssignment.Expression, typeof(object))); var value = lambda.Compile().Invoke(); UpdateDefinitionList.Add(Builders<T>.Update.Set(_fieldname, value)); } else { Visit(memberAssignment.Expression); } } return node; } #endregion #region 訪問二元表達式 /// <summary> /// 訪問二元表達式 /// </summary> /// <param name="node"></param> /// <returns></returns> protected override Expression VisitBinary(BinaryExpression node) { UpdateDefinition<T> updateDefinition; var value = ((ConstantExpression)node.Right).Value; if (node.Type == typeof(int)) { var realValue = (int)value; if (node.NodeType == ExpressionType.Decrement) realValue = -realValue; updateDefinition = Builders<T>.Update.Inc(_fieldname, realValue); } else if (node.Type == typeof(long)) { var realValue = (long)value; if (node.NodeType == ExpressionType.Decrement) realValue = -realValue; updateDefinition = Builders<T>.Update.Inc(_fieldname, realValue); } else if (node.Type == typeof(double)) { var realValue = (double)value; if (node.NodeType == ExpressionType.Decrement) realValue = -realValue; updateDefinition = Builders<T>.Update.Inc(_fieldname, realValue); } else if (node.Type == typeof(decimal)) { var realValue = (decimal)value; if (node.NodeType == ExpressionType.Decrement) realValue = -realValue; updateDefinition = Builders<T>.Update.Inc(_fieldname, realValue); } else if (node.Type == typeof(float)) { var realValue = (float)value; if (node.NodeType == ExpressionType.Decrement) realValue = -realValue; updateDefinition = Builders<T>.Update.Inc(_fieldname, realValue); } else { throw new Exception(_fieldname + "不支持該類型操做"); } UpdateDefinitionList.Add(updateDefinition); return node; } #endregion #region 訪問數組表達式 /// <summary> /// 訪問數組表達式 /// </summary> /// <param name="node"></param> /// <returns></returns> protected override Expression VisitNewArray(NewArrayExpression node) { var listLambda = Expression.Lambda<Func<IList>>(node); var list = listLambda.Compile().Invoke(); UpdateDefinitionList.Add(Builders<T>.Update.Set(_fieldname, list)); return node; } /// <summary> /// 訪問集合表達式 /// </summary> /// <param name="node"></param> /// <returns></returns> protected override Expression VisitListInit(ListInitExpression node) { var listLambda = Expression.Lambda<Func<IList>>(node); var list = listLambda.Compile().Invoke(); UpdateDefinitionList.Add(Builders<T>.Update.Set(_fieldname, list)); return node; } #endregion #region 訪問常量表達式 /// <summary> /// 訪問常量表達式 /// </summary> /// <param name="node"></param> /// <returns></returns> protected override Expression VisitConstant(ConstantExpression node) { var value = node.Type.IsEnum ? (int)node.Value : node.Value; UpdateDefinitionList.Add(Builders<T>.Update.Set(_fieldname, value)); return node; } #endregion #region 訪問成員表達式 /// <summary> /// 訪問成員表達式 /// </summary> /// <param name="node"></param> /// <returns></returns> protected override Expression VisitMember(MemberExpression node) { if (node.Type.GetInterfaces().Any(a => a.Name == "IList")) { var lambda = Expression.Lambda<Func<IList>>(node); var value = lambda.Compile().Invoke(); UpdateDefinitionList.Add(Builders<T>.Update.Set(_fieldname, value)); } else { var lambda = Expression.Lambda<Func<object>>(Expression.Convert(node, typeof(object))); var value = lambda.Compile().Invoke(); if (node.Type.IsEnum) value = (int)value; UpdateDefinitionList.Add(Builders<T>.Update.Set(_fieldname, value)); } return node; } #endregion } #endregion
對於Lambda表達式的封裝,我側重講一下。假若有一段這樣的更新代碼:
new MongoDbService().Update<User>(a => a._id == "d99ce40d7a0b49768b74735b91f2aa75", a => new User { AddressList = new List<string> { "number1", "number2" }, Age = 10, BirthDateTime = DateTime.Now, Name = "skychen", NumList = new List<int> { 1211,23344 }, Sex = Sex.Woman, Son = new User { Name = "xiaochenpi", Age = 1 } });
那麼,咱們能夠調試監視看看(下圖),咱們能夠得出兩個重要信息:
1.Expression<Func<T, T>>解析出來Body的NodeType是MemberInit
2.Bindings裏有須要修改的字段信息。
再調試進去看看Bindings的第一項,咱們又能夠了解了幾個重要信息。
1.Bindings裏的元素是MemberAssignment類型。
2.Member能取到Name屬性,也就是字段名
3.Expression屬性,使用 Expression.Lambda,進行Compile().Invoke()就能獲得咱們須要的值。
fileName和Value都能取到了,那麼更新天然能解決了。
上圖是源碼的部分核心代碼,奇怪的是,我並無在VisitMemberInit裏進行遍歷Bindings後進行Update.Set,而是將item的Expression屬性再一次訪問。那是由於我須要針對不一樣的數據類型進行處理。例如:
常量,我能夠定義一個object value進行去接收,若是遇到枚舉我須要強轉成整型。
集合與數組,假如草率的使用object類型,object value = Expression.Lambda<Func<object>>(node).Compile().Invoke(),那麼更新到MongoDB裏就會有bug,奇怪的_t,_v就會出現。以此我須要定義爲IList才能解決這個問題。
此外,工做中還會遇到金額或者數量自增的狀況。Amount = a.Amount+9.9M,Count =a.Count-1。 MongoDB.Driver提供了Builders<T>.Update.Inc方法,所以重寫二元表達式進行封裝。
通過測試,官方驅動2.4.3和2.4.4版本對類型IList支持有問題,以下圖,因此如今封裝版本最高支持到2.4.2。
不知道有多少朋友直接拖到文章尾部直接下載源碼的。。。。。。
若是對您有用,麻煩您推薦一下。
此外還要感謝非非大哥哥,率先作了個人小白鼠給我提出了難得的BUG,否則我還真不敢放出源碼。
若是有什麼問題和建議,能夠在下方評論,我會及時回覆。
雙手奉上源碼:https://github.com/SkyChenSky/Framework.MongoDB.git