您好,登錄后才能下訂單哦!
這篇文章給大家介紹使用mongodb的三個(gè)大坑是否踩過,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。
前段時(shí)間有位朋友在微信群?jiǎn)枺谙?mongodb 中插入的時(shí)間為啥取出來(lái)的時(shí)候少了 8 個(gè)小時(shí),8 在時(shí)間處理上是一個(gè)非常敏感的數(shù)字,又吉利又是一個(gè)普適的話題,后來(lái)我想想初次使用 mongodb 的朋友一定還會(huì)遇到各種新坑,比如說: 插入的數(shù)據(jù)取不出來(lái),看不爽的 ObjectID,時(shí)區(qū)不對(duì)等等,這篇就和大家一起聊一聊。
這個(gè)問題是使用強(qiáng)類型操作 mongodb 你一定會(huì)遇到的問題,案例代碼如下:
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; } }
我去,這么簡(jiǎn)單的一個(gè)操作還報(bào)錯(cuò),要初學(xué)到放棄嗎? 挺急的,在線等!
作為一個(gè)碼農(nóng)還得有鉆研代碼的能力,從錯(cuò)誤信息中看說有一個(gè) _id
不匹配 student 中的任何一個(gè)字段,然后把全部堆棧找出來(lái)。
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
接下來(lái)就用 dnspy 去定位一下 MongoQueryProviderImpl.Execute
到底干的啥,截圖如下:
我去,這代碼硬核哈,用了 LambdaExpression
表達(dá)式樹,我們知道表達(dá)式樹用于將一個(gè)領(lǐng)域的查詢結(jié)構(gòu)轉(zhuǎn)換為另一個(gè)領(lǐng)域的查詢結(jié)構(gòu),但要尋找如何構(gòu)建這個(gè)方法體就比較耗時(shí)間了,接下來(lái)還是用 dnspy 去調(diào)試看看有沒有更深層次的堆棧。
這個(gè)堆棧信息就非常清楚了,原來(lái)是在 MongoDB.Bson.Serialization.BsonClassMapSerializer.DeserializeClass
方法中出了問題,接下來(lái)找到問題代碼,簡(jiǎn)化如下:
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
,添加字段容易,接下來(lái)看看怎么讓 IgnoreExtraElements = true,找了一圈源碼,發(fā)現(xiàn)這里是關(guān)鍵:
也就是: foreach (IBsonClassMapAttribute bsonClassMapAttribute in classMap.ClassType.GetTypeInfo().GetCustomAttributes(false).OfType<IBsonClassMapAttribute>())
這句話,這里的 classMap 就是 student,只有讓 foreach 得以執(zhí)行才能有望 classMap.IgnoreExtraElements 賦值為 true ,接下來(lái)找找看在類上有沒有類似 IgnoreExtraElements
的 Attribute,嘿嘿,還真有一個(gè)類似的: BsonIgnoreExtraElements
,如下代碼:
[BsonIgnoreExtraElements] public class Student { public string StudentName { get; set; } public DateTime Created { get; set; } }
接下來(lái)執(zhí)行一下代碼,可以看到問題搞定:
如果你想驗(yàn)證的話,可以繼續(xù)用 dnspy 去驗(yàn)證一下源碼哈,如下代碼所示:
接下來(lái)還有一種辦法就是增加 _id 字段,如果你不知道用什么類型接,那就用object就好啦,后續(xù)再改成真正的類型。
如果你細(xì)心的話,你會(huì)發(fā)現(xiàn)剛才案例中的 Created 時(shí)間是 2020/8/16 4:24:57
, 大家請(qǐng)放心,我不會(huì)傻到凌晨4點(diǎn)還在寫代碼,好了哈,看看到底問題在哪吧, 可以先看看 mongodb 中的記錄數(shù)據(jù),如下:
{ "_id" : ObjectId("5f38b83e0351908eedac60c9"), "StudentName" : "hxc", "Created" : ISODate("2020-08-16T04:38:22.587Z") }
從 ISODate 可以看出,這是格林威治時(shí)間,按照0時(shí)區(qū)存儲(chǔ),所以這個(gè)問題轉(zhuǎn)成了如何在獲取數(shù)據(jù)的時(shí)候,自動(dòng)將 ISO 時(shí)間轉(zhuǎn)成 Local 時(shí)間就可以了,如果你看過底層源碼,你會(huì)發(fā)現(xiàn)在 mongodb 中每個(gè)實(shí)體的每個(gè)類型都有一個(gè)專門的 XXXSerializer
,如下圖:
接下來(lái)就好好研讀一下里面的 Deserialize
方法即可,代碼精簡(jiǎn)后如下:
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; }
可以看出,如果當(dāng)前的 this._kind= DateTimeKind.Local
的話,就將 UTC 時(shí)間轉(zhuǎn)成 Local 時(shí)間,如果你有上一個(gè)坑的經(jīng)驗(yàn),你大概就知道應(yīng)該也是用特性注入的,
[BsonDateTimeOptions(Kind = DateTimeKind.Local)] public DateTime Created { get; set; }
不信的話,我調(diào)試給你看看哈。
接下來(lái)再看看 this._kind
是怎么被賦的。
在第一個(gè)坑中,不知道大家看沒看到類似這樣的語(yǔ)句: ObjectId("5f38b83e0351908eedac60c9")
,乍一看像是一個(gè) GUID,當(dāng)然肯定不是,這是mongodb自己組建了一個(gè) number 組合的十六進(jìn)制表示,姑且不說性能如何,反正看著不是很舒服,畢竟大家都習(xí)慣使用 int/long 類型展示的主鍵ID。
那接下來(lái)的問題是:如何改成我自定義的 number ID 呢? 當(dāng)然可以,只要實(shí)現(xiàn) IIdGenerator
接口即可,那主鍵ID的生成,我準(zhǔn)備用 雪花算法
,完整代碼如下:
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:
關(guān)于使用mongodb的三個(gè)大坑是否踩過就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。