Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fubar-coder/393cb9d41db707bd8730df0bd2a92d06 to your computer and use it in GitHub Desktop.
Save fubar-coder/393cb9d41db707bd8730df0bd2a92d06 to your computer and use it in GitHub Desktop.
SequentialGuid

By Alex Siepman

Converted to Markdown from http://www.siepman.nl/blog/post/2013/10/28/ID-Sequential-Guid-COMB-Vs-Int-Identity-using-Entity-Framework.aspx

Introduction

Recently i had to choose a type for an Id in a SQL server Database. We are using Entity framework to access the database. I had to choose between Guid an Int. I seems to me that Guid must be a slow solution but in a real world scenario, this was not true (see results later in this blog). First an (optional) small introduction about some terms used here. Then the testresult, folowwing by the C# implementation of a third option, called Sequential Guid.

Guid

Is a 16 byte random number (for example c68199c2-f8e3-42bf-83f7-479bd33c94e6). It is the Micrsoft implementation of a UUDI. Think of all the possible numbers as unique tickets of a lottery. You need to sell 200.000.000.000 tickets every second from the big bang unitil now (13.700.000.000 years) to sell all the unique tickets (numbers). If you use that random chosen number as an Id in a database, the chance of a duplicate is virtualy zero. The probability of one duplicate would be about 50% if every person on earth owned 600.000.000 Guids.

UniqueIdentifier

A Guid is called a uniqueidentifier in the SQL server database. The last 6 bytes of it are the most significant bytes when the records are indexed / sorted.

Sequential Guid / COMB

Is a Guid but the most significant bytes are created sequentially. It is also called COMB (combination of Guid and sequence).

SqlGuid

A .NET framework object that sorts Guids the same way as the uniqueidentifier in the SQL server database.

Advantages of a Guid

Using a Guid as Id instead of a int wit a autonummer / database-sequence / Identity has a lot of (functional) advantages:

  • Object orientation: An object can creates it own Id. That supports one of the most important object orientation principles: encapsulation.
  • Safer: It is very hard to guess an Id.
  • Faster: Getting an Id using a connection to the database or even a webservice call to a webservice that calls to a database, is much slower than creating a Guid with Guid.NewGuid().
  • Less errors: Less code means less errors. Creating a guid uses less code to get the new Id-value in your object.
  • No connection needed. If your app in offline, the app can still add new record locally and safe them later in the database.
  • Synchronizing easier: synchronize data from multiple sources is much easier.
  • Merging tables easier: This is much easier as the Guid is always unique.

Advantages of Int

Using an int also has advantages:

  • Testing and debugging: Working with 22 is easier than fbc32944-d573-4d67-bed2-565cbe6d5f70, even when using test-guids like 00000000-0000-4000-0000-000000000022.
  • Uses less memory: an int uses less space on disk or in memory. Note that an Int in memory on a 64 bits computer takes 64 bits and a guid 128. Not only the fields but also the indexes takes more space.
  • Better support: an Int is supported by all databases. However the implementation of the autonumbering is different.

How fast (or slow) is a Guid?

Searching the internet, I found many statements that Guid is much slower, in particular when inserting a new record in the database. The Id of a table is the clustered index bij default (the fysical order of the records) so you can image that inserting record random in an existing list less efficent than adding records at the end.

Now we know that adding at the end is easier for the database. We can aldo use a Sequential Guid. In this case I created a Sequential Guid with 10 random bytes and 6 bytes created sequentialy, running out of sequential values in almost 90 years (or 281.474.976.710.656 values).

I created 3.000.000 records in a database on the same computer as the C# application that uses the database. So you get an idea of the total costst. Every insert uses a new DbContext. I tested 5 scenario's (SQL create statements here):

  • Int not declared with Identity(1,1) / autonumber. This is not a real world scenario, but it shows the extra time that it takes to use Identity in an other Scenario.
  • Sequential Guid
  • Int declared with Identity(1,1) / autonumber. I didn't used a the Sequence introduced in SqlServer 2012 because it is likely to be slower.
  • Regular Guid with clusted index.
  • Regular Guid with non clustered index and an extra field with a Clustered Index declared with Identity(1,1).

Here are the results of 3.000.000 inserts:

Int (Not unique) Sequential guid Int, identity Guid clustered Guid, non clustered
500.000 0:45:01 00:49:14 00:51:31 00:49:36 00:57:48
1.000.000 01:30:54 01:38:48 01:43:23 01:45:37 01:58:14
1.500.000 02:15:30 02:27:19 02:36:34 02:42:43 03:00:08
2.000.000 03:00:22 03:15:05 03:28:42 03:39:59 04:03:10
2.500.000 03:45:20 04:02:40 04:21:53 04:36:28 05:06:09
3.000.000 04:31:29 04:51:06 05:14:36 05:32:05 06:11:19
Absolute 00:19:37 00:43:07 01:00:36 01:39:50
Percentage 7,23% 14,81% 19,26% 30,06%

Result

The sequential Guid is actually faster than the Int, declared with Identity!

The Sequential Guid Gernerator implementation

The sequential Guid is essentially a Guid with the last 6 bytes replaced by a number that corresponds to the current date and time. I saw some implemtations on the internet that created the 6 bytes this way:

Date Time Value
Januari 1, 0001 00:00:00 Hex: 00000
December 31, 9999 23:59:59 Hex: FFFFFF

This makes no sense as the application wil never run 2000 years ago and it is also unlikely that it runs for almost 8000 years. That is why I used this:

Date Time Value
Oktober 15, 2011 00:00:00 Hex: 00000
Januari 1, 2100 00:00:00 Hex: FFFFFF

This are the default values but you can use other values (when calling the constructor). In this case, the counter is incremented 3 times per millisecond.

Some examples of using the SequentialGuid:

// Based on DateTime.Now
Guid sqlGuid1 = SequentialGuid.NewGuid();// The easy way suitable for 99% of the usages
Guid sqlGuid2 = new SequentialGuid().GetGuid();
 
// Use an other range 
SequentialGuid sqlGuidCenturyObject = new SequentialGuid(new DateTime(2013, 10, 30), new DateTime(2113, 10, 30));
// 25 Guids from the object above
IEnumerable<Guid> guids = Enumerable.Range(1, 25).Select(o => sqlGuidCenturyObject.GetGuid());
 
// Based on an other date within the range. Used in unit tests of the SequentialGuid class
Guid sqlGuid3 = new SequentialGuid().GetGuid(new DateTime(2015, 1, 1));
Guid sqlGuid4 = new SequentialGuid().GetGuid(DateTime.Now.Ticks + 1000);

Source code of SequentialGuid

Note: A newer version of the Source code can be found here.

This is the source code used for this post:

public class SequentialGuid
{
 
    public DateTime SequenceStartDate { get; private set; }
    public DateTime SequenceEndDate { get; private set; }
 
    private const int NumberOfBytes = 6;
    private const int PermutationsOfAByte = 256;
    private readonly long _maximumPermutations = (long)Math.Pow(PermutationsOfAByte, NumberOfBytes);
    private long _lastSequence;
 
    public SequentialGuid(DateTime sequenceStartDate, DateTime sequenceEndDate)
    {
        SequenceStartDate = sequenceStartDate;
        SequenceEndDate = sequenceEndDate;
    }
 
    public SequentialGuid()
        : this(new DateTime(2011, 10, 15), new DateTime(2100, 1, 1))
    {
    }
 
    private static readonly Lazy<SequentialGuid> InstanceField = new Lazy<SequentialGuid>(() => new SequentialGuid());
    internal static SequentialGuid Instance
    {
        get
        {
            return InstanceField.Value;
        }
    }
 
    public static Guid NewGuid()
    {
        return Instance.GetGuid();
    }
 
    public TimeSpan TimePerSequence
    {
        get
        {
            var ticksPerSequence = TotalPeriod.Ticks / _maximumPermutations;
            var result = new TimeSpan(ticksPerSequence);
            return result;
        }
    }
 
    public TimeSpan TotalPeriod
    {
        get
        {
            var result = SequenceEndDate - SequenceStartDate;
            return result;
        }
    }
 
    private long GetCurrentSequence(DateTime value)
    {
        var ticksUntilNow = value.Ticks - SequenceStartDate.Ticks;
        var result = ((decimal)ticksUntilNow / TotalPeriod.Ticks * _maximumPermutations);
        return (long)result;
    }
 
    public Guid GetGuid()
    {
        return GetGuid(DateTime.Now);
    }
 
    private readonly object _synchronizationObject = new object();
    internal Guid GetGuid(DateTime now)
    {
        if (now < SequenceStartDate || now >= SequenceEndDate)
        {
            return Guid.NewGuid(); // Outside the range, use regular Guid
        }
 
        var sequence = GetCurrentSequence(now);
        return GetGuid(sequence);
    }
 
    internal Guid GetGuid(long sequence)
    {
        lock (_synchronizationObject)
        {
            if (sequence <= _lastSequence)
            {
                // Prevent double sequence on same server
                sequence = _lastSequence + 1;
            }
            _lastSequence = sequence;
        }
 
        var sequenceBytes = GetSequenceBytes(sequence);
        var guidBytes = GetGuidBytes();
        var totalBytes = guidBytes.Concat(sequenceBytes).ToArray();
        var result = new Guid(totalBytes);
        return result;
    }
 
    private IEnumerable<byte> GetSequenceBytes(long sequence)
    {
        var sequenceBytes = BitConverter.GetBytes(sequence);
        var sequenceBytesLongEnough = sequenceBytes.Concat(new byte[NumberOfBytes]);
        var result = sequenceBytesLongEnough.Take(NumberOfBytes).Reverse();
        return result;
    }
 
    private IEnumerable<byte> GetGuidBytes()
    {
        var result = Guid.NewGuid().ToByteArray().Take(10).ToArray();
        return result;
    }
}

Note: In the real implementation you better don't use DateTime.Now but an other version that is easy to mock when unittesting. The production code that I use, goes out of scope for this blog.

By Alex Siepman

Converted to Markdown from http://www.siepman.nl/blog/post/2015/06/20/SequentialGuid-Comb-Sql-Server-With-Creation-Date-Time-.aspx

Why a new version of SequentialGuid?

In an earlier post I compared the performance of Int (with autonumber) and a (Sequential) guid in SQL server. I posted an implementation of a C# SequentialGuid that created Guid's that were sequential, but they where still regular Guids. Once you had a Guid, it was just a regular Guid with the last 6 bytes ordered. The new version is a wrapper around the Guid with some cool extra stuff.

What is new in this version?

The basic algorithm is the same (backwards compatible) but the new version adds the following (new) possibilities:

// Create some SequentialGuids
var sGuid1 = SequentialGuid.NewSequentialGuid(); // 475db041-7922-4ac2-b111-0a727ed760b4
System.Threading.Thread.Sleep(500);
var sGuid2 = SequentialGuid.NewSequentialGuid(); // f7b1fd91-964a-48ca-8c6c-0a727ed826e7
var sGuid3 = SequentialGuid.NewSequentialGuid(); // 92ff115d-d1bd-4374-af81-0a727ed826e8
 
// NEW: Comparing Sequential Guids is now easy
 
var isBigger = sGuid2 > sGuid3; // false
var isSmaller = sGuid2 < sGuid3; // true
 
var wrongOrder = new[] {sGuid2, sGuid3, sGuid1};
var goodOrder = wrongOrder.OrderBy(g => g); // sGuid1, sGuid2, sGuid3
 
// NEW (and cool): You can see the time creation date and time, 
//                 even after it was stored in the database
 
var sGuidFromDb1 = (SequentialGuid) new Guid("475db041-7922-4ac2-b111-0a727ed760b4");
var sGuidFromDb2 = (SequentialGuid) new Guid("f7b1fd91-964a-48ca-8c6c-0a727ed826e7");
var sGuidFromDb3 = (SequentialGuid) new Guid("92ff115d-d1bd-4374-af81-0a727ed826e8");
 
string s1 = sGuidFromDb1.ToString(); //475db041-7922-4ac2-b111-0a727ed760b4 (2015-06-20 14:51:21.634)
             
DateTime d2 = sGuidFromDb2.CreatedDateTime; // 2015-06-20 14:51:22.1349254
DateTime d3 = sGuidFromDb3.CreatedDateTime; // 2015-06-20 14:51:22.1349353
 
// Regular stuff
 
Guid id = SequentialGuid.NewSequentialGuid(); // Just like Guid.NewGuid(); 
// id = 30adce22-de76-4279-99ca-0a72884df151

The ordering is the same as in SQL Server.

Choices

I decided to do the conversion from SequentialGuid to Guid implicit because each SequentialGuid is a Guid. The conversion from Guid to SequentialGuid needs to be explicit because not all Guids are SequentialGuids.

The SequentialGuid is a struct, because the Guid was already a struct. This makes the difference between the two as small as possible. The CreationDataTime is not lazy because a struct should not use much memory.

The period is no longer parameterized in the constructor, because comparing will not work with different periods.

Guids and Sequential Guids can also be compared.

Source

This is the source of the new version of SequentialGuid:

[Serializable]
public struct SequentialGuid : IComparable<SequentialGuid>, IComparable<Guid>, IComparable
{
    private const int NumberOfSequenceBytes = 6;
    private const int PermutationsOfAByte = 256;
    private static readonly long MaximumPermutations = 
            (long)Math.Pow(PermutationsOfAByte, NumberOfSequenceBytes);
    private static long _lastSequence;
         
    
    private static readonly DateTime SequencePeriodStart = 
        new DateTime(2011, 11, 15, 0, 0, 0, DateTimeKind.Utc); // Start = 000000
 
    private static readonly DateTime SequencePeriodeEnd = 
        new DateTime(2100, 1, 1, 0, 0, 0, DateTimeKind.Utc);   // End   = FFFFFF
 
    private readonly Guid _guidValue;
 
    public SequentialGuid(Guid guidValue)
    {
        _guidValue = guidValue;
    }
 
    public SequentialGuid(string guidValue)
        : this(new Guid(guidValue))
    {
    }
 
    [System.Security.SecuritySafeCritical]
    public static SequentialGuid NewSequentialGuid()
    {
        // You might want to inject DateTime.Now in production code
        return new SequentialGuid(GetGuidValue(DateTime.Now));
    }
 
    public static TimeSpan TimePerSequence
    {
        get
        {
            var ticksPerSequence = TotalPeriod.Ticks / MaximumPermutations;
            var result = new TimeSpan(ticksPerSequence);
            return result;
        }
    }
 
    public static TimeSpan TotalPeriod
    {
        get
        {
            var result = SequencePeriodeEnd - SequencePeriodStart;
            return result;
        }
    }
 
    #region FromDateTimeToGuid
 
    // Internal for testing
    internal static Guid GetGuidValue(DateTime now)
    {
        if (now < SequencePeriodStart || now >= SequencePeriodeEnd)
        {
            return Guid.NewGuid(); // Outside the range, use regular Guid
        }
 
        var sequence = GetCurrentSequence(now);
        return GetGuid(sequence);
    }
 
    private static long GetCurrentSequence(DateTime now)
    {
        var ticksUntilNow = now.Ticks - SequencePeriodStart.Ticks;
        var factor = (decimal)ticksUntilNow / TotalPeriod.Ticks;
        var resultDecimal = factor * MaximumPermutations;
        var resultLong = (long)resultDecimal;
        return resultLong;
    }
 
    private static readonly object SynchronizationObject = new object();
    private static Guid GetGuid(long sequence)
    {
        lock (SynchronizationObject)
        {
            if (sequence <= _lastSequence)
            {
                // Prevent double sequence on same server
                sequence = _lastSequence + 1;
            }
            _lastSequence = sequence;
        }
 
        var sequenceBytes = GetSequenceBytes(sequence);
        var guidBytes = GetGuidBytes();
        var totalBytes = guidBytes.Concat(sequenceBytes).ToArray();
        var result = new Guid(totalBytes);
        return result;
    }
 
    private static IEnumerable<byte> GetSequenceBytes(long sequence)
    {
        var sequenceBytes = BitConverter.GetBytes(sequence);
        var sequenceBytesLongEnough = sequenceBytes.Concat(new byte[NumberOfSequenceBytes]);
        var result = sequenceBytesLongEnough.Take(NumberOfSequenceBytes).Reverse();
        return result;
    }
 
    private static IEnumerable<byte> GetGuidBytes()
    {
        return Guid.NewGuid().ToByteArray().Take(10);
    }
 
    #endregion
 
    #region FromGuidToDateTime
 
    public DateTime CreatedDateTime
    {
        get
        {
            return GetCreatedDateTime(_guidValue);
        }
    }
 
    internal static DateTime GetCreatedDateTime(Guid value)
    {
        var sequenceBytes = GetSequenceLongBytes(value).ToArray();
        var sequenceLong = BitConverter.ToInt64(sequenceBytes, 0);
        var sequenceDecimal = (decimal)sequenceLong;
        var factor = sequenceDecimal / MaximumPermutations;
        var ticksUntilNow = factor * TotalPeriod.Ticks;
        var nowTicksDecimal = ticksUntilNow + SequencePeriodStart.Ticks;
        var nowTicks = (long)nowTicksDecimal;
    var result = new DateTime(nowTicks);
    return result;
    }
 
    private static IEnumerable<byte> GetSequenceLongBytes(Guid value)
    {
        const int numberOfBytesOfLong = 8;
        var sequenceBytes = value.ToByteArray().Skip(10).Reverse().ToArray();
        var additionalBytesCount = numberOfBytesOfLong - sequenceBytes.Length;
        return sequenceBytes.Concat(new byte[additionalBytesCount]);
    }
 
    #endregion
 
    #region Relational Operators
 
    public static bool operator <(SequentialGuid value1, SequentialGuid value2)
    {
        return value1.CompareTo(value2) < 0;
    }
 
    public static bool operator >(SequentialGuid value1, SequentialGuid value2)
    {
        return value1.CompareTo(value2) > 0;
    }
 
    public static bool operator <(Guid value1, SequentialGuid value2)
    {
        return value1.CompareTo(value2) < 0;
    }
 
    public static bool operator >(Guid value1, SequentialGuid value2)
    {
        return value1.CompareTo(value2) > 0;
    }
 
    public static bool operator <(SequentialGuid value1, Guid value2)
    {
        return value1.CompareTo(value2) < 0;
    }
 
    public static bool operator >(SequentialGuid value1, Guid value2)
    {
        return value1.CompareTo(value2) > 0;
    }
 
    public static bool operator <=(SequentialGuid value1, SequentialGuid value2)
    {
        return value1.CompareTo(value2) <= 0;
    }
 
    public static bool operator >=(SequentialGuid value1, SequentialGuid value2)
    {
        return value1.CompareTo(value2) >= 0;
    }
 
    public static bool operator <=(Guid value1, SequentialGuid value2)
    {
        return value1.CompareTo(value2) <= 0;
    }
 
    public static bool operator >=(Guid value1, SequentialGuid value2)
    {
        return value1.CompareTo(value2) >= 0;
    }
 
    public static bool operator <=(SequentialGuid value1, Guid value2)
    {
        return value1.CompareTo(value2) <= 0;
    }
 
    public static bool operator >=(SequentialGuid value1, Guid value2)
    {
        return value1.CompareTo(value2) >= 0;
    }
 
    #endregion
 
    #region Equality Operators
 
    public static bool operator ==(SequentialGuid value1, SequentialGuid value2)
    {
        return value1.CompareTo(value2) == 0;
    }
 
    public static bool operator !=(SequentialGuid value1, SequentialGuid value2)
    {
        return !(value1 == value2);
    }
 
    public static bool operator ==(Guid value1, SequentialGuid value2)
    {
        return value1.CompareTo(value2) == 0;
    }
 
    public static bool operator !=(Guid value1, SequentialGuid value2)
    {
        return !(value1 == value2);
    }
 
    public static bool operator ==(SequentialGuid value1, Guid value2)
    {
        return value1.CompareTo(value2) == 0;
    }
 
    public static bool operator !=(SequentialGuid value1, Guid value2)
    {
        return !(value1 == value2);
    }
 
    #endregion
 
    #region CompareTo
 
    public int CompareTo(object obj)
    {
        if (obj is SequentialGuid)
        {
            return CompareTo((SequentialGuid)obj);
        }
        if (obj is Guid)
        {
            return CompareTo((Guid)obj);
        }
        throw new ArgumentException("Parameter is not of the rigt type");
    }
 
    public int CompareTo(SequentialGuid other)
    {
        return CompareTo(other._guidValue);
    }
 
    public int CompareTo(Guid other)
    {
        return CompareImplementation(_guidValue, other);
    }
 
    private static readonly int[] IndexOrderingHighLow =
                                  { 10, 11, 12, 13, 14, 15, 8, 9, 7, 6, 5, 4, 3, 2, 1, 0 };
 
    private static int CompareImplementation(Guid left, Guid right)
    {
        var leftBytes = left.ToByteArray();
        var rightBytes = right.ToByteArray();
 
        return IndexOrderingHighLow.Select(i => leftBytes[i].CompareTo(rightBytes[i]))
                                   .FirstOrDefault(r => r != 0);
    }
 
    #endregion
 
    #region Equals
 
    public override bool Equals(Object obj)
    {
        if (obj is SequentialGuid || obj is Guid)
        {
            return CompareTo(obj) == 0;
        }
 
        return false;
    }
 
    public bool Equals(SequentialGuid other)
    {
        return CompareTo(other) == 0;
    }
 
    public bool Equals(Guid other)
    {
        return CompareTo(other) == 0;
    }
 
    public override int GetHashCode()
    {
        return _guidValue.GetHashCode();
    }
 
    #endregion
 
    #region Conversion operators
 
    public static implicit operator Guid(SequentialGuid value)
    {
        return value._guidValue;
    }
 
    public static explicit operator SequentialGuid(Guid value)
    {
        return new SequentialGuid(value);
    }
 
    #endregion
 
    #region ToString
 
    public override string ToString()
    {
        var roundedCreatedDateTime = Round(CreatedDateTime, TimeSpan.FromMilliseconds(1));
        return string.Format("{0} ({1:yyyy-MM-dd HH:mm:ss.fff})", 
                             _guidValue, roundedCreatedDateTime);
    }
 
    private static DateTime Round(DateTime dateTime, TimeSpan interval)
    {
        var halfIntervalTicks = (interval.Ticks + 1) >> 1;
 
        return dateTime.AddTicks(halfIntervalTicks - 
               ((dateTime.Ticks + halfIntervalTicks) % interval.Ticks));
    }
 
    #endregion
}
public class SequentialGuid
{
public DateTime SequenceStartDate { get; private set; }
public DateTime SequenceEndDate { get; private set; }
private const int NumberOfBytes = 6;
private const int PermutationsOfAByte = 256;
private readonly long _maximumPermutations = (long)Math.Pow(PermutationsOfAByte, NumberOfBytes);
private long _lastSequence;
public SequentialGuid(DateTime sequenceStartDate, DateTime sequenceEndDate)
{
SequenceStartDate = sequenceStartDate;
SequenceEndDate = sequenceEndDate;
}
public SequentialGuid()
: this(new DateTime(2011, 10, 15), new DateTime(2100, 1, 1))
{
}
private static readonly Lazy<SequentialGuid> InstanceField = new Lazy<SequentialGuid>(() => new SequentialGuid());
internal static SequentialGuid Instance
{
get
{
return InstanceField.Value;
}
}
public static Guid NewGuid()
{
return Instance.GetGuid();
}
public TimeSpan TimePerSequence
{
get
{
var ticksPerSequence = TotalPeriod.Ticks / _maximumPermutations;
var result = new TimeSpan(ticksPerSequence);
return result;
}
}
public TimeSpan TotalPeriod
{
get
{
var result = SequenceEndDate - SequenceStartDate;
return result;
}
}
private long GetCurrentSequence(DateTime value)
{
var ticksUntilNow = value.Ticks - SequenceStartDate.Ticks;
var result = ((decimal)ticksUntilNow / TotalPeriod.Ticks * _maximumPermutations);
return (long)result;
}
public Guid GetGuid()
{
return GetGuid(DateTime.Now);
}
private readonly object _synchronizationObject = new object();
internal Guid GetGuid(DateTime now)
{
if (now < SequenceStartDate || now >= SequenceEndDate)
{
return Guid.NewGuid(); // Outside the range, use regular Guid
}
var sequence = GetCurrentSequence(now);
return GetGuid(sequence);
}
internal Guid GetGuid(long sequence)
{
lock (_synchronizationObject)
{
if (sequence <= _lastSequence)
{
// Prevent double sequence on same server
sequence = _lastSequence + 1;
}
_lastSequence = sequence;
}
var sequenceBytes = GetSequenceBytes(sequence);
var guidBytes = GetGuidBytes();
var totalBytes = guidBytes.Concat(sequenceBytes).ToArray();
var result = new Guid(totalBytes);
return result;
}
private IEnumerable<byte> GetSequenceBytes(long sequence)
{
var sequenceBytes = BitConverter.GetBytes(sequence);
var sequenceBytesLongEnough = sequenceBytes.Concat(new byte[NumberOfBytes]);
var result = sequenceBytesLongEnough.Take(NumberOfBytes).Reverse();
return result;
}
private IEnumerable<byte> GetGuidBytes()
{
var result = Guid.NewGuid().ToByteArray().Take(10).ToArray();
return result;
}
}
[Serializable]
public struct SequentialGuid : IComparable<SequentialGuid>, IComparable<Guid>, IComparable
{
private const int NumberOfSequenceBytes = 6;
private const int PermutationsOfAByte = 256;
private static readonly long MaximumPermutations =
(long)Math.Pow(PermutationsOfAByte, NumberOfSequenceBytes);
private static long _lastSequence;
private static readonly DateTime SequencePeriodStart =
new DateTime(2011, 11, 15, 0, 0, 0, DateTimeKind.Utc); // Start = 000000
private static readonly DateTime SequencePeriodeEnd =
new DateTime(2100, 1, 1, 0, 0, 0, DateTimeKind.Utc); // End = FFFFFF
private readonly Guid _guidValue;
public SequentialGuid(Guid guidValue)
{
_guidValue = guidValue;
}
public SequentialGuid(string guidValue)
: this(new Guid(guidValue))
{
}
[System.Security.SecuritySafeCritical]
public static SequentialGuid NewSequentialGuid()
{
// You might want to inject DateTime.Now in production code
return new SequentialGuid(GetGuidValue(DateTime.Now));
}
public static TimeSpan TimePerSequence
{
get
{
var ticksPerSequence = TotalPeriod.Ticks / MaximumPermutations;
var result = new TimeSpan(ticksPerSequence);
return result;
}
}
public static TimeSpan TotalPeriod
{
get
{
var result = SequencePeriodeEnd - SequencePeriodStart;
return result;
}
}
#region FromDateTimeToGuid
// Internal for testing
internal static Guid GetGuidValue(DateTime now)
{
if (now < SequencePeriodStart || now >= SequencePeriodeEnd)
{
return Guid.NewGuid(); // Outside the range, use regular Guid
}
var sequence = GetCurrentSequence(now);
return GetGuid(sequence);
}
private static long GetCurrentSequence(DateTime now)
{
var ticksUntilNow = now.Ticks - SequencePeriodStart.Ticks;
var factor = (decimal)ticksUntilNow / TotalPeriod.Ticks;
var resultDecimal = factor * MaximumPermutations;
var resultLong = (long)resultDecimal;
return resultLong;
}
private static readonly object SynchronizationObject = new object();
private static Guid GetGuid(long sequence)
{
lock (SynchronizationObject)
{
if (sequence <= _lastSequence)
{
// Prevent double sequence on same server
sequence = _lastSequence + 1;
}
_lastSequence = sequence;
}
var sequenceBytes = GetSequenceBytes(sequence);
var guidBytes = GetGuidBytes();
var totalBytes = guidBytes.Concat(sequenceBytes).ToArray();
var result = new Guid(totalBytes);
return result;
}
private static IEnumerable<byte> GetSequenceBytes(long sequence)
{
var sequenceBytes = BitConverter.GetBytes(sequence);
var sequenceBytesLongEnough = sequenceBytes.Concat(new byte[NumberOfSequenceBytes]);
var result = sequenceBytesLongEnough.Take(NumberOfSequenceBytes).Reverse();
return result;
}
private static IEnumerable<byte> GetGuidBytes()
{
return Guid.NewGuid().ToByteArray().Take(10);
}
#endregion
#region FromGuidToDateTime
public DateTime CreatedDateTime
{
get
{
return GetCreatedDateTime(_guidValue);
}
}
internal static DateTime GetCreatedDateTime(Guid value)
{
var sequenceBytes = GetSequenceLongBytes(value).ToArray();
var sequenceLong = BitConverter.ToInt64(sequenceBytes, 0);
var sequenceDecimal = (decimal)sequenceLong;
var factor = sequenceDecimal / MaximumPermutations;
var ticksUntilNow = factor * TotalPeriod.Ticks;
var nowTicksDecimal = ticksUntilNow + SequencePeriodStart.Ticks;
var nowTicks = (long)nowTicksDecimal;
var result = new DateTime(nowTicks);
return result;
}
private static IEnumerable<byte> GetSequenceLongBytes(Guid value)
{
const int numberOfBytesOfLong = 8;
var sequenceBytes = value.ToByteArray().Skip(10).Reverse().ToArray();
var additionalBytesCount = numberOfBytesOfLong - sequenceBytes.Length;
return sequenceBytes.Concat(new byte[additionalBytesCount]);
}
#endregion
#region Relational Operators
public static bool operator <(SequentialGuid value1, SequentialGuid value2)
{
return value1.CompareTo(value2) < 0;
}
public static bool operator >(SequentialGuid value1, SequentialGuid value2)
{
return value1.CompareTo(value2) > 0;
}
public static bool operator <(Guid value1, SequentialGuid value2)
{
return value1.CompareTo(value2) < 0;
}
public static bool operator >(Guid value1, SequentialGuid value2)
{
return value1.CompareTo(value2) > 0;
}
public static bool operator <(SequentialGuid value1, Guid value2)
{
return value1.CompareTo(value2) < 0;
}
public static bool operator >(SequentialGuid value1, Guid value2)
{
return value1.CompareTo(value2) > 0;
}
public static bool operator <=(SequentialGuid value1, SequentialGuid value2)
{
return value1.CompareTo(value2) <= 0;
}
public static bool operator >=(SequentialGuid value1, SequentialGuid value2)
{
return value1.CompareTo(value2) >= 0;
}
public static bool operator <=(Guid value1, SequentialGuid value2)
{
return value1.CompareTo(value2) <= 0;
}
public static bool operator >=(Guid value1, SequentialGuid value2)
{
return value1.CompareTo(value2) >= 0;
}
public static bool operator <=(SequentialGuid value1, Guid value2)
{
return value1.CompareTo(value2) <= 0;
}
public static bool operator >=(SequentialGuid value1, Guid value2)
{
return value1.CompareTo(value2) >= 0;
}
#endregion
#region Equality Operators
public static bool operator ==(SequentialGuid value1, SequentialGuid value2)
{
return value1.CompareTo(value2) == 0;
}
public static bool operator !=(SequentialGuid value1, SequentialGuid value2)
{
return !(value1 == value2);
}
public static bool operator ==(Guid value1, SequentialGuid value2)
{
return value1.CompareTo(value2) == 0;
}
public static bool operator !=(Guid value1, SequentialGuid value2)
{
return !(value1 == value2);
}
public static bool operator ==(SequentialGuid value1, Guid value2)
{
return value1.CompareTo(value2) == 0;
}
public static bool operator !=(SequentialGuid value1, Guid value2)
{
return !(value1 == value2);
}
#endregion
#region CompareTo
public int CompareTo(object obj)
{
if (obj is SequentialGuid)
{
return CompareTo((SequentialGuid)obj);
}
if (obj is Guid)
{
return CompareTo((Guid)obj);
}
throw new ArgumentException("Parameter is not of the rigt type");
}
public int CompareTo(SequentialGuid other)
{
return CompareTo(other._guidValue);
}
public int CompareTo(Guid other)
{
return CompareImplementation(_guidValue, other);
}
private static readonly int[] IndexOrderingHighLow =
{ 10, 11, 12, 13, 14, 15, 8, 9, 7, 6, 5, 4, 3, 2, 1, 0 };
private static int CompareImplementation(Guid left, Guid right)
{
var leftBytes = left.ToByteArray();
var rightBytes = right.ToByteArray();
return IndexOrderingHighLow.Select(i => leftBytes[i].CompareTo(rightBytes[i]))
.FirstOrDefault(r => r != 0);
}
#endregion
#region Equals
public override bool Equals(Object obj)
{
if (obj is SequentialGuid || obj is Guid)
{
return CompareTo(obj) == 0;
}
return false;
}
public bool Equals(SequentialGuid other)
{
return CompareTo(other) == 0;
}
public bool Equals(Guid other)
{
return CompareTo(other) == 0;
}
public override int GetHashCode()
{
return _guidValue.GetHashCode();
}
#endregion
#region Conversion operators
public static implicit operator Guid(SequentialGuid value)
{
return value._guidValue;
}
public static explicit operator SequentialGuid(Guid value)
{
return new SequentialGuid(value);
}
#endregion
#region ToString
public override string ToString()
{
var roundedCreatedDateTime = Round(CreatedDateTime, TimeSpan.FromMilliseconds(1));
return string.Format("{0} ({1:yyyy-MM-dd HH:mm:ss.fff})",
_guidValue, roundedCreatedDateTime);
}
private static DateTime Round(DateTime dateTime, TimeSpan interval)
{
var halfIntervalTicks = (interval.Ticks + 1) >> 1;
return dateTime.AddTicks(halfIntervalTicks -
((dateTime.Ticks + halfIntervalTicks) % interval.Ticks));
}
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment