.net平台的MongoDB使用

來源:互聯網
上載者:User

標籤:space   mem   就會   tor   表關聯   資料類型   compile   並且   double   

網址:http://www.cnblogs.com/skychen1218/p/6595759.html 前言

  最近花了點時間玩了下MongoDB.Driver,進行封裝了工具庫,平常也會經常用到MongoDB,因此寫一篇文章梳理知識同時把自己的成果分享給大家。

  本篇會設計到Lambda運算式的解析,有興趣的同學也看看我之前寫的《運算式樹狀架構的解析》。

  文章最後會給出源碼。

MongoDB簡介

  MongoDB是一個基於分布式檔案儲存體的非關係型資料庫,相比於其他NoSql它支援複雜的查詢。

  文本是類似JSON的BSON格式,BSON是在JSON的基礎上進化:更快的遍曆、操作更簡易、更多的資料類型。因此MongoDB可以儲存比較複雜的資料類型,同樣也支援建立索引。

  MongoDB的概念有:

  • DataBase(庫)
  • Collections(集合),類似於關係型資料庫的表
  • Document(文檔),類似於關係型資料庫的一條資料

  

MongoDB優缺點
  • 優點
  1. 高效性,內建GridFS,從而達到海量資料存放區,並且滿足大資料集的快速範圍查詢。
  2. 高擴充性,分區使MongoDB的有更高的輸送量,複製使MongoDB更高的可用性。
  3. BSON文檔,易於理解、查看,
  4. 免費
  • 缺點
  1. 不支援事務
  2. 不支援表關聯
  3. 不耗CPU卻耗記憶體
  4. 沒有成熟的管理工具
MongoDB使用情境

  擁有高效的儲存的特點,讓MongoDB用在動作記錄記錄是非常流行的做法。

  隨著版本的升級提供更加強大的功能,產品逐漸成熟用在主業務也很多,例如電商行業的訂單系統與包裹跟蹤模組,海量的主訂單與訂單明細,包裹的狀態變更資訊。

  然而因為BSON文檔的儲存方式,使平常的開發的思維模式有所變更。舉個栗子,傳統用關係型資料庫,訂單模組就會分主訂單表和訂單明細表,建立訂單就會用事務同時添加兩表的資料,尋找訂單也會通過兩表關聯查詢出來。但是使用MongoDB,主訂單表與其明細,將會以一個完整的對象儲存為文檔。

  也因為不支援事務、表關聯的原因,它更加適合用作於一個完整的業務模組。

  部分朋友會帶著一個問題,非關係型資料庫和關係型資料庫哪個更好。我認為,誰都無法代替誰,一般情況下,非關係型資料庫更多的作為關係型資料庫擴充,用好了效果甚佳,濫用了只會寸步難行。

  

MongoDB安裝

  本來想寫的,相應的文章在園子太多了,借用一位仁兄的博文,傳送門

  MongoDB:https://www.mongodb.com/download-center#community

  管理工具:Robomongo,傳送門

MongoDB.Driver的使用

  

  建立一個控制台,到Nuget下載MongoDB.Driver。寫入以下代碼:

 

using System;
using FrameWork.MongoDB.MongoDbConfig;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;

namespace FrameWork.MongoDb.Demo
{
class Program
{
static void Main(string[] args)
{
var database = "testdatabase";
var collection = "TestMongo";
var db = new MongoClient("您的地址").GetDatabase(database);
var coll = db.GetCollection<TestMongo>(collection);

var entity = new TestMongo
{
Name = "SkyChen",
Amount = 100,
CreateDateTime = DateTime.Now
};

coll.InsertOneAsync(entity).ConfigureAwait(false);

}
}

public class TestMongo : MongoEntity
{

[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime CreateDateTime { get; set; }

public decimal Amount { get; set; }

public string Name { get; set; }

}
}

  第一個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;
}
}

 

Lambda運算式更新封裝

  曾經用過其他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

.net平台的MongoDB使用

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.