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

結語

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

當然上面的內容對於大佬來說可能是常規操作,但如果對你有一點點用處,請點贊,評論,並關注下。

后面我會將我在工作學習中遇到的有趣的問題分享給大家,謝謝!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM