Inverse solution of objectid in. Net core

Time:2021-2-18

preface

When designing a database, we usually need to assign a primary key to the business data table. In many cases, in order to save trouble, I use the guidguuid directly. However, in monggodb, objectid (hereinafter referred to as OID) is implemented internally. The implementation of the source code is given in the driver of. Net core.

After carefully studying the official source code, we found that its implementation principle is very simple and easy to learn. In the latest version, the unpack function is emasculated, which may be that the official does not think there are too many use scenarios for unpacking. However, we think that for data traceability, the operation of unpacking is very necessary, especially in the context of the current popularity of micro services.

To this end, in reference to the official code on the basis of some improvements, increased their own needs. This example code adds the operation of understanding package, implicit conversion of string, and public attribute of reading unpacked data.

Data structure of objectid

First, let’s look at the design of oid’s data structure.

As can be seen from the figure above, the data structure of oid is mainly composed of four parts: Unix timestamp, machine name, process number and auto increment number. Oid is actually a string with a total length of 12 bytes 24. The easy to remember formula is: 4323, 4 bytes of time, 3 bytes of machine name, 2 bytes of process number and 3 bytes of auto increment number.

1. UNIX time stamp: Unix time stamp is recorded in seconds, that is, the total number of seconds from 1970 / 1 / 1 00:00:00 to the current time.
2. Machine name: record the equipment number of the current production oid
3. Process number: the number of the currently running oid program
4. Auto increment number: in the current second, each call will automatically grow (thread safe)

According to the algorithm, the maximum number of IDs generated in one second is 2 ^ 24 = 16777216 records, so there is no need to worry about ID collision.

Realization idea

Let’s take a look at the class structure diagram after code implementation.

It can be found from the above figure that the class diagram is mainly composed of two parts: objectid / objectidfactory. In the class objectid, it mainly implements operations such as production, unpacking, calculation, conversion, and data structure disclosure. However, objectidfactory has only one function, that is, production oid.

Therefore, we know that the newid in the class objectid actually calls the newid method of objectidfactory.

For the sake of productivity, the static objectidfactory object is declared in objectid. Some initialization work needs to be completed in the objectidfactory constructor when the program starts, such as obtaining the machine name and process number. These are all one-time work.

Code implementation of class objectidfactory

public class ObjectIdFactory
{
  private int increment;
  private readonly byte[] pidHex;
  private readonly byte[] machineHash;
  private readonly UTF8Encoding utf8 = new UTF8Encoding(false);
  private readonly DateTime unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

  public ObjectIdFactory()
  {
    MD5 md5 = MD5.Create();
    machineHash = md5.ComputeHash(utf8.GetBytes(Dns.GetHostName()));
    pidHex = BitConverter.GetBytes(Process.GetCurrentProcess().Id);
    Array.Reverse(pidHex);
  }

  /// <summary>
  ///A new 24 digit unique number is generated
  /// </summary>
  /// <returns></returns>
  public ObjectId NewId()
  {
    int copyIdx = 0;
    byte[] hex = new byte[12];
    byte[] time = BitConverter.GetBytes(GetTimestamp());
    Array.Reverse(time);
    Array.Copy(time, 0, hex, copyIdx, 4);
    copyIdx += 4;

    Array.Copy(machineHash, 0, hex, copyIdx, 3);
    copyIdx += 3;

    Array.Copy(pidHex, 2, hex, copyIdx, 2);
    copyIdx += 2;

    byte[] inc = BitConverter.GetBytes(GetIncrement());
    Array.Reverse(inc);
    Array.Copy(inc, 1, hex, copyIdx, 3);

    return new ObjectId(hex);
  }

  private int GetIncrement() => System.Threading.Interlocked.Increment(ref increment);
  private int GetTimestamp() => Convert.ToInt32(Math.Floor((DateTime.UtcNow - unixEpoch).TotalSeconds));
}

The internal implementation of ObjectIdFactory is very simple, but it is also the core of the whole Oid program. In the constructor, the machine name and process number are acquired for subsequent production. In the core method NewId, Timestamp, machineHash, pidHex and increment are written to several groups in turn. Finally, new ObjectId (hex) is invoked to return the production of good Oid.

Code implementation of class objectid

Code implementation of class objectid
public class ObjectId
{
  private readonly static ObjectIdFactory factory = new ObjectIdFactory();

  public ObjectId(byte[] hexData)
  {
    this.Hex = hexData;
    ReverseHex();
  }
  
  public override string ToString()
  {
    if (Hex == null)
      Hex = new byte[12];
    StringBuilder hexText = new StringBuilder();
    for (int i = 0; i < this.Hex.Length; i++)
    {
      hexText.Append(this.Hex[i].ToString("x2"));
    }
    return hexText.ToString();
  }

  public override int GetHashCode() => ToString().GetHashCode();

  public ObjectId(string value)
  {
    if (string.IsNullOrEmpty(value)) throw new ArgumentNullException("value");
    if (value.Length != 24) throw new ArgumentOutOfRangeException("value should be 24 characters");
    Hex = new byte[12];
    for (int i = 0; i < value.Length; i += 2)
    {
      try
      {
        Hex[i / 2] = Convert.ToByte(value.Substring(i, 2), 16);
      }
      catch
      {
        Hex[i / 2] = 0;
      }
    }
    ReverseHex();
  }

  private void ReverseHex()
  {
    int copyIdx = 0;
    byte[] time = new byte[4];
    Array.Copy(Hex, copyIdx, time, 0, 4);
    Array.Reverse(time);
    this.Timestamp = BitConverter.ToInt32(time, 0);
    copyIdx += 4;
    byte[] mid = new byte[4];
    Array.Copy(Hex, copyIdx, mid, 0, 3);
    this.Machine = BitConverter.ToInt32(mid, 0);
    copyIdx += 3;
    byte[] pids = new byte[4];
    Array.Copy(Hex, copyIdx, pids, 0, 2);
    Array.Reverse(pids);
    this.ProcessId = BitConverter.ToInt32(pids, 0);
    copyIdx += 2;
    byte[] inc = new byte[4];
    Array.Copy(Hex, copyIdx, inc, 0, 3);
    Array.Reverse(inc);
    this.Increment = BitConverter.ToInt32(inc, 0);
  }

  public static ObjectId NewId() => factory.NewId();

  public int CompareTo(ObjectId other)
  {
    if (other is null)
      return 1;
    for (int i = 0; i < Hex.Length; i++)
    {
      if (Hex[i] < other.Hex[i])
        return -1;
      else if (Hex[i] > other.Hex[i])
        return 1;
    }
    return 0;
  }

  public bool Equals(ObjectId other) => CompareTo(other) == 0;
  public static bool operator <(ObjectId a, ObjectId b) => a.CompareTo(b) < 0;
  public static bool operator <=(ObjectId a, ObjectId b) => a.CompareTo(b) <= 0;
  public static bool operator ==(ObjectId a, ObjectId b) => a.Equals(b);
  public override bool Equals(object obj) => base.Equals(obj);
  public static bool operator !=(ObjectId a, ObjectId b) => !(a == b);
  public static bool operator >=(ObjectId a, ObjectId b) => a.CompareTo(b) >= 0;
  public static bool operator >(ObjectId a, ObjectId b) => a.CompareTo(b) > 0;
  public static implicit operator string(ObjectId objectId) => objectId.ToString();
  public static implicit operator ObjectId(string objectId) => new ObjectId(objectId);
  public static ObjectId Empty { get { return new ObjectId("000000000000000000000000"); } }
  public byte[] Hex { get; private set; }
  public int Timestamp { get; private set; }
  public int Machine { get; private set; }
  public int ProcessId { get; private set; }
  public int Increment { get; private set; }
}

Objectid seems to have a little more code, but in fact, the core implementation method is only the reversehex () method, which reverses internally ObjectIdFactory.NewId () so that the caller can call the ObjectId.Timestamp And other public attributes to trace the production process of oid.

Other object comparison and implicit conversion to string / objectid are grammatical tasks, which are all for improving coding efficiency.

It should be noted that the static object objectidfactory is created inside the class objectid. We still remember the initialization work inside the constructor of objectidfactory. The static object created here is also a design to improve production efficiency.

Call example

After completing the code transformation, we can call and test the transformed code to verify the correctness of the program.

NewId

Let’s try to produce a set of oid to see the effect.


for (int i = 0; i < 100; i++)
{
  var oid = ObjectId.NewId();
  Console.WriteLine(oid);
}

output

As can be seen from the above figure, the output of this part of oid is orderly, which should also be a reason to replace the guid / UUID.

Production / unpacking


var sourceId = ObjectId.NewId();
var reverseId = new ObjectId(sourceId);

It can be seen from the unpacking that the values in the two red boxes in the figure above are consistent, and the unpacking is successful!

Implicit transformation

var sourceId = ObjectId.NewId();

//Convert to string
var stringId = sourceId;
string userId= ObjectId.NewId();

//Convert to objectid
ObjectId id = stringId;

Implicit conversion can improve coding efficiency!

Concluding remarks

Through the above code implementation, into some of their own needs. Now, business tracking and log checking can be realized by unpacking. In some scenarios, it is very helpful. The added implicit conversion syntax sugar can also improve the coding efficiency. At the same time, the code is optimized to. Net core 3.1, and some C # syntax sugar is also used.

The above is the detailed content of the method to realize objectid inverse solution in. Net core. For more information about objectid inverse solution in. Net core, please pay attention to other related articles in developer!