Repository navigation
Configuration Strongly Typed IDs
Configure FxMap to work seamlessly with strongly typed ID patterns, ensuring type safety throughout your application.
Strongly typed IDs are custom value types that wrap primitive types (like string or Guid) to provide type safety and prevent accidentally mixing different types of IDs.
public class Order
{
public string Id { get; set; }
public string UserId { get; set; }
public string ProductId { get; set; }
}
// ❌ Easy to make mistakes!
order.UserId = product.Id; // Wrong ID type!public class Order
{
public OrderId Id { get; set; }
public UserId UserId { get; set; }
public ProductId ProductId { get; set; }
}
// ✅ Compile-time type safety!
order.UserId = productId; // Compiler error!FxMap internally uses strings for ID lookups and comparisons. When you use strongly typed IDs, FxMap needs to know how to convert between your custom ID types and the string representation.
FxMap needs explicit converters to translate from string IDs (used in queries) to your strongly typed ID instances.
Use AddStronglyTypeIdConverter to register your ID converters:
builder.Services.AddFxMap(cfg =>
{
cfg.AddEntitiesFromAssemblyContaining<SomeEntityAssemblyMarker>();
cfg.AddProfilesFromAssemblyContaining<SomeProfileAssemblyMarker>();
// Register strongly typed ID converters
cfg.AddStronglyTypeIdConverter(a => a.OfType<StronglyTypeIdConverters>());
});Create a class that implements IStronglyTypeConverter<T> for each strongly typed ID:
using FxMap.Abstractions;
// Example strongly typed IDs with different underlying types
public record struct UserId(string Value); // string-based ID
public record struct OrderId(Guid Value); // Guid-based ID
public record struct ProvinceId(long Value); // long-based ID
public record struct ProductId(int Value); // int-based ID
// Converter for UserId (string)
public class UserIdConverter : IStronglyTypeConverter<UserId>
{
public UserId Convert(string input) => new UserId(input);
public bool CanConvert(string input) => !string.IsNullOrWhiteSpace(input);
}
// Converter for OrderId (Guid)
public class OrderIdConverter : IStronglyTypeConverter<OrderId>
{
public OrderId Convert(string input) => new OrderId(Guid.Parse(input));
public bool CanConvert(string input) => Guid.TryParse(input, out _);
}
// Converter for ProvinceId (long)
public class ProvinceIdConverter : IStronglyTypeConverter<ProvinceId>
{
public ProvinceId Convert(string input) => new ProvinceId(long.Parse(input));
public bool CanConvert(string input) => long.TryParse(input, out _);
}
// Converter for ProductId (int)
public class ProductIdConverter : IStronglyTypeConverter<ProductId>
{
public ProductId Convert(string input) => new ProductId(int.Parse(input));
public bool CanConvert(string input) => int.TryParse(input, out _);
}Organize all your converters in a single registry class:
using FxMap.Abstractions;
public class StronglyTypeIdConverters
{
// Converter for UserId (string)
public class UserIdConverter : IStronglyTypeConverter<UserId>
{
public UserId Convert(string input) => new UserId(input);
public bool CanConvert(string input) => !string.IsNullOrWhiteSpace(input);
}
// Converter for OrderId (Guid)
public class OrderIdConverter : IStronglyTypeConverter<OrderId>
{
public OrderId Convert(string input) => new OrderId(Guid.Parse(input));
public bool CanConvert(string input) => Guid.TryParse(input, out _);
}
// Converter for ProvinceId (long)
public class ProvinceIdConverter : IStronglyTypeConverter<ProvinceId>
{
public ProvinceId Convert(string input) => new ProvinceId(long.Parse(input));
public bool CanConvert(string input) => long.TryParse(input, out _);
}
// Converter for ProductId (int)
public class ProductIdConverter : IStronglyTypeConverter<ProductId>
{
public ProductId Convert(string input) => new ProductId(int.Parse(input));
public bool CanConvert(string input) => int.TryParse(input, out _);
}
// Converter for CountryId (string)
public class CountryIdConverter : IStronglyTypeConverter<CountryId>
{
public CountryId Convert(string input) => new CountryId(input);
public bool CanConvert(string input) => !string.IsNullOrWhiteSpace(input);
}
}Here's a complete example showing strongly typed IDs with FxMap:
// Define strongly typed IDs with different underlying types
public record struct UserId(string Value); // string-based
public record struct OrderId(Guid Value); // Guid-based
public record struct ProvinceId(long Value); // long-based
public record struct CountryId(string Value); // string-based
// Define converters
public class StronglyTypeIdConverters
{
public class UserIdConverter : IStronglyTypeConverter<UserId>
{
public UserId Convert(string input) => new UserId(input);
public bool CanConvert(string input) => !string.IsNullOrWhiteSpace(input);
}
public class OrderIdConverter : IStronglyTypeConverter<OrderId>
{
public OrderId Convert(string input) => new OrderId(Guid.Parse(input));
public bool CanConvert(string input) => Guid.TryParse(input, out _);
}
public class ProvinceIdConverter : IStronglyTypeConverter<ProvinceId>
{
public ProvinceId Convert(string input) => new ProvinceId(long.Parse(input));
public bool CanConvert(string input) => long.TryParse(input, out _);
}
public class CountryIdConverter : IStronglyTypeConverter<CountryId>
{
public CountryId Convert(string input) => new CountryId(input);
public bool CanConvert(string input) => !string.IsNullOrWhiteSpace(input);
}
}
// Entity configs with strongly typed IDs
public class UserConfig : EntityConfigureOf<User>
{
protected override void Configure()
{
Id(x => x.Id);
DefaultProperty(x => x.Name);
UseDistributedKey<UserDistributedKey>();
}
}
public class ProvinceConfig : EntityConfigureOf<Province>
{
protected override void Configure()
{
Id(x => x.Id);
DefaultProperty(x => x.Name);
UseDistributedKey<ProvinceDistributedKey>();
}
}
// Domain models with strongly typed IDs
public class User
{
public UserId Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public ProvinceId ProvinceId { get; set; }
}
public class Province
{
public ProvinceId Id { get; set; }
public string Name { get; set; }
public CountryId CountryId { get; set; }
public Country Country { get; set; }
}
// Configure FxMap
builder.Services.AddFxMap(cfg =>
{
cfg.AddEntitiesFromAssemblyContaining<SomeEntityAssemblyMarker>();
cfg.AddProfilesFromAssemblyContaining<SomeProfileAssemblyMarker>();
// Register the strongly typed ID converters
cfg.AddStronglyTypeIdConverter(a => a.OfType<StronglyTypeIdConverters>());
})
.AddEntityFrameworkCore(options => options.AddDbContexts(typeof(TestDbContext), typeof(OtherDbContext)...)});Once configured, you can use strongly typed IDs in your response DTOs:
// DTO with strongly typed IDs
public class OrderResponse
{
public OrderId Id { get; set; } // Guid-based ID
public UserId UserId { get; set; } // string-based ID
public string UserName { get; set; }
public string UserEmail { get; set; }
public ProvinceId UserProvinceId { get; set; }
}
// Profile using strongly typed ID in .Of()
public class OrderResponseProfile : ProfileOf<OrderResponse>
{
protected override void Configure()
{
// FxMap automatically converts UserId (string) for lookup
UseDistributedKey<UserDistributedKey>()
.Of(x => x.UserId) // strongly typed UserId
.For(x => x.UserName) // default property (Name)
.For(x => x.UserEmail, "Email")
.For(x => x.UserProvinceId, "ProvinceId"); // maps to strongly typed ProvinceId
}
}
// Usage
var response = new OrderResponse
{
Id = new OrderId(Guid.NewGuid()),
UserId = new UserId("user-456")
};
await _mapper.MapDataAsync(response);
// UserName, UserEmail, and UserProvinceId are now populated!For IDs following a common pattern, you can create a generic converter:
// Base interface for strongly typed IDs
public interface IStronglyTypedId<T>
{
T Value { get; }
}
// Generic strongly typed ID record
public record struct StronglyTypedId<T>(T Value) : IStronglyTypedId<T>;
// Generic converter
public class GenericStronglyTypedIdConverter<TId, TValue>
: IStronglyTypeConverter<StronglyTypedId<TValue>>
where TId : IStronglyTypedId<TValue>
{
public StronglyTypedId<TValue> Convert(string input)
{
var value = (TValue)System.Convert.ChangeType(input, typeof(TValue));
return new StronglyTypedId<TValue>(value);
}
public bool CanConvert(string input)
{
return !string.IsNullOrWhiteSpace(input);
}
}
// Usage
public record struct UserId(string Value) : IStronglyTypedId<string>;
public record struct ProductId(Guid Value) : IStronglyTypedId<Guid>;- Use records or readonly structs for immutable ID types
- Keep all converters in a single registry class for easy management
- Implement validation in the converter if needed
- Consider using a base interface for consistency across all IDs
- Forgetting to register converters will cause runtime errors when using strongly typed IDs
- The CanConvert method should validate the input string before conversion
- Converters are only needed for ID properties, not all strongly typed properties
Source: https://fxmapper.net/docs/configuration/strongly-typed-ids/ — the website is always the most up to date version.
Introduction
Getting Started
Configuration
- Service Registration
- Entities & Profiles
- Handlers
- Pipelines
- Strongly Typed IDs
- Error Handling
- Retry Policy
- Supervisor
- Telemetry
- Kestrel (HTTP/2)
- Analyzers
Expressions
- Overview
- Property Access
- Filters, Sorting & Paging
- Functions
- Aggregations
- Projections
- Coalesce & Ternary
- GroupBy
Data Providers
Transports
Integrations
Quality
AI Assistants
Migration
Release Notes