文章出處

回到目錄

說一些關于ObjectId的事

MongoDB確實是最像關系型數據庫的NoSQL,這在它主鍵設計上可以體現的出來,它并沒有采用自動增長主鍵,因為在分布式服務器之間做數據同步很麻煩,而是采用了一種ObjectId的方式,它生成方便,占用空間比long多了4個字節,(12個字節)在數據表現層面也說的過去,它是一種以時間,機器,進程和自增幾個因素組合的方式來體現的,可以近似看成是按時間的先后進行排序的,對于ObjectId的生成我們可以通過MongoDB服務端去獲得,或者在客戶端也有對它的集成,使用方便,一般情況下,在客戶端實體類中只要定義一個ObjectId類型的屬性,這個屬性就默認被賦上值了,應該說,還是比較方便的,由于它存儲是一種字符串,所以,一般客戶端,像NoRM都為我們實現了對string類型的隱藏轉換,應該說,還是比較友好的!

ObjectId的組成

為何選擇十六進制表示法

為什么在ObjectId里,將byte[]數組轉為字符串時,使用十六進制而沒有使用默認的十進制呢,居 我的研究,它應該是考慮字符串的長度一致性吧,因為byte取值為(0~255),如果使用默認的十進制那么它的值長度非常不規范,有1位,2位和3位, 而如果使用十六進制表示,它的長度都為2位,2位就可以表示0到255中的任何數字了,0對應0x00,255對應0xFF,呵呵,將它們轉為字符串后,即可 以保證數據的完整性,又可以讓它看上去長度是一致的,何樂不為呢,哈哈!

漂亮的設計,原自于扎實的基礎!

 在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;
            //進程碼
            Array.Copy(procID, 0, oid, copyidx, 2);
            copyidx += 2;
            //自增值
            Array.Copy(BitConverter.GetBytes(GenerateInc()), 0, oid, copyidx, 3);
            return oid;
        }

完整的ObjectId類型源代碼

它重寫的ToString()方法,為的是實現byte[]到string串之間的類型轉換,并且為string和ObjectId對象實現 implicit的隱式類型轉換,方便開發人員在實際中最好的使用它們,需要注意的是在byte[]中存儲的數據都是以十六進制的形式體現的

    /// <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串生成的規則及方式

    /// <summary>
    /// Shameless-ly ripped off, then slightly altered from samus' implementation on GitHub
    /// http://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;
            //進程碼
            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;
        }
    }

事實上,通過對NoRm這個MongoDB客戶端的學習,讓我們的眼界放寬了許多,可能在思考問題時不局限于眼前,對于同一個問題可以會有更多的解決方法了,呵呵!

回到目錄


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()