Strongly-typed ID update 0.2.1: Using strongly-typed entity IDs to avoid primitive obsession - Part 6
Tags: aspnetcore
Published: 2020-06-14 18:03:37

---
title: Strongly-typed ID update 0.2.1: Using strongly-typed entity IDs to avoid primitive obsession - Part 6
published: true
date: 2020-05-26 09:00:00 UTC
tags: ASPNETCore
canonical_url: https://andrewlock.net/strongly-typed-id-updates/
---

 ![Strongly-typed ID update 0.2.1](https://andrewlock.net/content/images/2019/obsession5.png)

Last year I [wrote a series about using strongly typed IDs](https://andrewlock.net/series/using-strongly-typed-entity-ids-to-avoid-primitive-obsession/) to avoid a whole class of bugs in C# applications. In this post I describe some recent updates to [a NuGet package that drastically reduces the amount of boilerplate you have to write](https://github.com/andrewlock/StronglyTypedId) by auto-generating it for you at compile-time.

## Background

If you don't know what strongly typed IDs are about, I suggest reading the previous posts in [this series](https://andrewlock.net/series/using-strongly-typed-entity-ids-to-avoid-primitive-obsession/). In summary, strongly-typed IDs help avoid bugs introduced by using primitive types for entity identifiers. For example, imagine you have [a method signature like the following](https://andrewlock.net/using-strongly-typed-entity-ids-to-avoid-primitive-obsession-part-1/#an-example-of-the-problem):

```csharp
public Order GetOrderForUser(Guid orderId, Guid userId);
```

Can you spot the bug in the method call?

```csharp
public Order GetOrder(Guid orderId, Guid userId)
{
    return _service.GetOrderForUser(userId, orderId);
}
```

The call above accidentally inverts the order of `orderId` and `userId` when calling the method. Unfortunately, the type system doesn't help us here because both IDs are using the same type, `Guid`.

Strongly Typed IDs allow you to avoid these types of bugs entirely, by using different types for the entity IDs, and using the type system to best effect. This is something that's easy to achieve in some languages (e.g. F#), but is a bit of a mess in C# ([at least until we get record types in C# 9](https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/)!):

```csharp
public readonly struct OrderId : IComparable<OrderId>, IEquatable<OrderId>
{
    public Guid Value { get; }

    public OrderId(Guid value)
    {
        Value = value;
    }

    public static OrderId New() => new OrderId(Guid.NewGuid());

    public bool Equals(OrderId other) => this.Value.Equals(other.Value);
    public int CompareTo(OrderId other) => Value.CompareTo(other.Value);

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        return obj is OrderId other && Equals(other);
    }

    public override int GetHashCode() => Value.GetHashCode();
    public override string ToString() => Value.ToString();

    public static bool operator ==(OrderId a, OrderId b) => a.CompareTo(b) == 0;
    public static bool operator !=(OrderId a, OrderId b) => !(a == b);
}
```

The [StronglyTypedId NuGet package](https://github.com/andrewlock/StronglyTypedId) massively simplifies the amount of code you need to write to the following:

```csharp
[StronglyTypedId]
public partial struct OrderId { }
```

On top of that, the StronglyTypedId package uses Roslyn to auto generate the additional code whenever you save a file. No need for snippets, full IntelliSense, but all the benefits of strongly-typed IDs!

<picture>
    <source srcset="/content/images/2019/strongly_typed_id.mp4" type="video/mp4">
    <img src="https://andrewlock.net/content/images/2019/strongly_typed_id.gif" alt="Generating a strongly-typed ID using the StronglyTypedId packages">
</source></picture>

So that's the background, now lets look at some of the updates

## Recent updates

These updates are primarily courtesy of [Bartłomiej Oryszak](https://github.com/vebbo2) who did great work! There are primarily three updates:

- Update to the latest version of [_CodeGeneration.Roslyn_](https://github.com/AArnott/CodeGeneration.Roslyn) to support for .NET Core 3.x
- Support creating JSON converters for System.Text.Json
- Support for using long as a backing type for the strongly typed ID

### Support for .NET Core 3.x

_StronglyTypedId_ has now been updated to the latest version of [_CodeGeneration.Roslyn_](https://github.com/AArnott/CodeGeneration.Roslyn) to support for .NET Core 3.x. This brings updates to the Roslyn build tooling, which makes the library much easier to consume. You can add a single `<PackageReference>` in your project.

```xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="StronglyTypedId" Version="0.2.1" PrivateAssets="all"/>
  </ItemGroup>
</Project>
```

> Setting `PrivateAssets=all` prevents the _CodeGeneration.Roslyn.Attributes_ and _StronglyTypedId.Attributes_ from being published to the output. There's no harm in them being there, but they're only used at compile time!

With the package added, you can now add the `[StronglyTypedId]` to your IDs:

```csharp
[StronglyTypedId(generateJsonConverter: false)]
public partial struct OrderId {}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Value = " + new OrderId().Value);
    }
}
```

This will generate a `Guid`-backed ID, with a `TypeConverter`, without any JSON converters. If you _do_ want explicit JSON converters, you have another option—_System.Text.Json_ converters.

### Support for System.Text.Json converters

_StronglyTypedId_ has always supported the _Newtonsoft.Json_ `JsonConverter` but now you have another option, _System.Text.Json_. You can generate this converter by passing an appropriate `StronglyTypedIdJsonConverter` value:

```csharp
[StronglyTypedId(jsonConverter: StronglyTypedIdJsonConverter.SystemTextJson)]
public partial struct OrderId {}
```

This generates a converter similar to the following:

```csharp
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

[JsonConverter(typeof(OrderIdSystemTextJsonConverter))]
readonly partial struct OrderId : IComparable<OrderId>, IEquatable<OrderId>
{
    // other implementation
    public class OrderIdSystemTextJsonConverter : JsonConverter<OrderId>
    {
        public override OrderId Read(ref Utf8JsonReader reader, System.Type typeToConvert, JsonSerializerOptions options)
        {
            return new OrderId(System.Guid.Parse(reader.GetString()));
        }

        public override void Write(Utf8JsonWriter writer, OrderId value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value.Value);
        }
    }
}
```

If you want to generate both a _System.Text.Json_ converter and a _Newtonsoft.Json_ converter, you can use flags:

```csharp
[StronglyTypedId(jsonConverter: StronglyTypedIdJsonConverter.SystemTextJson | StronglyTypedIdJsonConverter.NewtonsoftJson)
public partial struct OrderId {}
```

> Remember, if you generate a Newtonsoft.Json converter, you'll need to add a reference to the project file.

### Support for long as a backing type

The final update is adding support for using `long` as the backing field for your strongly typed IDs. To use `long`, use the `StronglyTypedIdBackingType.Long` option:

```csharp
[StronglyTypedId(backingType: StronglyTypedIdBackingType.Long)]
public partial struct OrderId {}
```

The currently supported backing fields are:

- `Guid`
- `int`
- `long`
- `string`

## Future work

C# 9 is bringing some interesting features, most notably [source generators](https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/) and [record types](https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/). Both of these features have the potential to impact the StronglyTypedId package in different ways.

Source generators are designed for exactly the sort of functionality StronglyTypedId provides - build time enhancement of existing types. From a usage point of view, as far as I can tell, converting to using source generators would provide essentially the exact same experience as you can get now with [_CodeGeneration.Roslyn_](https://github.com/AArnott/CodeGeneration.Roslyn). For that reason, it doesn't really seem worth the effort looking into at this point, unless I've missed something!

Record types on the other hand are much more interesting. Records provide _exactly_ the experience we're looking for here! With the exception of the built-in `TypeConverter` and `JsonConverter`s, records seem like they would give an overall better experience out of the box. So when C#9 drops, I think this library can probably be safely retired 🙂

## Summary

In this post I described some recent enhancements to the [_StronglyTypedId_](https://github.com/andrewlock/StronglyTypedId) NuGet package, which lets you generate strongly-typed IDs at compile time. The updates simplify using the _StronglyTypedId_ package in your app by supporting .NET Core 3.x, added support for `System.Text.Json` as a `JsonConverter`, and using `long` as a backing field. If you have any issues using the package, let me know [in the issues on GitHub](https://github.com/andrewlock/StronglyTypedId), or in the comments below.