Strongly Typed Entity ID¶
Introduction¶
In domain-driven design modeling, the entity ID is very important. It is the unique identifier of an entity, the primary key of the entity, and an important attribute of the entity. Usually, we use types such as int
, long
, Guid
, and string
to define entity IDs. However, using these types in the system can lead to the following problems:
- When defining an ID value, it is impossible to determine from the type whether it represents an entity ID or another type, which reduces code readability.
- When assigning a value to an ID field referenced by an entity, it is easy to mistakenly assign a non-expected value of another type to the ID field, leading to errors.
To solve the above problems, we recommend using strongly typed entity IDs
. Strongly typed entity IDs are encapsulations based on basic types. The currently supported basic types are:
- Int32
- Int64
- Guid
- String
How to Use¶
Strongly typed IDs need to be modified with the keywords public
, partial
, and record
, and need to implement one of the interfaces IInt32StronglyTypedId
, IInt64StronglyTypedId
, IGuidStronglyTypedId
, or IStringStronglyTypedId
.
-
Add a reference to the
NetCorePal.Extensions.Domain.Abstractions
package.dotnet add package NetCorePal.Extensions.Domain.Abstractions
-
You can write code similar to the following to implement a strongly typed entity ID:
using NetCorePal.Extensions.Domain; namespace YourNamespace; public partial record Int64OrderId : IInt64StronglyTypedId;
The following code is automatically generated by
SourceGenerator
:using NetCorePal.Extensions.Domain; using System; using System.ComponentModel; namespace YourNamespace; [TypeConverter(typeof(EntityIdTypeConverter<Int64OrderId, Int64>))] public partial record Int64OrderId(Int64 Id) : IInt64StronglyTypedId { public static implicit operator Int64(Int64OrderId id) => id.Id; public static implicit operator Int64OrderId(Int64 id) => new Int64OrderId(id); public override string ToString() { return Id.ToString(); } }
More examples:
using NetCorePal.Extensions.Domain; namespace YourNamespace; // Int32 strongly typed entity ID public partial record Int32OrderId : IInt32StronglyTypedId; // Guid strongly typed entity ID public partial record GuidOrderId : IGuidStronglyTypedId; // String strongly typed entity ID public partial record StringOrderId : IStringStronglyTypedId;
Json Serialization Support¶
In scenarios such as inter-service calls and WebAPI, it usually involves serialization and deserialization between entity types and JSON strings. To ensure that strongly typed entity IDs work properly in these scenarios, we provide serialization support based on System.Text.Json
and Newtonsoft.Json
.
-
For
System.Text.Json
, you can use the following code to implement:using NetCorePal.Extensions.Domain.Json; var builder = WebApplication.CreateBuilder(args); builder.Services.AddMvc().AddControllersAsServices().AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new EntityIdJsonConverterFactory()); });
-
For
Newtonsoft.Json
, you can use the following code to implement:Add the
NetCorePal.Extensions.AspNetCore
packagedotnet add package NetCorePal.Extensions.AspNetCore
Add the following code to the
Startup.cs
file:builder.Services.AddControllers().AddNewtonsoftJson(options => { options.SerializerSettings.Converters.Add(new NewtonsoftEntityIdJsonConverter()); });
The following example shows the effect of serialization and deserialization:
JsonSerializerOptions options = new(); options.Converters.Add(new EntityIdJsonConverterFactory()); var id = JsonSerializer.Deserialize<OrderId1>("\"12\"", options); Assert.NotNull(id); Assert.True(id.Id == 12); var id2 = new OrderId2(2); var json = JsonSerializer.Serialize(id2, options); Assert.Equal("\"2\"", json);
Note: Strongly typed entity IDs will be serialized as strings.