MongoDB學習筆記~ObjectId主鍵的設計
說一些關于ObjectId的事
MongoDB確實是最像關系型數據庫的(de)(de)NoSQL,這在(zai)(zai)它主鍵設計上(shang)可(ke)以體現(xian)的(de)(de)出來,它并沒有(you)采(cai)用自動(dong)增長主鍵,因為在(zai)(zai)分(fen)布式(shi)服(fu)務器之間(jian)做數據同(tong)步很麻(ma)煩(fan),而(er)是采(cai)用了一(yi)種ObjectId的(de)(de)方(fang)(fang)式(shi),它生(sheng)(sheng)成(cheng)(cheng)(cheng)方(fang)(fang)便,占用空間(jian)比(bi)long多了4個字(zi)(zi)節,(12個字(zi)(zi)節)在(zai)(zai)數據表現(xian)層面也(ye)說(shuo)的(de)(de)過去,它是一(yi)種以時(shi)(shi)間(jian),機器,進程和自增幾個因素組合的(de)(de)方(fang)(fang)式(shi)來體現(xian)的(de)(de),可(ke)以近似看成(cheng)(cheng)(cheng)是按時(shi)(shi)間(jian)的(de)(de)先后(hou)進行排序的(de)(de),對(dui)于ObjectId的(de)(de)生(sheng)(sheng)成(cheng)(cheng)(cheng)我們可(ke)以通過MongoDB服(fu)務端去獲得,或者在(zai)(zai)客(ke)戶端也(ye)有(you)對(dui)它的(de)(de)集成(cheng)(cheng)(cheng),使用方(fang)(fang)便,一(yi)般情況下,在(zai)(zai)客(ke)戶端實體類(lei)中只要定義一(yi)個ObjectId類(lei)型的(de)(de)屬(shu)性(xing),這個屬(shu)性(xing)就默認被賦上(shang)值(zhi)了,應該說(shuo),還是比(bi)較(jiao)方(fang)(fang)便的(de)(de),由于它存儲是一(yi)種字(zi)(zi)符(fu)串,所以,一(yi)般客(ke)戶端,像NoRM都為我們實現(xian)了對(dui)string類(lei)型的(de)(de)隱藏轉(zhuan)換(huan),應該說(shuo),還是比(bi)較(jiao)友(you)好的(de)(de)!
ObjectId的組成
為何選擇十六進制表示法
為什么(me)在ObjectId里(li),將(jiang)byte[]數組(zu)轉(zhuan)(zhuan)為字(zi)符(fu)串(chuan)時,使(shi)用(yong)(yong)十六(liu)(liu)進(jin)制(zhi)而沒(mei)有使(shi)用(yong)(yong)默認(ren)(ren)的(de)(de)十進(jin)制(zhi)呢(ni),居 我的(de)(de)研究(jiu),它(ta)(ta)(ta)(ta)應該是(shi)考(kao)慮字(zi)符(fu)串(chuan)的(de)(de)長度(du)一致(zhi)性吧(ba),因為byte取值為(0~255),如果(guo)使(shi)用(yong)(yong)默認(ren)(ren)的(de)(de)十進(jin)制(zhi)那么(me)它(ta)(ta)(ta)(ta)的(de)(de)值長度(du)非常不規(gui)范,有1位(wei),2位(wei)和3位(wei), 而如果(guo)使(shi)用(yong)(yong)十六(liu)(liu)進(jin)制(zhi)表示(shi),它(ta)(ta)(ta)(ta)的(de)(de)長度(du)都為2位(wei),2位(wei)就可(ke)以(yi)表示(shi)0到255中(zhong)的(de)(de)任何(he)數字(zi)了(le),0對應0x00,255對應0xFF,呵(he)呵(he),將(jiang)它(ta)(ta)(ta)(ta)們轉(zhuan)(zhuan)為字(zi)符(fu)串(chuan)后,即可(ke) 以(yi)保證數據的(de)(de)完(wan)整性,又(you)可(ke)以(yi)讓它(ta)(ta)(ta)(ta)看上去長度(du)是(shi)一致(zhi)的(de)(de),何(he)樂不為呢(ni),哈(ha)哈(ha)!
漂亮的設計,原自于扎實的基礎!
在C#版的NoRM這樣設計ObjectId
/// <summary> /// Generates a byte array ObjectId. /// </summary> /// <returns> /// </returns> public static byte[] Generate() { var oid = new byte[12]; var copyidx = 0; //時間差 Array.Copy(BitConverter.GetBytes(GenerateTime()), 0, oid, copyidx, 4); copyidx += 4; //機器碼 Array.Copy(machineHash, 0, oid, copyidx, 3); copyidx += 3; //進程碼(ma) Array.Copy(procID, 0, oid, copyidx, 2); copyidx += 2; //自增值(zhi) Array.Copy(BitConverter.GetBytes(GenerateInc()), 0, oid, copyidx, 3); return oid; }
完(wan)整的ObjectId類型源代碼
它重(zhong)寫的(de)(de)ToString()方(fang)法,為的(de)(de)是實(shi)現byte[]到string串之間的(de)(de)類(lei)型轉換(huan),并且為string和ObjectId對象實(shi)現 implicit的(de)(de)隱(yin)式類(lei)型轉換(huan),方(fang)便開發人員在實(shi)際中(zhong)最好的(de)(de)使用(yong)它們,需要注意的(de)(de)是在byte[]中(zhong)存(cun)儲的(de)(de)數據都(dou)是以十(shi)六進制的(de)(de)形式體現的(de)(de)
/// <summary> /// Represents a Mongo document's ObjectId /// </summary> [System.ComponentModel.TypeConverter(typeof(ObjectIdTypeConverter))] public class ObjectId { private string _string; /// <summary> /// Initializes a new instance of the <see cref="ObjectId"/> class. /// </summary> public ObjectId() { } /// <summary> /// Initializes a new instance of the <see cref="ObjectId"/> class. /// </summary> /// <param retval="value"> /// The value. /// </param> public ObjectId(string value) : this(DecodeHex(value)) { } /// <summary> /// Initializes a new instance of the <see cref="ObjectId"/> class. /// </summary> /// <param retval="value"> /// The value. /// </param> internal ObjectId(byte[] value) { this.Value = value; } /// <summary> /// Provides an empty ObjectId (all zeros). /// </summary> public static ObjectId Empty { get { return new ObjectId("000000000000000000000000"); } } /// <summary> /// Gets the value. /// </summary> /// <value>The value.</value> public byte[] Value { get; private set; } /// <summary> /// Generates a new unique oid for use with MongoDB Objects. /// </summary> /// <returns> /// </returns> public static ObjectId NewObjectId() { // TODO: generate random-ish bits. return new ObjectId { Value = ObjectIdGenerator.Generate() }; } /// <summary> /// Tries the parse. /// </summary> /// <param retval="value"> /// The value. /// </param> /// <param retval="id"> /// The id. /// </param> /// <returns> /// The try parse. /// </returns> public static bool TryParse(string value, out ObjectId id) { id = Empty; if (value == null || value.Length != 24) { return false; } try { id = new ObjectId(value); return true; } catch (FormatException) { return false; } } /// <summary> /// Implements the operator ==. /// </summary> /// <param retval="a">A.</param> /// <param retval="b">The b.</param> /// <returns>The result of the operator.</returns> public static bool operator ==(ObjectId a, ObjectId b) { if (ReferenceEquals(a, b)) { return true; } if (((object)a == null) || ((object)b == null)) { return false; } return a.Equals(b); } /// <summary> /// Implements the operator !=. /// </summary> /// <param retval="a">A.</param> /// <param retval="b">The b.</param> /// <returns>The result of the operator.</returns> public static bool operator !=(ObjectId a, ObjectId b) { return !(a == b); } /// <summary> /// Returns a hash code for this instance. /// </summary> /// <returns> /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// </returns> public override int GetHashCode() { return this.Value != null ? this.ToString().GetHashCode() : 0; } /// <summary> /// Returns a <see cref="System.String"/> that represents this instance. /// </summary> /// <returns> /// A <see cref="System.String"/> that represents this instance. /// </returns> public override string ToString() { if (this._string == null && this.Value != null) { this._string = BitConverter.ToString(this.Value).Replace("-", string.Empty).ToLower(); } return this._string; } /// <summary> /// Determines whether the specified <see cref="System.Object"/> is equal to this instance. /// </summary> /// <param retval="o"> /// The <see cref="System.Object"/> to compare with this instance. /// </param> /// <returns> /// <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>. /// </returns> public override bool Equals(object o) { var other = o as ObjectId; return this.Equals(other); } /// <summary> /// Equalses the specified other. /// </summary> /// <param retval="other"> /// The other. /// </param> /// <returns> /// The equals. /// </returns> public bool Equals(ObjectId other) { return other != null && this.ToString() == other.ToString(); } /// <summary> /// Decodes a HexString to bytes. /// </summary> /// <param retval="val"> /// The hex encoding string that should be converted to bytes. /// </param> /// <returns> /// </returns> protected static byte[] DecodeHex(string val) { var chars = val.ToCharArray(); var numberChars = chars.Length; var bytes = new byte[numberChars / 2]; for (var i = 0; i < numberChars; i += 2) { bytes[i / 2] = Convert.ToByte(new string(chars, i, 2), 16); } return bytes; } /// <summary>TODO::Description.</summary> public static implicit operator string(ObjectId oid) { return oid == null ? null : oid.ToString(); } /// <summary>TODO::Description.</summary> public static implicit operator ObjectId(String oidString) { ObjectId retval = ObjectId.Empty; if(!String.IsNullOrEmpty(oidString)) { retval = new ObjectId(oidString); } return retval; } }
ObjectIdGenerator源代碼
它主要實現了ObjectId串(chuan)生成的規(gui)則及方式(shi)
/// <summary> /// Shameless-ly ripped off, then slightly altered from samus' implementation on GitHub /// //github.com/samus/mongodb-csharp/blob/f3bbb3cd6757898a19313b1af50eff627ae93c16/MongoDBDriver/ObjectIdGenerator.cs /// </summary> internal static class ObjectIdGenerator { /// <summary> /// The epoch. /// </summary> private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); /// <summary> /// The inclock. /// </summary> private static readonly object inclock = new object(); /// <summary> /// The inc. /// </summary> private static int inc; /// <summary> /// The machine hash. /// </summary> private static byte[] machineHash; /// <summary> /// The proc id. /// </summary> private static byte[] procID; /// <summary> /// Initializes static members of the <see cref="ObjectIdGenerator"/> class. /// </summary> static ObjectIdGenerator() { GenerateConstants(); } /// <summary> /// Generates a byte array ObjectId. /// </summary> /// <returns> /// </returns> public static byte[] Generate() { var oid = new byte[12]; var copyidx = 0; //時間差 Array.Copy(BitConverter.GetBytes(GenerateTime()), 0, oid, copyidx, 4); copyidx += 4; //機器碼 Array.Copy(machineHash, 0, oid, copyidx, 3); copyidx += 3; //進程(cheng)碼(ma) Array.Copy(procID, 0, oid, copyidx, 2); copyidx += 2; //自增值 Array.Copy(BitConverter.GetBytes(GenerateInc()), 0, oid, copyidx, 3); return oid; } /// <summary> /// Generates time. /// </summary> /// <returns> /// The time. /// </returns> private static int GenerateTime() { var now = DateTime.Now.ToUniversalTime(); var nowtime = new DateTime(epoch.Year, epoch.Month, epoch.Day, now.Hour, now.Minute, now.Second, now.Millisecond); var diff = nowtime - epoch; return Convert.ToInt32(Math.Floor(diff.TotalMilliseconds)); } /// <summary> /// Generate an increment. /// </summary> /// <returns> /// The increment. /// </returns> private static int GenerateInc() { lock (inclock) { return inc++; } } /// <summary> /// Generates constants. /// </summary> private static void GenerateConstants() { machineHash = GenerateHostHash(); procID = BitConverter.GetBytes(GenerateProcId()); } /// <summary> /// Generates a host hash. /// </summary> /// <returns> /// </returns> private static byte[] GenerateHostHash() { using (var md5 = MD5.Create()) { var host = Dns.GetHostName(); return md5.ComputeHash(Encoding.Default.GetBytes(host)); } } /// <summary> /// Generates a proc id. /// </summary> /// <returns> /// Proc id. /// </returns> private static int GenerateProcId() { var proc = Process.GetCurrentProcess(); return proc.Id; } }
事(shi)實上,通過對(dui)NoRm這(zhe)個MongoDB客戶端的學(xue)習,讓(rang)我們的眼(yan)(yan)界放寬(kuan)了(le)許多(duo),可(ke)能在(zai)思考問(wen)題時不局限(xian)于眼(yan)(yan)前,對(dui)于同(tong)一個問(wen)題可(ke)以(yi)會有更(geng)多(duo)的解決方法了(le),呵呵!