Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ but this project DOES NOT adhere to [Semantic Versioning](http://semver.org/).
* Add CQRS output caching support with new `LeanCode.CQRS.OutputCaching` package
* Migrate Test infrastructure to Microsoft Testing Platform v2 and xunit v3
* Add `RawString` and `PrefixedString` source generated typed IDs support
* Implement `ISpanParsable<TSelf>` in Typed IDs with span-based and string-based parsing APIs
* Add `Destructure()` method and raw value accessors (`Guid`, `Ulid`, `ValuePart`) to prefixed typed IDs

## 9.0

Expand Down
51 changes: 42 additions & 9 deletions docs/domain/id/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,25 +47,58 @@ public class Employee : IAggregateRoot<EmployeeId>

### API

The generated ID supports the following operations:
The generated ID supports the following operations (example for `RawGuid`):

```cs
public readonly partial record struct ID
public readonly partial record struct ID : IEquatable<ID>,
IComparable<ID>,
ISpanFormattable,
IUtf8SpanFormattable,
ISpanParsable<ID>,
IEqualityOperators<ID, ID, bool>
{
public static readonly TestIntId Empty;
public static readonly ID Empty;

public int Value { get; }
public Guid Value { get; }
public bool IsEmpty { get; }

public static ID Parse(int? v);
public static ID? ParseNullable(int? id);
public static bool TryParse([NotNullWhen(true)] int? v, out ID id);
public static bool IsValid([NotNullWhen(true)] int? v);
// Parsing from backing type (if the ID is just a wrapper for raw backing type)
public static ID Parse(Guid v);
public static ID? ParseNullable(Guid? id);
public static bool TryParse([NotNullWhen(true)] Guid? v, out ID id);
public static bool IsValid([NotNullWhen(true)] Guid? v);

public static ID New(); // Only if generation is possible
}
```

### Prefixed ID features

Prefixed IDs (`PrefixedGuid`, `PrefixedUlid`, `PrefixedString`) provide additional APIs for accessing components:

```cs
// PrefixedGuid
public Guid Guid { get; }
public (string prefix, Guid data) Destructure();

// PrefixedUlid
public Ulid Ulid { get; }
public (string prefix, Ulid data) Destructure();

// PrefixedString
public string ValuePart { get; }
public (string prefix, string data) Destructure();
public static ID FromValuePart(string valuePart);
```

Example usage:

```cs
var id = OrderId.Parse("order_01ARZ3NDEKTSV4RRFFQ69G5FAV");
var (prefix, ulid) = id.Destructure(); // ("order", Ulid)
var rawUlid = id.Ulid; // Access raw Ulid directly
```

### Configuration

The format of the ID can be configured using:
Expand All @@ -80,7 +113,7 @@ The format of the ID can be configured using:
- `PrefixedString` - uses `string` as the underlying type; it is represented as a `(prefix)_(value)` string where `(value)` is an arbitrary string; does not support generating new IDs at runtime.
- `CustomPrefix` - for `Prefixed*` formats, you can configure what prefix it uses (if you e.g. want to use a shorter one).
- `SkipRandomGenerator` - setting this to `true` will skip generating `New` factory method (for formats that support generation).
- `MaxValueLength` - optional maximum length constraint for the value part. For `RawString`, this is the entire string length. For `PrefixedString`, this excludes the prefix and separator. When set, validation is performed in `Parse`/`IsValid` methods. Consider SQL Server's 900-byte key limit (~450 nvarchar chars) when choosing this value.
- `MaxValueLength` - required maximum length constraint for the value part of the string IDs. For `RawString`, this is the entire string length. For `PrefixedString`, this excludes the prefix and separator. Validation is performed in `Parse`/`IsValid` methods. Used for configuring columns in the database, so consider SQL Server's 900-byte key limit (~450 nvarchar chars) when choosing this value.

Examples:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ this PropertiesConfigurationBuilder<TId?> builder
private static PropertiesConfigurationBuilder<TId> AreRawTypedId<TBacking, TId>(
this PropertiesConfigurationBuilder<TId> builder
)
where TBacking : struct
where TBacking : struct, IEquatable<TBacking>, IComparable<TBacking>, ISpanParsable<TBacking>
where TId : struct, IRawTypedId<TBacking, TId>
{
return builder.HaveConversion<RawTypedIdConverter<TBacking, TId>, RawTypedIdComparer<TBacking, TId>>();
Expand All @@ -122,7 +122,7 @@ this PropertiesConfigurationBuilder<TId> builder
private static PropertiesConfigurationBuilder<TId?> AreRawTypedId<TBacking, TId>(
this PropertiesConfigurationBuilder<TId?> builder
)
where TBacking : struct
where TBacking : struct, IEquatable<TBacking>, IComparable<TBacking>, ISpanParsable<TBacking>
where TId : struct, IRawTypedId<TBacking, TId>
{
return builder.HaveConversion<RawTypedIdConverter<TBacking, TId>, RawTypedIdComparer<TBacking, TId>>();
Expand Down
4 changes: 2 additions & 2 deletions src/Domain/LeanCode.DomainModels.EF/TypedIdConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public PrefixedTypedIdConverter()

[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public class RawTypedIdConverter<TBacking, TId> : ValueConverter<TId, TBacking>
where TBacking : struct
where TBacking : struct, IEquatable<TBacking>, IComparable<TBacking>, ISpanParsable<TBacking>
where TId : struct, IRawTypedId<TBacking, TId>
{
public static readonly RawTypedIdConverter<TBacking, TId> Instance = new();
Expand Down Expand Up @@ -47,7 +47,7 @@ public PrefixedTypedIdComparer()

[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public class RawTypedIdComparer<TBacking, TId> : ValueComparer<TId>
where TBacking : struct
where TBacking : struct, IEquatable<TBacking>, IComparable<TBacking>, ISpanParsable<TBacking>
where TId : struct, IRawTypedId<TBacking, TId>
{
public static readonly RawTypedIdComparer<TBacking, TId> Instance = new();
Expand Down
Loading