這些MongoDB的隱藏操作你真的都掌握了嗎?反正我是剛知道
背景
最近公司系統還原使用者時偶爾會出現部分使用者資訊未還原成功的問題,最為開發人員,最頭疼的不是程式碼存在bug,而是測試發現了bug,但一旦我去重現,它就不見了。Are you kidding me?
經過漫長的溝通與嘗試,終於發現了端倪,這個問題只有在多人同時操作修改同一使用者資訊時才會出現。
哦,那你死定了,小bug。
分析
經過短暫的程式碼review,發現還原使用者時,程式碼中會先把使用者獲取出來,然後修改使用者資訊,最後再將修改後的使用者更新至資料庫中。
var user1 = collection.Find(x => x.Id == "user1").FirstOrDefault(); user1.Name = "B"; collection.ReplaceOneAsync(x => x.Id == "user1", user1);
這就導致程式碼併發執行時,後面的可能覆蓋前方的操作。
如下圖操作1及操作2執行結束後,資料庫中使用者1的名稱為A,年齡為18;操作1的修改使用者名稱稱為B被覆蓋.
所以我們需要採用原子操作來修改使用者資訊,我們調整程式碼如下
UpdateDefinition<Persion> update = Builders<Persion>.Update.Set(y => y.Name, "B"); collection.UpdateOne(x => x.Id == "user1", update);
這樣就把修改操作交給資料庫來執行,僅修改要修改的屬性,避免操作互相影響;
看到這裡有的大神就會吐槽,這麼簡單的東西也好意思拿來說,請在耐心往下看,重點在下邊。
重點
如果我們的資料結構類似這樣:
/// <summary> /// 人 /// </summary> [BsonIgnoreExtraElements] public class Persion : BaseEntity { /// <summary> /// 名稱 /// </summary> [BsonElement("name")] public string Name { get; set; } /// <summary> /// 年齡 /// </summary> [BsonElement("age")] public int Age { get; set; } /// <summary> /// 親戚 /// </summary> [BsonElement("relatives")] public List<Relative> Relatives { get; set; } }
/// <summary> /// 親屬 /// </summary> public class Relative { /// <summary> /// 名稱 /// </summary> [BsonElement("name")] public string Name { get; set; } /// <summary> /// 與本人關係 /// </summary> [BsonElement("relationship")] public Relationship Relationship { get; set; } }
/// <summary> /// 與本人關係 /// </summary> public enum Relationship { /// <summary> /// 爸爸 /// </summary> father = 0, /// <summary> /// 媽媽 /// </summary> mother = 1, /// <summary> /// 兒子 /// </summary> son = 2, /// <summary> /// 女兒 /// </summary> daughter = 3, /// <summary> /// 不明 /// </summary> unknow = 100 }
如果我們想更新名稱為“趙小明”的人的名稱為“趙剛”的親戚與本人關係為“爸爸”,請考慮,應該怎麼處理。
注意:人的親戚可以有多個,所以是List。
這時我們就用到了ArrayFilters物件,其存在於UpdateOptions中,如果沒有系統的看過MongoDB的介面,我相信大部分人都會忽略它。
好了,廢話不多說,讓我們來看看它的用法吧
FilterDefinition<Persion> filter = Builders<Persion>.Filter.Where(x => x.Name == "趙小明" && x.Relatives != null && x.Relatives.Count > 0); UpdateDefinition<Persion> update = Builders<Persion>.Update.Set("relatives.$[i].relationship", Relationship.father); var option = new UpdateOptions() { ArrayFilters = new List<ArrayFilterDefinition> { new JsonArrayFilterDefinition<Relationship>("{'i.name': '趙剛'}") } }; collection.UpdateMany(filter, update, option);
可以看到,我們先生成一個查詢條件,名稱為“趙小明”,存在親戚的人;
然後更新其"relatives.$[i].relationship"屬性,為Relationship.father,其中的$[i]為佔位符;
再生成一個決定$[i]值的JsonArrayFilterDefinition<Relationship>("{'i.name': '趙剛'}");
最後用這些條件來更新資料庫。
引申
好了,更新是實現了,那有求知慾的小夥伴就會想查詢怎麼辦呢?
這還不簡單,一行語句就搞定了
var user = collection.Find(x => x.Name == "趙小明" && x.Relatives != null && x.Relatives.Count > 0 && x.Relatives.Exists(y => y.Name == "趙剛")).FirstOrDefault();
沒錯,但如果此人存在幾千萬個親戚(現實生活中怎麼可能,笑),我只需要其與一個名為“趙剛”的親戚的關係,不想把整個物件都載入到記憶體中怎麼辦?
這時我們就需要用到ProjectionDefinitionBuilder物件了,
FilterDefinition<Persion> filter = Builders<Persion>.Filter.Where(x => x.Name == "趙小明" && x.Relatives != null && x.Relatives.Count > 0); var findOptions = new FindOptions<Persion, Relative>() { Projection = new ProjectionDefinitionBuilder<Persion>().Expression(x => x.Relatives.FirstOrDefault(r => r.Name != "趙剛")) }; Relative cursor = collection.FindSync(filter, findOptions).FirstOrDefault();
我們就得到了我們想要的親戚物件,而不是包含幾千萬親戚資訊的完整Persion物件了。
結語
作為一名部落格萌新,我只是將我遇到的問題總結下來並分享給大家,有不對的地方,務必幫忙指正。
當然上面的內容對於大佬來說可能是常規操作,但如果對你有一點點用處,請點贊,評論,並關注下。
後面我會將我在工作學習中遇到的有趣的問題分享給大家,謝謝!!!