.NET生成MongoDB中的主键ObjectId

前言

  因为很多场景下我们需要在创建MongoDB数据的时候提前生成好主键为了返回或者通过主键查询创建的业务,像EF中我们可以生成Guid来,本来想着要不要实现一套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位表示计数器。

601e2b6b  a3203c  c89f   2d31aa   ↑        ↑       ↑       ↑ 时间戳    机器码   进程ID   随机数

MongoDB.Driver驱动安装

1、直接命令自动安装

Install-Package MongoDB.Driver

2、搜索Nuget手动安装

.NET生成MongoDB中的主键ObjectId

调用生成主键ObjectId

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

mongo-csharp-driver ObjectId详解

关于ObjectId的生成原理大家阅读如下源码即可。

源码地址: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            {                return false;            }        }
        /// <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();            return hash;        }
        /// <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();        }    }}