RefinId - fast and compact 8-byte substitution of GUID for your identifiers with built-in entity types support. You can use it for your Microsoft.NET data access code or something else.
For my .NET projects I wanted to use something faster than GUID with ability to determine entity type from identifier itself. I considered sharding and lazy writing support as a nice addition.
Sometimes you may consider using GUID for your identifiers (regardless concrete database). But wait, do you know about performance degradation as a result of this choice? Let me show you problems with GUID as a primary key, especially when key is clustered index, for Microsoft SQL Server comparing to "long" 8-byte identifier.
- GUID is takes 16 byte. Use 16 bytes or 8 bytes – not such a big difference, huh? Remember, that primary key will be used in all child tables and clustered key – in all non-clustered indexes. Not a big deal when tables small enough, but for big tables… it depends.
- If you generate GUID in code or with NEWID() SQL function you get random keys and, as a result, frequent page splitting and index fragmentation.
You can find more thorough explanation here.
TBD (building, NuGet)
For simple situation (default implemenations) you can use something like this:
var defaultHelper = new DefaultHelper(connnString, "System.Data.SqlClient");
defaultHelper.Install(0, 0, false, new Table(1, "Test1"), new Table(2, "TestId2"));
...
short type = 1;
long id = defaultHelper.GetProvider().Create(type);
I decide that for my goals 8-byte structure with LayoutKind.Explicit will be good enough to hold Value, Type and Shard fields. Also I reserved single byte for future use (e.g. for extending one of other fields). Then, I wrote implicit operators to convert to and from long (and trivial Equals and GetHashCode implementation). You can take a look on code from unit-test to see how to use it:
LongId id = 0x1FEECCBB44332211;
Assert.That(id.Value, Is.EqualTo(0x44332211));
Assert.That(id.Type, Is.EqualTo(0x1FEE));
Assert.That(id.Shard, Is.EqualTo(0xCC));
Assert.That(id.Reserved, Is.EqualTo(0xBB));
DefaultLongIdProvider provides Create method to retrieve new identifier for specified entity type. This factory is thread-safe.
Two things are not trivial, let me explain it:
- It uses simple Dictionary, because all writing to dictionary performed inside the constructor.
- It uses private IdWrapper class. If we use long without wrapper, we cannot use ref for Interlocked.Increment(ref long).
DefaultLongIdInstaller implements basic code to install LongId support into your database. This is necessary, because your database is a good place to store last inserted identifiers.
If your database does compatible with some features used, you can write your own installer. I am using types VARCHAR and BIGINT and SMALLINT.
License: MIT.
The source code depends on following NuGet packages:
- Moq (only for RefinId.Specs)
- NUnit (only for RefinId.Specs)
- System.Data.SQLite.Core (only for RefinId.Specs)