­

这些MongoDB的隐藏操作你真的都掌握了吗?反正我是刚知道

  • 2020 年 3 月 16 日
  • 筆記

背景

最近公司系统还原用户时偶尔会出现部分用户信息未还原成功的问题,最为开发人员,最头疼的不是代码存在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对象了。

结语

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

当然上面的内容对于大佬来说可能是常规操作,但如果对你有一点点用处,请点赞,评论,并关注下。

后面我会将我在工作学习中遇到的有趣的问题分享给大家,谢谢!!!