Thursday, May 13, 2010

Generating NewSequentialID compatible Sequential Guids in C#

Recently I've needed to generate sequential guids that out of the scope of the .NET base class library and come up with the way suggested all over the web - interop Windows API function from rpcrt4.dll called UuidCreateSequential.

[SuppressUnmanagedCodeSecurity]
[DllImport( "rpcrt4.dll", SetLastError = true )]
private static extern int UuidCreateSequential( out Guid value );

The function has return codes and to handle them we need something like an Enum like that:

private enum RpcUuidCodes : int
{
    RPC_S_OK = 0,
    RPC_S_UUID_LOCAL_ONLY = 1824,
    RPC_S_UUID_NO_ADDRESS = 1739
}

There are two values indicating specific cases like RPC_S_UUID_NO_ADDRESS that means the network card isn't installed on a computer and it's better to handle them to be notified of this.

Guid sequentialGuid;

int resultCode = UuidCreateSequential( out sequentialGuid );

switch( resultCode )
{
    case (int) RpcUuidCodes.RPC_S_OK:
        // all ok
        break;
    case (int) RpcUuidCodes.RPC_S_UUID_LOCAL_ONLY:
        throw new Exception( @"SequentialGuid:NewGuid failed - UuidCreateSequential returned RPC_S_UUID_LOCAL_ONLY" );
    case (int) RpcUuidCodes.RPC_S_UUID_NO_ADDRESS:
        throw new Exception( @"SequentialGuid:NewGuid failed - UuidCreateSequential returned RPC_S_UUID_NO_ADDRESS" );
    default:
        throw new Exception( String.Format( @"SequentialGuid:NewGuid failed - UuidCreateSequential returned {0}", resultCode ) );
}

It makes us able to generate Guids that already seem to be not very unique:

38ae1879-5ea9-11df-b491-001bfcb38e8d
38ae187a-5ea9-11df-b491-001bfcb38e8d
38ae187b-5ea9-11df-b491-001bfcb38e8d


Frankly the reason I need them is generating sequential guids compatible with SQL Server NewSequentialID function but it turned out it wasn't the end. When I compared guids generated by SQL Server to produced by me the result had me to start googling again. The default constraint in a simple table generates different sequences.

CREATE TABLE CustomerNames
(
 Uuid uniqueidentifier NOT NULL DEFAULT NEWSEQUENTIALID(),
 Name nvarchar(250) NOT NULL
);

8c31f017-ac5e-df11-b491-001bfcb38e8d
8d31f017-ac5e-df11-b491-001bfcb38e8d

They differ in 2nd and 3rd series of bytes. SQL Server uses the same UuidCreateSequential function to generate sequences but it scrambles bytes in the middle. So we need to scramble them back the same way.

public static Guid ConvertToSqlCompatible( Guid guid )
{
    byte[] guidBytes = guid.ToByteArray();
    Array.Reverse( guidBytes, 0, 4 );
    Array.Reverse( guidBytes, 4, 2 );
    Array.Reverse( guidBytes, 6, 2 );

    return new Guid( guidBytes );
}

Having this we can produce sequential guids the same way SQL server does.

public static Guid NewSqlCompatibleGuid()
{
    Guid sequentialGuid = SequentialGuid.NewGuid();

    return ConvertToSqlCompatible( sequentialGuid );
}

Finally I've ended up with SequentialGuid structure as below:


Source code can be downloaded at MSDN Code Gallery.

2 comments:

  1. This is nice indeed.

    ReplyDelete
  2. I saw what you did there. Good gracious that is some technique for the C# interpreter to interpret very slowly. This be the worst case use for C# language when it have big CPU load factor of arithmetic random and slip sideways encoding.

    ReplyDelete