Jeg havde aldrig gættet at min første blog post ville omhandle noget der har med SQL Server at gøre, men – der kan man bare se…
I det sidste er jeg blevet mere varm på idéen med at bruge GUIDs som PK i SQL Server. Den største ulempe er nok at ens index begynder at performe dårligt når man bruger almindelige, tilfældige GUIDs (NEWID eller Guid.NewGuid ).
Som en løsning på problemet kom Microsoft op med NEWSEQUENTIALID i SQL Server 2005. Denne funktion (som kun kan bruges ifm. en kolonnes DEFAULT værdi) returnerer altid den næste GUID i rækkefølge, uanset hvilken tabel Guid’en skal bruges i.
Det er jo genialt! Men hvad hvis man gerne vil populere ID-kolonnen selv, og ikke gider hente den fra databasen efter at rækken er blevet tilføjet? Artiklen her dokumenterer at NEWSEQUENTIALID bruger en Windows API metode ved navn UuidCreateSequential - og den kan vi bruge fra C#!
Efter at have googlet lidt rundt lavede jeg først denne metode:
public static class SequentialGuid
{
[DllImport("rpcrt4.dll", SetLastError = true)]
private static extern int UuidCreateSequential(out Guid guid);
public static Guid NewGuid()
{
Guid newGuid;
int returnVal = UuidCreateSequential(out newGuid);
if (returnVal != 0)
throw new Exception("UuidCreateSequential failed with exit code " + returnVal);
return newGuid;
}
}
Da jeg kørte en test der sammenlignede de GUIDs som min metode genererer og dem som SQL Server genererer fandt jeg ud af at de er formateret lidt forskelligt. Det var da irriterende:
SQL Server:
E4DF655E-A03F-DE11-9DE7-001FF3ABBCAF
E5DF655E-A03F-DE11-9DE7-001FF3ABBCAF
SequentialGuid.NewGuid():
5E665517-3FA0-11DE-9DE7-001FF3ABBCAF
5E665518-3FA0-11DE-9DE7-001FF3ABBCAF
Jeg har fremhævet de bytes der “tæller opad”. Der sker åbenbart det, at SQL Server konverterer de tre første byte-gruppers endianness for bedre at kunne sortere. Derfor lavede jeg en ny metode som returnerer GUIDs i SQL Server formatet:
public static Guid NewSqlServerFormattedGuid()
{
var bytes = NewGuid().ToByteArray();
CopyBigEndianBytes(BitConverter.ToInt32(bytes, 0), 4, bytes, 0);
CopyBigEndianBytes(BitConverter.ToInt16(bytes, 4), 2, bytes, 4);
CopyBigEndianBytes(BitConverter.ToInt16(bytes, 6), 2, bytes, 6);
return new Guid(bytes);
}
// Tak til Jon Skeet http://www.yoda.arachsys.com/csharp/miscutil
private static void CopyBigEndianBytes(long value, int bytes, byte[] buffer, int index)
{
int endOffset = index + bytes - 1;
for (int i = 0; i < bytes; i++)
{
buffer[endOffset - i] = unchecked((byte)(value & 0xff));
value = value >> 8;
}
}
Jeg fik inspiration fra artiklen her, som fremlægger en lidt anden løsning, men som jeg synes ikke var så elegant.
Der er også andre sjove finurligheder med Sequential GUIDs – bl.a. at man kan se computerens MAC adresse i de sidste 6 bytes. Så hvis du laver systemer til PET er det måske bedre at bruge noget andet.