mongodb 这三个大坑踩过吗?

背景讲故事
前段时间有位朋友在微信群问 , 在向 mongodb 中插入的时间为啥取出来的时候少了 8 个小时 , 8 在时间处理上是一个非常敏感的数字 , 又吉利又是一个普适的话题 , 后来我想想初次使用 mongodb 的朋友一定还会遇到各种新坑 , 比如说: 插入的数据取不出来 , 看不爽的 ObjectID , 时区不对等等 , 这篇就和大家一起聊一聊 。
1号坑 插进去的数据取不出来案例展示
这个问题是使用强类型操作 mongodb 你一定会遇到的问题 , 案例代码如下:
class Program{static void Main(string[] args){var client = new MongoClient("mongodb://192.168.1.128:27017");var database = client.GetDatabase("school");var table = database.GetCollection<Student>("student");table.InsertOne(new Student() { StudentName = "hxc", Created = DateTime.Now });var query = table.AsQueryable().ToList();}}public class Student{public string StudentName { get; set; }public DateTime Created { get; set; }}

mongodb 这三个大坑踩过吗?

文章插图
 
我去 , 这么简单的一个操作还报错 , 要初学到放弃吗? 挺急的 , 在线等!
堆栈中深挖源码
作为一个码农还得有钻研代码的能力 , 从错误信息中看说有一个 _id 不匹配 student 中的任何一个字段 , 然后把全部堆栈找出来 。
System.FormatExceptionHResult=0x80131537Message=Element '_id' does not match any field or property of class Newtonsoft.Test.Student.Source=MongoDB.DriverStackTrace:at MongoDB.Driver.Linq.MongoQueryProviderImpl`1.Execute(Expression expression)at MongoDB.Driver.Linq.MongoQueryableImpl`2.GetEnumerator()at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)at Newtonsoft.Test.Program.Main(String[] args) in E:crmJsonNetNewtonsoft.TestProgram.cs:line 32接下来就用 DNSpy 去定位一下 MongoQueryProviderImpl.Execute 到底干的啥 , 截图如下:
mongodb 这三个大坑踩过吗?

文章插图
 
我去 , 这代码硬核哈 , 用了 LambdaExpression 表达式树 , 我们知道表达式树用于将一个领域的查询结构转换为另一个领域的查询结构 , 但要寻找如何构建这个方法体就比较耗时间了 , 接下来还是用 dnspy 去调试看看有没有更深层次的堆栈 。
mongodb 这三个大坑踩过吗?

文章插图
 
这个堆栈信息就非常清楚了 , 原来是在 MongoDB.Bson.Serialization.BsonClassMapSerializer.DeserializeClass 方法中出了问题 , 接下来找到问题代码 , 简化如下:
public TClass DeserializeClass(BsonDeserializationContext context){ while (reader.ReadBsonType() != BsonType.EndOfDocument) {TrieNameDecoder<int> trieNameDecoder = new TrieNameDecoder<int>(elementTrie);string text = reader.ReadName(trieNameDecoder);if (trieNameDecoder.Found){int value = https://www.isolves.com/it/sjk/bk/2020-08-19/trieNameDecoder.Value;BsonMemberMap bsonMemberMap = allMemberMaps[value];}else{if (!this._classMap.IgnoreExtraElements){throw new FormatException(string.Format("Element '{0}' does not match any field or property of class {1}.", text, this._classMap.ClassType.FullName));}reader.SkipValue();} }}上面的代码逻辑非常清楚 , 要么 student 中存在 _id 字段 , 也就是 trieNameDecoder.Found, 要么使用 忽略未知的元素 , 也就是 this._classMap.IgnoreExtraElements , 添加字段容易 , 接下来看看怎么让 IgnoreExtraElements = true , 找了一圈源码 , 发现这里是关键:
mongodb 这三个大坑踩过吗?

文章插图
 
也就是: foreach (IBsonClassMapAttribute bsonClassMapAttribute in classMap.ClassType.GetTypeInfo().GetCustomAttributes(false).OfType<IBsonClassMapAttribute>())这句话 , 这里的 classMap 就是 student , 只有让 foreach 得以执行才能有望 classMap.IgnoreExtraElements 赋值为 true  , 接下来找找看在类上有没有类似 IgnoreExtraElements 的 Attribute , 嘿嘿 , 还真有一个类似的: BsonIgnoreExtraElements  , 如下代码:
[BsonIgnoreExtraElements]public class Student{public string StudentName { get; set; }public DateTime Created { get; set; }}接下来执行一下代码 , 可以看到问题搞定:


推荐阅读