用过 mongodb 吧, 这三个大坑踩过吗?
一:背景
1. 讲故事
前段时间有位朋友在微信群问,在向 mongodb 中插入的时间为啥取出来的时候少了 8 个小时,8 在时间处理上是一个非常敏感的数字,又吉利又是一个普适的话题,后来我想想初次使用 mongodb 的朋友一定还会遇到各种新坑,比如说: 插入的数据取不出来,看不爽的 ObjectID,时区不对等等,这篇就和大家一起聊一聊。
二: 1号坑 插进去的数据取不出来
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; }
}
我去,这么简单的一个操作还报错,要初学到放弃吗? 挺急的,在线等!
2. 堆栈中深挖源码
作为一个码农还得有钻研代码的能力,从错误信息中看说有一个 _id
不匹配 student 中的任何一个字段,然后把全部堆栈找出来。
System.FormatException
HResult=0x80131537
Message=Element '_id' does not match any field or property of class Newtonsoft.Test.Student.
Source=MongoDB.Driver
StackTrace:
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:\crm\JsonNet\Newtonsoft.Test\Program.cs:line 32
接下来就用 dnspy 去定位一下 MongoQueryProviderImpl.Execute
到底干的啥,截图如下:
我去,这代码硬核哈,用了 LambdaExpression
表达式树,我们知道表达式树用于将一个领域的查询结构转换为另一个领域的查询结构,但要寻找如何构建这个方法体就比较耗时间了,接下来还是用 dnspy 去调试看看有没有更深层次的堆栈。
这个堆栈信息就非常清楚了,原来是在 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 = 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,找了一圈源码,发现这里是关键:
也就是: 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; }
}
接下来执行一下代码,可以看到问题搞定:
如果你想验证的话,可以继续用 dnspy 去验证一下源码哈,如下代码所示:
接下来还有一种办法就是增加 _id 字段,如果你不知道用什么类型接,那就用object就好啦,后续再改成真正的类型。
三: 2号坑 DateTime 时区不对
如果你细心的话,你会发现刚才案例中的 Created 时间是 2020/8/16 4:24:57
, 大家请放心,我不会傻到凌晨4点还在写代码,好了哈,看看到底问题在哪吧, 可以先看看 mongodb 中的记录数据,如下:
{
"_id" : ObjectId("5f38b83e0351908eedac60c9"),
"StudentName" : "hxc",
"Created" : ISODate("2020-08-16T04:38:22.587Z")
}
从 ISODate 可以看出,这是格林威治时间,按照0时区存储,所以这个问题转成了如何在获取数据的时候,自动将 ISO 时间转成 Local 时间就可以了,如果你看过底层源码,你会发现在 mongodb 中每个实体的每个类型都有一个专门的 XXXSerializer
,如下图:
接下来就好好研读一下里面的 Deserialize
方法即可,代码精简后如下:
public override DateTime Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
IBsonReader bsonReader = context.Reader;
BsonType currentBsonType = bsonReader.GetCurrentBsonType();
DateTime value;
switch (this._kind)
{
case DateTimeKind.Unspecified:
case DateTimeKind.Local:
value = DateTime.SpecifyKind(BsonUtils.ToLocalTime(value), this._kind);
break;
case DateTimeKind.Utc:
value = BsonUtils.ToUniversalTime(value);
break;
}
return value;
}
可以看出,如果当前的 this._kind= DateTimeKind.Local
的话,就将 UTC 时间转成 Local 时间,如果你有上一个坑的经验,你大概就知道应该也是用特性注入的,
[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime Created { get; set; }
不信的话,我调试给你看看哈。
接下来再看看 this._kind
是怎么被赋的。
四: 3号坑 自定义ObjectID
在第一个坑中,不知道大家看没看到类似这样的语句: ObjectId("5f38b83e0351908eedac60c9")
,乍一看像是一个 GUID,当然肯定不是,这是mongodb自己组建了一个 number 组合的十六进制表示,姑且不说性能如何,反正看着不是很舒服,毕竟大家都习惯使用 int/long 类型展示的主键ID。
那接下来的问题是:如何改成我自定义的 number ID 呢? 当然可以,只要实现 IIdGenerator
接口即可,那主键ID的生成,我准备用 雪花算法
,完整代码如下:
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() { Created = DateTime.Now });
table.InsertOne(new Student() { Created = DateTime.Now });
}
}
class Student
{
[BsonId(IdGenerator = typeof(MyGenerator))]
public long ID { get; set; }
[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime Created { get; set; }
}
public class MyGenerator : IIdGenerator
{
private static readonly IdWorker worker = new IdWorker(1, 1);
public object GenerateId(object container, object document)
{
return worker.NextId();
}
public bool IsEmpty(object id)
{
return id == null || Convert.ToInt64(id) == 0;
}
}
然后去看一下 mongodb 生成的 json:
四: 总结
好了,这三个坑,我想很多刚接触 mongodb 的朋友是一定会遇到的困惑,总结一下方便后人乘凉,结果不重要,重要的还是探索问题的思路和不择手段。
用过 mongodb 吧, 这三个大坑踩过吗?的更多相关文章
- MongoDB学习笔记三—增删改文档上
插入insert 单条插入 > db.foo.insert({"bar":"baz"}) WriteResult({ }) 批量插入 > db.fo ...
- MongoDB【第三篇】MongoDB基本操作
MongoDB的基本操作包括文档的创建.删除.和更新 文档插入 1.插入 #查看当前都有哪些数据库 > show dbs; local 0.000GB tim 0.000GB #使用 tim数据 ...
- Mongodb学习笔记三(Mongodb索引操作及性能测试)
第三章 索引操作及性能测试 索引在大数据下的重要性就不多说了 下面测试中用到了mongodb的一个客户端工具Robomongo,大家可以在网上选择下载.官网下载地址:http://www.robomo ...
- MongoDB学习笔记三:查询
MongoDB中使用find来进行查询.查询就是返回一个集合中文档的子集,子集合的范围从0个文档到整个集合.find的第一个参数决定了要返回哪些文档,其形式也是一个文档,说明要执行的查询细节.空的查询 ...
- MongoDB 复制集 (三) 内部数据同步
一 数据同步 一个健康的secondary在运行时,会选择一个离自己最近的,数据比自己新的节点进行数据同步.选定节点后,它会从这个节点拉取oplog同步日志,具体流程是这样的: ...
- MongoDB学习笔记(三) 在MVC模式下通过Jqgrid表格操作MongoDB数据
看到下图,是通过Jqgrid实现表格数据的基本增删查改的操作.表格数据增删改是一般企业应用系统开发的常见功能,不过不同的是这个表格数据来源是非关系型的数据库MongoDB.nosql虽然概念新颖,但是 ...
- MongoDB【第三篇】RockMongo 的安装
第一步:准备 1. 安装 Nginx 参照 Nginx[第一篇]安装 2. 安装 php 参照 PHP[第一篇]安装 3. RockMongo 安装包 rockmongo-v1.0.5.r53.zip ...
- MongoDB系列:三、springboot整合mongoDB的简单demo
在上篇 MongoDB常用操作练习 中,我们在命令提示符窗口使用简单的mongdb的方法操作数据库,实现增删改查及其他的功能.在本篇中,我们将mongodb与spring boot进行整合,也就是在j ...
- MongoDB 教程(三):MongoDB 的下载、安装和配置
一.下载 下载地址:https://www.mongodb.com/download-center#community(这里是Windows 版,其他版本也可以在该网页进行下载) 版本选择: Mong ...
随机推荐
- python mysql中in参数化说明
第一种:拼接字符串,可以解决问题,但是为了避免sql注入,不建议这样写 还是看看第二种:使用.format()函数,很多时候我都是使用这个函数来对sql参数化的 举个例子: select * from ...
- adb连接多个设备时,选择某个设备
在emulator-5554模拟器上安装ebook.apk: adb -s emulator-5554 install ebook.apk 在真机上安装ebook.apk: adb -s HT9BYL ...
- 想理解JVM看了这篇文章,就知道了!(一)
前言 本章节属于Java进阶系列,前面关于设计模式讲解完了,有兴趣的童鞋可以翻看之前的博文,后面会讲解JVM的优化,整个系列会完整的讲解整个java体系与生态相关的中间件知识.本次将对jvm有更深 ...
- 扫描PDF417崩溃的原因找到:手机摄像头分辨率低
换孩子姥姥华为手机解决了. 能扫pdf417码了
- 还不懂mysql的undo log和mvcc?算我输!
最近一直没啥时间写点东西,坚持分享真的好难,也不知道该分享点啥,正好有人要问我这些东西,所以腾出点时间,写一下这个主题.同样本篇可以给读者承诺,听不懂或者没收获算我输,哈哈! 众所周知,mysql中读 ...
- animate动画基础
定义: animate() 方法执行 CSS 属性集的自定义动画. 1.该方法通过CSS样式将元素从一个状态改变为另一个状态.CSS属性值是逐渐改变的,这样就可以创建动画效果. 2.只有数字值可创建动 ...
- 谈谈Hadoop MapReduce和Spark MR实现
谈谈MapReduce的概念.Hadoop MapReduce和Spark基于MR的实现 什么是MapReduce? MapReduce是一种分布式海量数据处理的编程模型,用于大规模数据集的并行运算. ...
- Day01_搭建环境&CMS服务端开发
学成在线 第1天 讲义-项目概述 CMS接口开发 1 项目的功能构架 1.1 项目背景 受互联网+概念的催化,当今中国在线教育市场的发展可谓是百花齐放.如火如荼. 按照市场领域细分为:学前教育.K12 ...
- JavaWeb基础Day17 (JSP EL表达式 jstl标签库 beanutil工具类)
JSP jsp的实质就是指在html界面中嵌入Java代码 jsp脚本 <% Java代码 %> 相当于写在service方法中. <%=java 变量或者表达式 %> ...
- 10分钟了解js的宏任务和微任务
熟悉宏任务和微任务以及js(nodejs)事件循环机制,在写业务代码还是自己写库,或者看源码都是那么重要 看了部分文档,自己总结和实践了一下 js中同步任务.宏任务和微任务介绍 同步任务: 普通任务 ...