Using value types and aliases for IDs

I really like creating custom value types for encapsulating behavior and additional type safety. Recently, I started adding strongly-typed entity IDs. However, the method I took is slightly different from that other blog post. I figured most of the IDs are going to be the same, only the associated entity type is different. For example, OrderId and UserId, the only difference should the associated Order or User type. Additionally, it’s useful if we can create IDs in generic methods, such as GetNewId<TEntity>().

Strong ID type

What I ended up doing, I define one generic type, let’s call it Id<TEntity>, where TEntity is the entity type, for example Order. Then I can use Id<Order> and Id<User> everywhere. Id<Order> is different from Id<User>, so they are not interchangeable, which adds type safety.

public readonly record struct Id<TEntity>(int Value)
{
    public static readonly Id<TEntity> Empty = new(0);
}

Type aliases

The main problem with this is, writing the generic arguments everywhere is just ugly and extra work. I wanted to define an alias for each ID type. For example, UserId = Id<User>. This is shorter (no need for the <>) and less ugly.

C# has had type aliases as “global usings” for a while. Problem is, the global usings aren’t really global. They’re specific to one project. So I can define these type aliases UserId = Id<User> as global usings, but I have to do this for every project. Not good.

global using OrderId = EntityId.Id<EntityId.Entities.Order>;
global using UserId = EntityId.Id<EntityId.Entities.User>;
global using TestEntityId = EntityId.Id<EntityId.Entities.TestEntity>;

Solutions

I came up with two solutions:

What I ended up doing was a combination of those. I use Roslyn code generator to generate IDs for every entity type. The code generator scans assembly for entity types implementing IEntity, and then generates global usings for the entity IDs.

Then I use project file links to reference the generated global usings files.

  <ItemGroup>
    <Compile Include="..\EntityId\Usings.cs" Link="Usings.cs" />
  </ItemGroup>

The Entity ID type and the generator are open source.

Leave a Reply