dotnet add package ValueTypeGenerators
[ValueType(Type = TypeOfValue.Int32)]
public readonly partial struct Int32Id;
[ValueType(Type = TypeOfValue.Int32)]
public readonly partial struct PersonId;
[ValueType(Type = TypeOfValue.Int32)]
public readonly partial struct StudentId;
[ValueType(Type = TypeOfValue.Int32)]
public readonly partial struct ManagerId;
[ValueType(Type = TypeOfValue.Int64)]
public readonly partial struct Int64Id;
[ValueType(Type = TypeOfValue.Int64)]
public readonly partial struct TimestampId;
[ValueType(Type = TypeOfValue.Guid)]
public readonly partial struct GloballyUniqueId;
[ValueType(Type = TypeOfValue.Guid)]
public readonly partial struct ProductId;
[ValueType(Type = TypeOfValue.Guid)]
public readonly partial struct SubscriptionId;
[ValueType(Type = TypeOfValue.Guid)]
public readonly partial struct MarketId;
The property Type
of ValueType
attribute has a default value of TypeOfValue.Guid
,
so the ProductId
above and other Guid
-based types could be defined as:
[ValueType]
public readonly partial struct ProductId;
Below is the simplified version of what's generated.
// Value Type
// There is no Value property; instead, casting operators should be used
[StructLayout(LayoutKind.Explicit)]
struct ProductId {
[FieldOffset(0)]
private readonly Guid _value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator ProductId(Guid g) => Unsafe.As<Guid, ProductId>(ref g);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Guid(ProductId id) => id._value;
}
// System.Text.Json Converter
class ProductIdJsonConverter : JsonConverter<ProductId>;
// Entity Framework Core Value Comparer
class ProductIdValueComparer : ValueComparer<ProductId>;
// Entity Framework Core Value Converter
class ProductIdValueConverter : ValueConverter<ProductId, Guid>;
// Entity Framework Core Conventions
static class ValueTypeConventionExtensions {
public static void AddValueTypeConversions(this ModelConfigurationBuilder config);
}
[ValueType]
readonly partial struct ProductId;
record Product {
public ProductId Id { get; }
}
Casting is the only way to get the value.
var productId = (ProductId)Guid.NewGuid(); // equivalent to the generated method ProductId.NewProductId()
var productIdAsGuid = (Guid)productId;
[ValueType(Type = TypeOfValue.Int32)]
readonly partial struct ProductId;
Again, casting is the only way to get the value.
var productId = (ProductId)123;
var productIdAsInt = (int)productId;
[ValueType(CastOperator = CastOperator.Implicit)]
readonly partial struct ProductId;
Once again, casting is the only way to get the value, but this time it's implicit.
ProductId productId = Guid.NewGuid(); // equivalent to ProductId.NewProductId()
Guid productIdAsGuid = productId;
class DeliveryDbContext : DbContext {
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) {
base.ConfigureConventions(configurationBuilder);
configurationBuilder.AddValueTypeConversions();
}
protected override void OnModelCreating(ModelBuilder builder) {
base.OnModelCreating(builder);
builder.Entity<Product>(static cfg => {
cfg.HasKey(static x => x.Id);
cfg.Property(static x => x.Id).ValueGeneratedOnAdd();
});
}
}
app.MapGet("/products/{id}", static ([FromRoute] ProductId id) => {
// ...
});
app.MapGet("/people/{id}", static ([FromRoute] PersonId id) => {
// ...
});
app.MapGet("/time/{id}", static ([FromRoute] TimestampId id) => {
// ...
});