Make Strongly-Typed-Id of DDD easier in Entity Framework Core 7

Zack Yang
4 min readNov 24, 2022

In Domain-Driven-Deisgn (DDD), Strongly-typed-id is an important concept. Developers can benefit from using strongly-typed-id for the identity of an entity type. For example, the following is a method for removing a user by a specified id:

void RemoveById(long id);

We cannot tell what ‘id’ refers to. If we pass a product id to it, there will be no error in compilation time. Using generic types such as long to represent identity attributes weakens the business meaning of parameters.

If we define a type named UserId as follows:

class UserId

{

public long Value{get;init;}

public UserId(long value)

{

this.Value=value;

}

}

Then, we can define the Id property of User as UserId as follows:

class User

{

public UserId Id{get;}

public string Name{get;set;}

}

Then, the parameter type of RemoveById can be changed to UserId as follows:

void RemoveById(UserId id);

Be doing so, the meaning of parameter id can be implied from the data type, and it can also avoid ‘passing a productid to the parameter userId’.

In .NET, it is difficult to use strongly-typed-id in Entity Framework Core (EF Core). Since .NET 7, EF Core has built-in support of strongly-typed-id. Please check the “Value generation for DDD guarded types” of EF Core’s documentation for details.

Although EF Core has built-in support for strongly-typed-id, it requires programmers to write a lot of code. For example, the developer has to write the following 30 lines of code to implement an Strongly-typed-id class:

public readonly struct PersonId

{

public Guid Value { get; }

public PersonId(Guid value)

{

Value = value;

}

public override string ToString()

{

return Convert.ToString(Value);

}

public override int GetHashCode()

{

return Value.GetHashCode();

}

public override bool Equals(object obj)

{

if (obj is PersonId)

{

PersonId objId = (PersonId)obj;

return Value == objId.Value;

}

return base.Equals(obj);

}

public static bool operator ==(PersonId c1, PersonId c2)

{

return c1.Equals(c2);

}

public static bool operator !=(PersonId c1, PersonId c2)

{

return !c1.Equals(c2);

}

}

Additionally, a ValueConverter class and a custom ValueGenerator are also needed. The complexity of the code that needs to be written is prohibitive for developers who want to use strongly typed ids.

To solve this problem, based on SourceGenerator technology of .NET, I wrote an open source project that automatically generates the relevant code at compile time by simply tagging the entity class with a [HasStronglyTypedId].

GitHub repository: https://github.com/yangzhongke/LessCode.EFCore.StronglyTypedId

Here is an example of writing all the code in a console project to demonstrate its use. For more complex uses such as multi-project layering, see the project documentation and the Examples folder.

Note: The usage of this project may change with the upgrade, please refer to the latest official documentation.

Usage:

1、 Create a .NET 7 console application, and install the following Nuget packages to it: LessCode.EFCore, LessCode.EFCore.StronglyTypedIdCommons, LessCode.EFCore.StronglyTypedIdGenerator. Our project will use SQLServer and migration, so we will also install the following Nuget package: Microsoft.EntityFrameworkCore.SqlServer, Microsoft.EntityFrameworkCore.Tools.

2、 Create an entity type named Person:

[HasStronglyTypedId]

class Person

{

public PersonId Id { get; set; }

public string Name { get; set; }

}

It is noticeable that the [HasStronglyTypedId(typeof(Guid))] attribute on the Person, which represents the class with strongly-typed Id enabled. A class named PersonId will be automatically generated at compile time, So we declare an attribute named Id of type PersonId to represent the identity of the entity.

PersonId is stored in the database as a long by default, and if you want to save it as a Guid, you can change it to [HasStronglyTypedId(typeof(Guid))].

Let’s compile the project, and if it succeeds, we decompile the generated dll file and see that the PersonId and PersonIdValueConverter classes are automatically generated in the dll.

3、 Write DbContext as follows:

using LessCode.EFCore;

class TestDbContext:DbContext

{

public DbSet<Person> Persons { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

{

optionsBuilder.UseSqlServer(your_connectionString);

}

protected override void OnModelCreating(ModelBuilder modelBuilder)

{

base.OnModelCreating(modelBuilder);

modelBuilder.ConfigureStronglyTypedId();

}

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)

{

base.ConfigureConventions(configurationBuilder);

configurationBuilder.ConfigureStronglyTypedIdConventions(this);

}

}

4、 Next, let’s do the database migration and other operations, which are the standard operations of EF Core, I will not introduce them here.

5、 Write the following code to test:

using TestDbContext ctx = new TestDbContext();

Person p1 = new Person();

p1.Name = “yzk”;

ctx.Persons.Add(p1);

ctx.SaveChanges();

PersonId pId1 = p1.Id;

Console.WriteLine(pId1);

Person? p2 = FindById(new PersonId(1));

Console.WriteLine(p2.Name);

Person? FindById(PersonId pid)

{

using TestDbContext ctx = new TestDbContext();

return ctx.Persons.SingleOrDefault(p => p.Id == pid);

}

Strong-typed-id enables us to better implement DDD in EF Core. My open-source project allows developers to use strong-typed-id simply by putting an [HasStronglyTypedId] on the entity class. Hope it helps, and feel free to share it with your tech community.

--

--