.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();         }     } }

 

 

 

发表评论

评论已关闭。

相关文章