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