Strongly typed ID
Entities are usually integer, GUID or string types, because the database directly supports these types. However, if the entity's ID types are the same, such as the IDs of integers, there may be an error in the ID value transmission. See the example below.
public void AddProductToOrder(int orderId, int productId, int count) { ... } ... // This is the parameter passed incorrectlyAddProductToOrder(productId, orderId, int count);
The above code can be checked and compiled very well, but there is a problem when it runs, which is a logical bug.
Fortunately, strongly typed ids can be defined to solve this problem. The idea is simple, declare a specific type for each entity's ID, and now it needs to be written like this:
// Use strongly typed ID instead of integer IDpublic void AddProductToOrder(OrderId orderId, ProductId productId, int count) { ... } ... // This is the parameter passed incorrectlyAddProductToOrder(productId, orderId, int count);
In the above code we made the same error as the first example (swap productId and orderId), but in this case the type is different, so the compiler catches the error and reports it, we still need to fix it, but at least it doesn't explode in production.
Write a strongly typed id
public readonly struct ProductId : IEquatable<ProductId> { public ProductId(int value) { Value = value; } public int Value { get; } public bool Equals(ProductId other) => == Value; public override bool Equals(object obj) => obj is ProductId other && Equals(other); public override int GetHashCode() => (); public override string ToString() => $"ProductId {Value}"; public static bool operator ==(ProductId a, ProductId b) => (b); public static bool operator !=(ProductId a, ProductId b) => !(b); }
The above code is not difficult, but it is indeed a bit troublesome if every entity needs it. In C# 9, source generators can be used to accomplish this, but C# 9 also introduces another feature, which is more convenient to use.
Record Type
The Record type is a reference type with built-in invariance and value semantics. It is the same as the strong type we wrote above (manually written members implement Equals, GetHashCode, etc.), and is also very concise in the code. If we use Record to rewrite the type, it is like this:
public record ProductId(int Value);
Yes, you read that right, this is a line, and the above code is a big paragraph that does everything we do manually (actually, a lot more!).
The main difference is: our manual implementation is struct, i.e. value types, but records are reference types, which means they can be null, which may not be the main problem, especially when using nullable reference types, but be aware of this.
Is it easy to write a strongly typed id for each entity in the model now, it is very convenient to use Record, of course, there are other issues to consider, such as JSON serialization, working with Entity Framework Core, etc., but this is the story of another post!
Summarize
This is the end of this article about using records as strong typed ID in C#9. For more related content on C#9 records as strong typed ID, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!
Original author: thomas levelque
Original link: /2020/10/30/using-csharp-9-records-as-strongly-typed-ids/