这些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对象了。

结语

做为一名博客萌新,我只是将我遇到的问题总结下来并分享给你们,有不对的地方,务必帮忙指正。

固然上面的内容对于大佬来讲多是常规操做,但若是对你有一点点用处,请点赞,评论,并关注下。

后面我会将我在工做学习中遇到的有趣的问题分享给你们,谢谢!!!

相关文章
相关标签/搜索