這些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被覆蓋.ui

 

因此咱們須要採用原子操做來修改用戶信息,咱們調整代碼以下spa

UpdateDefinition<Persion> update = Builders<Persion>.Update.Set(y => y.Name, "B");
collection.UpdateOne(x => x.Id == "user1", update);

這樣就把修改操做交給數據庫來執行,僅修改要修改的屬性,避免操做互相影響;code

看到這裏有的大神就會吐槽,這麼簡單的東西也好意思拿來講,請在耐心往下看,重點在下邊。對象

重點

若是咱們的數據結構相似這樣:blog

/// <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對象了。

結語

做爲一名博客萌新,我只是將我遇到的問題總結下來並分享給你們,有不對的地方,務必幫忙指正。

固然上面的內容對於大佬來講多是常規操做,但若是對你有一點點用處,請點贊,評論,並關注下。

後面我會將我在工做學習中遇到的有趣的問題分享給你們,謝謝!!!

相關文章
相關標籤/搜索