回到目錄
說一些關于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用戶端的學習,讓我們的眼界放寬了許多,可能在思考問題時不局限于眼前,對于同一個問題可以會有更多的解決方法了,呵呵!
作者:倉儲大叔,張占嶺,
榮譽:微軟MVP
QQ:853066980
支付寶掃一掃,為大叔打賞!
