你的位置:首页 > 数据库

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