當前位置: 妍妍網 > 碼農

.NET生成MongoDB中的主鍵ObjectId

2024-03-02碼農

前言

因為很多場景下我們需要在建立MongoDB數據的時候提前生成好主鍵,像在EF中我們可以透過Guid.NewGuid()來生成主鍵,本來想著要不要實作一套MongoDB中ObjectId的,結果發現網上各種各樣的實作都有,不過好在閱讀C#MongoDB驅動mongo-csharp-driver程式碼的時候發現有ObjectId.GenerateNewId()的方法提供,我們可以直接呼叫即可,不需要我們在花費多余的時間設計重寫了。

MongoDB ObjectId型別概述

每次插入一條數據系統都會自動插入一個_id鍵,鍵值不可以重復,它可以是任何型別的,也可以手動的插入,預設情況下它的數據型別是ObjectId,由於MongoDB在設計之初就是用作分布式資料庫,所以使用ObjectId可以避免不同資料庫中_id的重復(如果使用自增的方式在分布式系統中就會出現重復的_id的值)。ObjectId使用12字節的儲存空間,每個字節可以儲存兩個十六進制數位,所以一共可以儲存24個十六進制數位組成的字串,在這24個字串中,前8位元表示時間戳,接下來6位是一個機器碼,接下來4位元表示行程id,最後6位表示計數器。

MongoDB 采用 ObjectId 來表示主鍵的型別,資料庫中每個文件都擁有一個_id 欄位表示主鍵,_id 的生成規則如下:

其中包括:4-byte Unix 時間戳,3-byte 機器 ID,2-byte 行程 ID,3-byte 計數器(初始化隨機)。

601e2b6b a3203c c89f 2d31aa
↑ ↑ ↑ ↑
 時間戳 機器碼 行程ID 隨機數

MongoDB.Driver驅動安裝

1、直接命令自動安裝

Install-Package MongoDB.Driver

2、搜尋Nuget手動安裝

呼叫生成主鍵ObjectId

var primarykeyId = ObjectId.GenerateNewId();
//輸出:641c54b2e674000035001dc2

mongo-csharp-driver ObjectId源碼詳解

關於ObjectId的生成原理大家閱讀如下源碼即可。

  • GitHub源碼地址:https://github.com/mongodb/mongo-csharp-driver/blob/ec74978f7e827515f29cc96fba0c727828e8df7c/src/MongoDB.Bson/ObjectModel/ObjectId.cs

  • /* Copyright 2010-present MongoDB Inc.
    *
    * Licensed under the Apache License, Version 2.0 (the "License");
    * you may not use this file except in compliance with the License.
    * You may obtain a copy of the License at
    *
    * http://www.apache.org/licenses/LICENSE-2.0
    *
    * Unless required by applicable law or agreed to in writing, software
    * distributed under the License is distributed on an "AS IS" BASIS,
    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    * See the License for the specific language governing permissions and
    * limitations under the License.
    */
    using System;
    using System.Diagnostics;
    using System.Runtime.CompilerServices;
    using System.Security;
    using System.Threading;
    namespace MongoDB.Bson
    {
    /// <summary>
    /// Represents an ObjectId (see also BsonObjectId).
    /// </summary>
    #if NET45
    [Serializable]
    #endif
    public struct ObjectId : IComparable<ObjectId>, IEquatable<ObjectId>, IConvertible
    {
    // private static fields
    private static readonly ObjectId __emptyInstance = default(ObjectId);
    private static readonly int __staticMachine = (GetMachineHash() + GetAppDomainId()) & 0x00ffffff;
    private static readonly short __staticPid = GetPid();
    private static int __staticIncrement = (new Random()).Next();
    // private fields
    private readonly int _a;
    private readonly int _b;
    private readonly int _c;
    // constructors
    /// <summary>
    /// Initializes a new instance of the ObjectId class.
    /// </summary>
    /// <param name="bytes">The bytes.</param>
    public ObjectId(byte[] bytes)
    {
    if (bytes == null)
    {
    throw new ArgumentNullException("bytes");
    }
    if (bytes.Length != 12)
    {
    throw new ArgumentException("Byte array must be 12 bytes long""bytes");
    }
    FromByteArray(bytes, 0, out _a, out _b, out _c);
    }
    /// <summary>
    /// Initializes a new instance of the ObjectId class.
    /// </summary>
    /// <param name="bytes">The bytes.</param>
    /// <param name="index">The index into the byte array where the ObjectId starts.</param>
    internal ObjectId(byte[] bytes, int index)
    {
    FromByteArray(bytes, index, out _a, out _b, out _c);
    }
    /// <summary>
    /// Initializes a new instance of the ObjectId class.
    /// </summary>
    /// <param name="timestamp">The timestamp (expressed as a DateTime).</param>
    /// <param name="machine">The machine hash.</param>
    /// <param name="pid">The PID.</param>
    /// <param name="increment">The increment.</param>
    public ObjectId(DateTime timestamp, int machine, short pid, int increment)
    : this(GetTimestampFromDateTime(timestamp), machine, pid, increment)
    {
    }
    /// <summary>
    /// Initializes a new instance of the ObjectId class.
    /// </summary>
    /// <param name="timestamp">The timestamp.</param>
    /// <param name="machine">The machine hash.</param>
    /// <param name="pid">The PID.</param>
    /// <param name="increment">The increment.</param>
    public ObjectId(int timestamp, int machine, short pid, int increment)
    {
    if ((machine & 0xff000000) != 0)
    {
    throw new ArgumentOutOfRangeException("machine""The machine value must be between 0 and 16777215 (it must fit in 3 bytes).");
    }
    if ((increment & 0xff000000) != 0)
    {
    throw new ArgumentOutOfRangeException("increment""The increment value must be between 0 and 16777215 (it must fit in 3 bytes).");
    }
    _a = timestamp;
    _b = (machine << 8) | (((int)pid >> 8) & 0xff);
    _c = ((int)pid << 24) | increment;
    }
    /// <summary>
    /// Initializes a new instance of the ObjectId class.
    /// </summary>
    /// <param name="value">The value.</param>
    public ObjectId(string value)
    {
    if (value == null)
    {
    throw new ArgumentNullException("value");
    }
    var bytes = BsonUtils.ParseHexString(value);
    FromByteArray(bytes, 0, out _a, out _b, out _c);
    }
    // public static properties
    /// <summary>
    /// Gets an instance of ObjectId where the value is empty.
    /// </summary>
    public static ObjectId Empty
    {
    get { return __emptyInstance; }
    }
    // public properties
    /// <summary>
    /// Gets the timestamp.
    /// </summary>
    public int Timestamp
    {
    get { return _a; }
    }
    /// <summary>
    /// Gets the machine.
    /// </summary>
    public int Machine
    {
    get { return (_b >> 8) & 0xffffff; }
    }
    /// <summary>
    /// Gets the PID.
    /// </summary>
    public short Pid
    {
    get { return (short)(((_b << 8) & 0xff00) | ((_c >> 24) & 0x00ff)); }
    }
    /// <summary>
    /// Gets the increment.
    /// </summary>
    public int Increment
    {
    get { return _c & 0xffffff; }
    }
    /// <summary>
    /// Gets the creation time (derived from the timestamp).
    /// </summary>
    public DateTime CreationTime
    {
    get { return BsonConstants.UnixEpoch.AddSeconds(Timestamp); }
    }
    // public operators
    /// <summary>
    /// Compares two ObjectIds.
    /// </summary>
    /// <param name="lhs">The first ObjectId.</param>
    /// <param name="rhs">The other ObjectId</param>
    /// <returns>True if the first ObjectId is less than the second ObjectId.</returns>
    public static bool operator <(ObjectId lhs, ObjectId rhs)
    {
    return lhs.CompareTo(rhs) < 0;
    }
    /// <summary>
    /// Compares two ObjectIds.
    /// </summary>
    /// <param name="lhs">The first ObjectId.</param>
    /// <param name="rhs">The other ObjectId</param>
    /// <returns>True if the first ObjectId is less than or equal to the second ObjectId.</returns>
    public static bool operator <=(ObjectId lhs, ObjectId rhs)
    {
    return lhs.CompareTo(rhs) <= 0;
    }
    /// <summary>
    /// Compares two ObjectIds.
    /// </summary>
    /// <param name="lhs">The first ObjectId.</param>
    /// <param name="rhs">The other ObjectId.</param>
    /// <returns>True if the two ObjectIds are equal.</returns>
    public static bool operator ==(ObjectId lhs, ObjectId rhs)
    {
    return lhs.Equals(rhs);
    }
    /// <summary>
    /// Compares two ObjectIds.
    /// </summary>
    /// <param name="lhs">The first ObjectId.</param>
    /// <param name="rhs">The other ObjectId.</param>
    /// <returns>True if the two ObjectIds are not equal.</returns>
    public static bool operator !=(ObjectId lhs, ObjectId rhs)
    {
    return !(lhs == rhs);
    }
    /// <summary>
    /// Compares two ObjectIds.
    /// </summary>
    /// <param name="lhs">The first ObjectId.</param>
    /// <param name="rhs">The other ObjectId</param>
    /// <returns>True if the first ObjectId is greather than or equal to the second ObjectId.</returns>
    public static bool operator >=(ObjectId lhs, ObjectId rhs)
    {
    return lhs.CompareTo(rhs) >= 0;
    }
    /// <summary>
    /// Compares two ObjectIds.
    /// </summary>
    /// <param name="lhs">The first ObjectId.</param>
    /// <param name="rhs">The other ObjectId</param>
    /// <returns>True if the first ObjectId is greather than the second ObjectId.</returns>
    public static bool operator >(ObjectId lhs, ObjectId rhs)
    {
    return lhs.CompareTo(rhs) > 0;
    }
    // public static methods
    /// <summary>
    /// Generates a new ObjectId with a unique value.
    /// </summary>
    /// <returns>An ObjectId.</returns>
    public static ObjectId GenerateNewId()
    {
    return GenerateNewId(GetTimestampFromDateTime(DateTime.UtcNow));
    }
    /// <summary>
    /// Generates a new ObjectId with a unique value (with the timestamp component based on a given DateTime).
    /// </summary>
    /// <param name="timestamp">The timestamp component (expressed as a DateTime).</param>
    /// <returns>An ObjectId.</returns>
    public static ObjectId GenerateNewId(DateTime timestamp)
    {
    return GenerateNewId(GetTimestampFromDateTime(timestamp));
    }
    /// <summary>
    /// Generates a new ObjectId with a unique value (with the given timestamp).
    /// </summary>
    /// <param name="timestamp">The timestamp component.</param>
    /// <returns>An ObjectId.</returns>
    public static ObjectId GenerateNewId(int timestamp)
    {
    int increment = Interlocked.Increment(ref __staticIncrement) & 0x00ffffff; // only use low order 3 bytes
    return new ObjectId(timestamp, __staticMachine, __staticPid, increment);
    }
    /// <summary>
    /// Packs the components of an ObjectId into a byte array.
    /// </summary>
    /// <param name="timestamp">The timestamp.</param>
    /// <param name="machine">The machine hash.</param>
    /// <param name="pid">The PID.</param>
    /// <param name="increment">The increment.</param>
    /// <returns>A byte array.</returns>
    public static byte[] Pack(int timestamp, int machine, short pid, int increment)
    {
    if ((machine & 0xff000000) != 0)
    {
    throw new ArgumentOutOfRangeException("machine""The machine value must be between 0 and 16777215 (it must fit in 3 bytes).");
    }
    if ((increment & 0xff000000) != 0)
    {
    throw new ArgumentOutOfRangeException("increment""The increment value must be between 0 and 16777215 (it must fit in 3 bytes).");
    }
    byte[] bytes = new byte[12];
    bytes[0] = (byte)(timestamp >> 24);
    bytes[1] = (byte)(timestamp >> 16);
    bytes[2] = (byte)(timestamp >> 8);
    bytes[3] = (byte)(timestamp);
    bytes[4] = (byte)(machine >> 16);
    bytes[5] = (byte)(machine >> 8);
    bytes[6] = (byte)(machine);
    bytes[7] = (byte)(pid >> 8);
    bytes[8] = (byte)(pid);
    bytes[9] = (byte)(increment >> 16);
    bytes[10] = (byte)(increment >> 8);
    bytes[11] = (byte)(increment);
    return bytes;
    }
    /// <summary>
    /// Parses a string and creates a new ObjectId.
    /// </summary>
    /// <param name="s">The string value.</param>
    /// <returns>A ObjectId.</returns>
    public static ObjectId Parse(string s)
    {
    if (s == null)
    {
    throw new ArgumentNullException("s");
    }
    ObjectId objectId;
    if (TryParse(s, out objectId))
    {
    return objectId;
    }
    else
    {
    var message = string.Format("'{0}' is not a valid 24 digit hex string.", s);
    throw new FormatException(message);
    }
    }
    /// <summary>
    /// Tries to parse a string and create a new ObjectId.
    /// </summary>
    /// <param name="s">The string value.</param>
    /// <param name="objectId">The new ObjectId.</param>
    /// <returns>True if the string was parsed successfully.</returns>
    public static bool TryParse(string s, out ObjectId objectId)
    {
    // don't throw ArgumentNullException if s is null
    if (s != null && s.Length == 24)
    {
    byte[] bytes;
    if (BsonUtils.TryParseHexString(s, out bytes))
    {
    objectId = new ObjectId(bytes);
    return true;
    }
    }
    objectId = default(ObjectId);
    return false;
    }
    /// <summary>
    /// Unpacks a byte array into the components of an ObjectId.
    /// </summary>
    /// <param name="bytes">A byte array.</param>
    /// <param name="timestamp">The timestamp.</param>
    /// <param name="machine">The machine hash.</param>
    /// <param name="pid">The PID.</param>
    /// <param name="increment">The increment.</param>
    public static void Unpack(byte[] bytes, out int timestamp, out int machine, out short pid, out int increment)
    {
    if (bytes == null)
    {
    throw new ArgumentNullException("bytes");
    }
    if (bytes.Length != 12)
    {
    throw new ArgumentOutOfRangeException("bytes", "Byte array must be 12 bytes long.");
    }
    timestamp = (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3];
    machine = (bytes[4] << 16) + (bytes[5] << 8) + bytes[6];
    pid = (short)((bytes[7] << 8) + bytes[8]);
    increment = (bytes[9] << 16) + (bytes[10] << 8) + bytes[11];
    }
    // private static methods
    private static int GetAppDomainId()
    {
    #if NETSTANDARD1_5 || NETSTANDARD1_6
    return 1;
    #else
    return AppDomain.CurrentDomain.Id;
    #endif
    }
    /// <summary>
    /// Gets the current process id. This method exists because of how CAS operates on the call stack, checking
    /// for permissions before executing the method. Hence, if we inlined this call, the calling method would not execute
    /// before throwing an exception requiring the try/catch at an even higher level that we don'




    t necessarily control.
    /// </summary>
    [MethodImpl(MethodImplOptions.NoInlining)]
    private static int GetCurrentProcessId()
    {
    return Process.GetCurrentProcess().Id;
    }
    private static int GetMachineHash()
    {
    // use instead of Dns.HostName so it will work offline
    var machineName = GetMachineName();
    return 0x00ffffff & machineName.GetHashCode(); // use first 3 bytes of hash
    }
    private static string GetMachineName()
    {
    return Environment.MachineName;
    }
    private static short GetPid()
    {
    try
    {
    return (short)GetCurrentProcessId(); // use low order two bytes only
    }
    catch (SecurityException)
    {
    return 0;
    }
    }
    private static int GetTimestampFromDateTime(DateTime timestamp)
    {
    var secondsSinceEpoch = (long)Math.Floor((BsonUtils.ToUniversalTime(timestamp) - BsonConstants.UnixEpoch).TotalSeconds);
    if (secondsSinceEpoch < int.MinValue || secondsSinceEpoch > int.MaxValue)
    {
    throw new ArgumentOutOfRangeException("timestamp");
    }
    return (int)secondsSinceEpoch;
    }
    private static void FromByteArray(byte[] bytes, int offset, out int a, out int b, out int c)
    {
    a = (bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3];
    b = (bytes[offset + 4] << 24) | (bytes[offset + 5] << 16) | (bytes[offset + 6] << 8) | bytes[offset + 7];
    c = (bytes[offset + 8] << 24) | (bytes[offset + 9] << 16) | (bytes[offset + 10] << 8) | bytes[offset + 11];
    }
    // public methods
    /// <summary>
    /// Compares this ObjectId to another ObjectId.
    /// </summary>
    /// <param name="other">The other ObjectId.</param>
    /// <returns>A 32-bit signed integer that indicates whether this ObjectId is less than, equal to, or greather than the other.</returns>
    public int CompareTo(ObjectId other)
    {
    int result = ((uint)_a).CompareTo((uint)other._a);
    if (result != 0) { return result; }
    result = ((uint)_b).CompareTo((uint)other._b);
    if (result != 0) { return result; }
    return ((uint)_c).CompareTo((uint)other._c);
    }
    /// <summary>
    /// Compares this ObjectId to another ObjectId.
    /// </summary>
    /// <param name="rhs">The other ObjectId.</param>
    /// <returns>True if the two ObjectIds are equal.</returns>
    public bool Equals(ObjectId rhs)
    {
    return
    _a == rhs._a &&
    _b == rhs._b &&
    _c == rhs._c;
    }
    /// <summary>
    /// Compares this ObjectId to another object.
    /// </summary>
    /// <param name="obj">The other object.</param>
    /// <returns>True if the other object is an ObjectId and equal to this one.</returns>
    public override bool Equals(object obj)
    {
    if (obj is ObjectId)
    {
    return Equals((ObjectId)obj);
    }
    else
    {
    returnfalse;
    }
    }
    /// <summary>
    /// Gets the hash code.
    /// </summary>
    /// <returns>The hash code.</returns>
    public override int GetHashCode()
    {
    int hash = 17;
    hash = 37 * hash + _a.GetHashCode();
    hash = 37 * hash + _b.GetHashCode();
    hash = 37 * hash + _c.GetHashCode();
    returnhash;
    }
    /// <summary>
    /// Converts the ObjectId to a byte array.
    /// </summary>
    /// <returns>A byte array.</returns>
    public byte[] ToByteArray()
    {
    var bytes = new byte[12];
    ToByteArray(bytes, 0);
    return bytes;
    }
    /// <summary>
    /// Converts the ObjectId to a byte array.
    /// </summary>
    /// <param name="destination">The destination.</param>
    /// <param name="offset">The offset.</param>
    public void ToByteArray(byte[] destination, int offset)
    {
    if (destination == null)
    {
    throw new ArgumentNullException("destination");
    }
    if (offset + 12 > destination.Length)
    {
    throw new ArgumentException("Not enough room in destination buffer.""offset");
    }
    destination[offset + 0] = (byte)(_a >> 24);
    destination[offset + 1] = (byte)(_a >> 16);
    destination[offset + 2] = (byte)(_a >> 8);
    destination[offset + 3] = (byte)(_a);
    destination[offset + 4] = (byte)(_b >> 24);
    destination[offset + 5] = (byte)(_b >> 16);
    destination[offset + 6] = (byte)(_b >> 8);
    destination[offset + 7] = (byte)(_b);
    destination[offset + 8] = (byte)(_c >> 24);
    destination[offset + 9] = (byte)(_c >> 16);
    destination[offset + 10] = (byte)(_c >> 8);
    destination[offset + 11] = (byte)(_c);
    }
    /// <summary>
    /// Returns a string representation of the value.
    /// </summary>
    /// <returns>A string representation of the value.</returns>
    public override string ToString()
    {
    var c = new char[24];
    c[0] = BsonUtils.ToHexChar((_a >> 28) & 0x0f);
    c[1] = BsonUtils.ToHexChar((_a >> 24) & 0x0f);
    c[2] = BsonUtils.ToHexChar((_a >> 20) & 0x0f);
    c[3] = BsonUtils.ToHexChar((_a >> 16) & 0x0f);
    c[4] = BsonUtils.ToHexChar((_a >> 12) & 0x0f);
    c[5] = BsonUtils.ToHexChar((_a >> 8) & 0x0f);
    c[6] = BsonUtils.ToHexChar((_a >> 4) & 0x0f);
    c[7] = BsonUtils.ToHexChar(_a & 0x0f);
    c[8] = BsonUtils.ToHexChar((_b >> 28) & 0x0f);
    c[9] = BsonUtils.ToHexChar((_b >> 24) & 0x0f);
    c[10] = BsonUtils.ToHexChar((_b >> 20) & 0x0f);
    c[11] = BsonUtils.ToHexChar((_b >> 16) & 0x0f);
    c[12] = BsonUtils.ToHexChar((_b >> 12) & 0x0f);
    c[13] = BsonUtils.ToHexChar((_b >> 8) & 0x0f);
    c[14] = BsonUtils.ToHexChar((_b >> 4) & 0x0f);
    c[15] = BsonUtils.ToHexChar(_b & 0x0f);
    c[16] = BsonUtils.ToHexChar((_c >> 28) & 0x0f);
    c[17] = BsonUtils.ToHexChar((_c >> 24) & 0x0f);
    c[18] = BsonUtils.ToHexChar((_c >> 20) & 0x0f);
    c[19] = BsonUtils.ToHexChar((_c >> 16) & 0x0f);
    c[20] = BsonUtils.ToHexChar((_c >> 12) & 0x0f);
    c[21] = BsonUtils.ToHexChar((_c >> 8) & 0x0f);
    c[22] = BsonUtils.ToHexChar((_c >> 4) & 0x0f);
    c[23] = BsonUtils.ToHexChar(_c & 0x0f);
    return new string(c);
    }
    // explicit IConvertible implementation
    TypeCode IConvertible.GetTypeCode()
    {
    return TypeCode.Object;
    }
    bool IConvertible.ToBoolean(IFormatProvider provider)
    {
    throw new InvalidCastException();
    }
    byte IConvertible.ToByte(IFormatProvider provider)
    {
    throw new InvalidCastException();
    }
    char IConvertible.ToChar(IFormatProvider provider)
    {
    throw new InvalidCastException();
    }
    DateTime IConvertible.ToDateTime(IFormatProvider provider)
    {
    throw new InvalidCastException();
    }
    decimal IConvertible.ToDecimal(IFormatProvider provider)
    {
    throw new InvalidCastException();
    }
    double IConvertible.ToDouble(IFormatProvider provider)
    {
    throw new InvalidCastException();
    }
    short IConvertible.ToInt16(IFormatProvider provider)
    {
    throw new InvalidCastException();
    }
    int IConvertible.ToInt32(IFormatProvider provider)
    {
    throw new InvalidCastException();
    }
    long IConvertible.ToInt64(IFormatProvider provider)
    {
    throw new InvalidCastException();
    }
    sbyte IConvertible.ToSByte(IFormatProvider provider)
    {
    throw new InvalidCastException();
    }
    float IConvertible.ToSingle(IFormatProvider provider)
    {
    throw new InvalidCastException();
    }
    string IConvertible.ToString(IFormatProvider provider)
    {
    return ToString();
    }
    object IConvertible.ToType(Type conversionType, IFormatProvider provider)
    {
    switch (Type.GetTypeCode(conversionType))
    {
    case TypeCode.String:
    return ((IConvertible)this).ToString(provider);
    case TypeCode.Object:
    if (conversionType == typeof(object) || conversionType == typeof(ObjectId))
    {
    return this;
    }
    if (conversionType == typeof(BsonObjectId))
    {
    return new BsonObjectId(this);
    }
    if (conversionType == typeof(BsonString))
    {
    return new BsonString(((IConvertible)this).ToString(provider));
    }
    break;
    }
    throw new InvalidCastException();
    }
    ushort IConvertible.ToUInt16(IFormatProvider provider)
    {
    throw new InvalidCastException();
    }
    uint IConvertible.ToUInt32(IFormatProvider provider)
    {
    throw new InvalidCastException();
    }
    ulong IConvertible.ToUInt64(IFormatProvider provider)
    {
    throw new InvalidCastException();
    }
    }
    }






























































  • 學習是一個永無止境的過程,你知道的越多,你不知道的也會越多,在有限的時間內堅持每天多學一點,你一定能成為你想要成為的那個人。不積跬步無以至千裏,不積小流無以成江河!!!

    See you next good day