• .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位表示计数器。

    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的生成原理大家阅读如下源码即可。

    源码地址: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
    {
        /// 
        /// Represents an ObjectId (see also BsonObjectId).
        /// 
    #if NET45
        [Serializable]
    #endif
        public struct ObjectId : IComparable, IEquatable, 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
            /// 
            /// Initializes a new instance of the ObjectId class.
            /// 
            /// The bytes.
            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);
            }
    
            /// 
            /// Initializes a new instance of the ObjectId class.
            /// 
            /// The bytes.
            /// The index into the byte array where the ObjectId starts.
            internal ObjectId(byte[] bytes, int index)
            {
                FromByteArray(bytes, index, out _a, out _b, out _c);
            }
    
            /// 
            /// Initializes a new instance of the ObjectId class.
            /// 
            /// The timestamp (expressed as a DateTime).
            /// The machine hash.
            /// The PID.
            /// The increment.
            public ObjectId(DateTime timestamp, int machine, short pid, int increment)
                : this(GetTimestampFromDateTime(timestamp), machine, pid, increment)
            {
            }
    
            /// 
            /// Initializes a new instance of the ObjectId class.
            /// 
            /// The timestamp.
            /// The machine hash.
            /// The PID.
            /// The increment.
            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;
            }
    
            /// 
            /// Initializes a new instance of the ObjectId class.
            /// 
            /// The value.
            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
            /// 
            /// Gets an instance of ObjectId where the value is empty.
            /// 
            public static ObjectId Empty
            {
                get { return __emptyInstance; }
            }
    
            // public properties
            /// 
            /// Gets the timestamp.
            /// 
            public int Timestamp
            {
                get { return _a; }
            }
    
            /// 
            /// Gets the machine.
            /// 
            public int Machine
            {
                get { return (_b >> 8) & 0xffffff; }
            }
    
            /// 
            /// Gets the PID.
            /// 
            public short Pid
            {
                get { return (short)(((_b << 8) & 0xff00) | ((_c >> 24) & 0x00ff)); }
            }
    
            /// 
            /// Gets the increment.
            /// 
            public int Increment
            {
                get { return _c & 0xffffff; }
            }
    
            /// 
            /// Gets the creation time (derived from the timestamp).
            /// 
            public DateTime CreationTime
            {
                get { return BsonConstants.UnixEpoch.AddSeconds(Timestamp); }
            }
    
            // public operators
            /// 
            /// Compares two ObjectIds.
            /// 
            /// The first ObjectId.
            /// The other ObjectId
            /// True if the first ObjectId is less than the second ObjectId.
            public static bool operator <(ObjectId lhs, ObjectId rhs)
            {
                return lhs.CompareTo(rhs) < 0;
            }
    
            /// 
            /// Compares two ObjectIds.
            /// 
            /// The first ObjectId.
            /// The other ObjectId
            /// True if the first ObjectId is less than or equal to the second ObjectId.
            public static bool operator <=(ObjectId lhs, ObjectId rhs)
            {
                return lhs.CompareTo(rhs) <= 0;
            }
    
            /// 
            /// Compares two ObjectIds.
            /// 
            /// The first ObjectId.
            /// The other ObjectId.
            /// True if the two ObjectIds are equal.
            public static bool operator ==(ObjectId lhs, ObjectId rhs)
            {
                return lhs.Equals(rhs);
            }
    
            /// 
            /// Compares two ObjectIds.
            /// 
            /// The first ObjectId.
            /// The other ObjectId.
            /// True if the two ObjectIds are not equal.
            public static bool operator !=(ObjectId lhs, ObjectId rhs)
            {
                return !(lhs == rhs);
            }
    
            /// 
            /// Compares two ObjectIds.
            /// 
            /// The first ObjectId.
            /// The other ObjectId
            /// True if the first ObjectId is greather than or equal to the second ObjectId.
            public static bool operator >=(ObjectId lhs, ObjectId rhs)
            {
                return lhs.CompareTo(rhs) >= 0;
            }
    
            /// 
            /// Compares two ObjectIds.
            /// 
            /// The first ObjectId.
            /// The other ObjectId
            /// True if the first ObjectId is greather than the second ObjectId.
            public static bool operator >(ObjectId lhs, ObjectId rhs)
            {
                return lhs.CompareTo(rhs) > 0;
            }
    
            // public static methods
            /// 
            /// Generates a new ObjectId with a unique value.
            /// 
            /// An ObjectId.
            public static ObjectId GenerateNewId()
            {
                return GenerateNewId(GetTimestampFromDateTime(DateTime.UtcNow));
            }
    
            /// 
            /// Generates a new ObjectId with a unique value (with the timestamp component based on a given DateTime).
            /// 
            /// The timestamp component (expressed as a DateTime).
            /// An ObjectId.
            public static ObjectId GenerateNewId(DateTime timestamp)
            {
                return GenerateNewId(GetTimestampFromDateTime(timestamp));
            }
    
            /// 
            /// Generates a new ObjectId with a unique value (with the given timestamp).
            /// 
            /// The timestamp component.
            /// An ObjectId.
            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);
            }
    
            /// 
            /// Packs the components of an ObjectId into a byte array.
            /// 
            /// The timestamp.
            /// The machine hash.
            /// The PID.
            /// The increment.
            /// A byte array.
            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;
            }
    
            /// 
            /// Parses a string and creates a new ObjectId.
            /// 
            /// The string value.
            /// A ObjectId.
            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);
                }
            }
    
            /// 
            /// Tries to parse a string and create a new ObjectId.
            /// 
            /// The string value.
            /// The new ObjectId.
            /// True if the string was parsed successfully.
            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;
            }
    
            /// 
            /// Unpacks a byte array into the components of an ObjectId.
            /// 
            /// A byte array.
            /// The timestamp.
            /// The machine hash.
            /// The PID.
            /// The increment.
            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
            }
    
            /// 
            /// 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.
            /// 
            [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
            /// 
            /// Compares this ObjectId to another ObjectId.
            /// 
            /// The other ObjectId.
            /// A 32-bit signed integer that indicates whether this ObjectId is less than, equal to, or greather than the other.
            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);
            }
    
            /// 
            /// Compares this ObjectId to another ObjectId.
            /// 
            /// The other ObjectId.
            /// True if the two ObjectIds are equal.
            public bool Equals(ObjectId rhs)
            {
                return
                    _a == rhs._a &&
                    _b == rhs._b &&
                    _c == rhs._c;
            }
    
            /// 
            /// Compares this ObjectId to another object.
            /// 
            /// The other object.
            /// True if the other object is an ObjectId and equal to this one.
            public override bool Equals(object obj)
            {
                if (obj is ObjectId)
                {
                    return Equals((ObjectId)obj);
                }
                else
                {
                    return false;
                }
            }
    
            /// 
            /// Gets the hash code.
            /// 
            /// The hash code.
            public override int GetHashCode()
            {
                int hash = 17;
                hash = 37 * hash + _a.GetHashCode();
                hash = 37 * hash + _b.GetHashCode();
                hash = 37 * hash + _c.GetHashCode();
                return hash;
            }
    
            /// 
            /// Converts the ObjectId to a byte array.
            /// 
            /// A byte array.
            public byte[] ToByteArray()
            {
                var bytes = new byte[12];
                ToByteArray(bytes, 0);
                return bytes;
            }
    
            /// 
            /// Converts the ObjectId to a byte array.
            /// 
            /// The destination.
            /// The offset.
            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);
            }
    
            /// 
            /// Returns a string representation of the value.
            /// 
            /// A string representation of the value.
            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();
            }
        }
    }
    复制代码

     

     

     

    4-byte Unix 时间戳,3-byte 机器 ID,2-byte 进程 ID,3-byte 计数器(初始化随机)
  • 相关阅读:
    安装nodejs
    【无标题】
    R语言将连续变量映射到颜色或尺寸上二
    redis事务、乐观锁和悲观锁以及秒杀测试案例
    ES6——Set和Map集合介绍
    Unity中的简单数据存储办法
    python逆向还原dnspy反编译的C#算法
    研究一下 JSON.parse(JSON.stringify(obj))
    Config:分布式配置中心
    华为机试真题 C++ 实现【租车骑绿岛】【2022.11 Q4新题】
  • 原文地址:https://www.cnblogs.com/Can-daydayup/p/17249852.html