MongoDB学习笔记~ObjectId主键的设计

回到目录

说一些关于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客户端的学习,让我们的眼界放宽了许多,可能在思考问题时不局限于眼前,对于同一个问题可以会有更多的解决方法了,呵呵!

回到目录

你可能感兴趣的:(mongodb)