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

说一些关于ObjectId的事

MongoDB确实是最像关系型数据库的NoSQL,这在它主键设计上可以体现的出来,它并没有采用自动增长主键,因为在分布式服务器之间做数据同步很麻烦,而是采用了一种ObjectId的方式,它生成方便,占用空间比long多了4个字节,(12个字节)在数据表现层面也说的过去,它是一种以时间,机器,进程和自增几个因素组合的方式来体现的,可以近似看成是按时间的先后进行排序的,对于ObjectId的生成我们可以通过MongoDB服务端去获得,或者在客户端也有对它的集成,使用方便,一般情况下,在客户端实体类中只要定义一个ObjectId类型的属性,这个属性就默认被赋上值了,应该说,还是比较方便的,由于它存储是一种字符串,所以,一般客户端,像NoRM都为我们实现了对string类型的隐藏转换,应该说,还是比较友好的!

ObjectId的组成

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

时间: 2024-10-28 04:36:54

MongoDB学习笔记~ObjectId主键的设计的相关文章

MongoDB学习笔记系列

回到占占推荐博客索引 该来的总会来的,Ef,Redis,MVC甚至Sqlserver都有了自己的系列,MongoDB没有理由不去整理一下,这个系列都是平时在项目开发时总结出来的,希望可以为各位一些帮助和启发,文章中有对新技术的研究(Mongo驱动),对老技术的回顾(代码重构),还有对架构设计的阐述等(面向接口编程,对扩展开放,对修改关闭,所以出现了IMongoRepository接口). MongoDB学习笔记系列~目录 MongoDB学习笔记~环境搭建 (2015-03-30 10:34) M

mongodb学习笔记系列一

一.简介和安装 ./bin/mongod --dbpath /path/to/database --logpath /path/to/log --fork --port 27017 mongodb非常的占磁盘空间, 刚启动后要占3-4G左右,--smallfiles 二.基本命令 1.登录mongodb client /use/local/mongo 2.查看当前数据库 show databases; show dbs; 两个可能 3.admin是和管理有关的库,local 是放schema有关

MongoDB学习笔记一 ID自增长

以下是官网原文地址: http://docs.mongodb.org/manual/tutorial/create-an-auto-incrementing-field/ 概要 MongoDB 的_id字段作为一个主键存在于所有文档的最顶层,_id必须是唯一的,而且总是具有唯一约束的索引.除了唯一约束,你可以在集合中的_id字段上使用任何值, 以下这个指南描述了在_id上创建一个自增序列的两种方式: Use Counter Collection Optimistic Loop 注意事项 一般情况

[Spring Data MongoDB]学习笔记--MapReduce

mongodb的MapReduce主要包含两个方法:map和reduce. 举个例子,假设现在有下面3条记录 { "_id" : ObjectId("4e5ff893c0277826074ec533"), "x" : [ "a", "b" ] } { "_id" : ObjectId("4e5ff893c0277826074ec534"), "x"

MongoDB学习笔记(查询)

1.  基本查询:    构造查询数据.    > db.test.findOne()    {         "_id" : ObjectId("4fd58ecbb9ac507e96276f1a"),         "name" : "stephen",         "age" : 35,         "genda" : "male",      

【转】MongoDB学习笔记(查询)

原文地址 MongoDB学习笔记(查询) 基本查询: 构造查询数据. > db.test.findOne() { "_id" : ObjectId("4fd58ecbb9ac507e96276f1a"), "name" : "stephen", "age" : 35, "genda" : "male", "email" : "[em

Mongodb学习笔记

总结下这几天Mongodb学习笔记 /** * 获取MongoClient * @author xuyw * @email [email protected] * @param host * @param port * @return */ public static MongoClient getMongoClient(String host, int... port) { MongoClient mongoClient = null; int portlen = 0; try { if (p

数据库模型设计——关系的实现,主键的设计

一.关系的实现 在实体关系模型中,我们知道有三种关系:一对一.一对多.多对多.这只是概念上的关系,但是在真实的关系数据库中,我们只有外键,并没有这三种关系,那么我们就来说一说在关系数据库管理系统中,怎么实现这三种关系. 一对多 这里先讲解一对多,因为这个关系最简单.一对多和多对一是一回事,所以就不再提多对一这个词.一对多的概念是一个对象A会对应多个对象B,而从B的角度看,一个对象B只会对于一个对象A.比如说班级和学生就是一对多关系.一个班级对应多个学生,一个学生只会对于一个班级. 一对多的关系之

MongoDB 学习笔记(二) 之查询

最简单的查询 个人认为mongoDB是面向对象的吧. 例如最简单的查询  整个数据集只有三条数据 第一查询姓名为张三的  数据 查询的条件比较好写 随意   db.collection.find(查询条件)   例如 15 得到的结果是这样 如果你不想返回某个字段呢 ,你可以自己定义返回的字段值 语法这样 db.collection.find({查询条件},{返回字段}) 16 我们看到每次查询 "_id" 这个字段 都返回  我们可以将它设置为0 这样的话就不会返回 如 查询条件里的