Skip to content

Commit 54747a3

Browse files
authored
Merge pull request #50 from I-RzR-I/feature/NameSpaceConceptChange
Add ime-ordered id generation
2 parents ce5bc2a + 9475444 commit 54747a3

79 files changed

Lines changed: 610 additions & 7957 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
> **Note** This repository is developed for net40, net45, .netstandard2.0 and .netstandard2.1.
22
3+
[![NuGet Version](https://img.shields.io/nuget/v/RzR.Extensions.Domain.svg?style=flat&logo=nuget)](https://www.nuget.org/packages/RzR.Extensions.Domain/)
4+
[![Nuget Downloads](https://img.shields.io/nuget/dt/RzR.Extensions.Domain.svg?style=flat&logo=nuget)](https://www.nuget.org/packages/RzR.Extensions.Domain)
5+
6+
<details>
7+
8+
<summary>Old version</summary>
9+
310
[![NuGet Version](https://img.shields.io/nuget/v/DomainCommonExtensions.svg?style=flat&logo=nuget)](https://www.nuget.org/packages/DomainCommonExtensions/)
411
[![Nuget Downloads](https://img.shields.io/nuget/dt/DomainCommonExtensions.svg?style=flat&logo=nuget)](https://www.nuget.org/packages/DomainCommonExtensions)
512

6-
> **v5 — Namespace reorganisation (BREAKING)**
7-
>
8-
> Starting with **v5**, the library was reorganised with a new namespace (from `DomainCommonExtensions` to `RzR.Extensions.Domain`) and focused into namespace buckets under the new root `RzR.Extensions.Domain.*`
9-
> (`Async`, `Collections`, `Cryptography`, `Data`, `Diagnostics`, `Internal`, `IO`, `Linq`, `Models`, `Primitives`, `Reflection`, `Text`, `Validation`).
10-
> Class names and method signatures are unchanged — only the namespaces moved.
11-
>
12-
> To soften the upgrade, every old (v4) namespace is preserved as `[Obsolete]` forwarder static classes under
13-
> [`src/DomainCommonExtensions/_Legacy/`](src/DomainCommonExtensions/_Legacy/), so existing v4 code keeps compiling
14-
> while the IDE highlights every call-site with the new location.
15-
>
16-
> See the full mapping in [docs/namespace-migration-v5.md](docs/namespace-migration-v5.md).
13+
</details>
14+
15+
<br />
16+
1717

1818
This library/repository was created as a way to simplify the development process. It collects the most used extension methods for primitive data types (`int`, `string`, `DateTime`, `Enum`, `bool`, `byte`, `char`, `Guid`, `TimeSpan`, …) and for the common collection interfaces (`ICollection`, `IEnumerable`, `IList`, `HashSet`, `IQueryable`, `IDictionary`, `ConcurrentDictionary`, `Queue`, `DynamicList`, …).
1919

2020
It also bundles `cryptography` helpers for encrypting/decrypting strings by key with `RSA`, `AES`, `TEA`, plus a `Base32` encoder and a password generator.
2121

22+
A `TimeSeqId` generator in `Primitives` produces time-ordered, lexicographically sortable 48-character unique identifiers with 64-bit CSPRNG entropy and a SHA-256 integrity checksum — a structured, sortable alternative to `Guid`.
23+
2224
If you need to extract documentation/comments from an `Assembly`, those extensions are here too. You will also find async lazy-load primitives `AsyncLazy<T>` and `AsyncExpiringLazy<T>`.
2325

2426
Additionally, there are extensions for `System.Data` (`DbDataReader`, `IDataRecord`, `DataTable`), `FileStream`, `MemoryStream`, `Type`, plus `Linq` / `Expression` extensions and a `PredicateBuilder` to keep call-sites clean.
@@ -31,12 +33,11 @@ I hope I'm on the right way to providing all these things to all who saw or sear
3133
**You can find a [demo](https://demowebutils.iamrzr.dev/) with some of the extension methods on the [demowebutils](https://demowebutils.iamrzr.dev/) website.**
3234

3335

34-
**In case you wish to use it in your project, you can install the package from <a href="https://www.nuget.org/packages/DomainCommonExtensions" target="_blank">nuget.org</a>** or specify what version you want:
36+
**In case you wish to use it in your project, you can install the package from <a href="https://www.nuget.org/packages/RzR.Extensions.Domain" target="_blank">nuget.org</a>** or specify what version you want:
3537

36-
> `Install-Package DomainCommonExtensions -Version x.x.x.x`
38+
> `Install-Package RzR.Extensions.Domain -Version x.x.x.x`
3739
3840
## Content
3941
1. [USING](docs/usage.md)
4042
2. [CHANGELOG](docs/CHANGELOG.md)
4143
3. [BRANCH-GUIDE](docs/branch-guide.md)
42-
4. [V5 NAMESPACE MIGRATION](docs/namespace-migration-v5.md)

build/generateCustomVersion.bat

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
@echo off
2+
cls
3+
4+
echo Write the source branch name (you current branch name, where were the changes made):
5+
set /p userDefinedSourceBranch=
6+
echo.
7+
echo Write the destination branch name (the branch from which you can get the latest changes, like 'develop' or 'main/master'):
8+
set /p userDefinedDestinationBranch=
9+
echo.
10+
echo Write your custom version number:
11+
set /p userDefinedVersion=
12+
echo.
13+
14+
:: Set init params value
15+
set applicationName=DomainCommonExtensions
16+
set runVersionIncrement=y
17+
set runGenChangeLog=y
18+
:: If runBuild > y(yes), build in release mode
19+
set runBuild=y
20+
set runSolutionTest=n
21+
set runTest=n
22+
set runPack=y
23+
set assemblyPath=$('..\src\shared\GeneralAssemblyInfo.cs')
24+
set genType=-2
25+
set setInChangeLogNewVersion=y
26+
set autoCommitAndPush=n
27+
set autoGetLatestDevelop=y
28+
set changeLogPath=$('..\docs\CHANGELOG.MD')
29+
set sourceBranch=%userDefinedSourceBranch%
30+
set destinationBranch=%userDefinedDestinationBranch%
31+
set customVersion=%userDefinedVersion%
32+
set solutionPath=$('..\src\RzR.Shared.Attributes.sln')
33+
set packResultPath=$('..\nuget\')
34+
set packProjectsPath=$('..\src\DomainCommonExtensions\DomainCommonExtensions.csproj')
35+
set testProjectsPath=$('..\src\tests\DataTypeTests\DataTypeTests.csproj')
36+
37+
38+
echo :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
39+
echo ::: Initialize: :::
40+
echo ::: - New application version generation :::
41+
echo ::: - Change log generation :::
42+
echo ::: - Build :::
43+
echo ::: - Test :::
44+
echo ::: - Create package :::
45+
echo :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
46+
echo:
47+
echo:
48+
49+
PowerShell -NoProfile -ExecutionPolicy ByPass -Command ".\GenerateBuildInfo.exe -scriptCommands \"runVersionIncrement=%runVersionIncrement%;runGenChangeLog=%runGenChangeLog%;runBuild=%runBuild%;runSolutionTest=%runSolutionTest%;runTest=%runTest%;runPack=%runPack%;setInChangeLogNewVersion=%setInChangeLogNewVersion%;autoCommitAndPush=%autoCommitAndPush%;autoGetLatestDevelop=%autoGetLatestDevelop%;changeLogPath=%changeLogPath%;sourceBranch=%sourceBranch%;destinationBranch=%destinationBranch%;assemblyPath=%assemblyPath%;customVersion=%customVersion%;genType=%genType%;solutionPath=%solutionPath%;packResultPath=%packResultPath%;packProjectsPath=%packProjectsPath%;testProjectsPath=%testProjectsPath%\"";
50+
51+
echo
52+
pause

docs/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
### **v6.0.0.8301** [[RzR](mailto:108324929+I-RzR-I@users.noreply.github.com)] 28-05-2026
2+
* **BREAKING:** `_Legacy/` backward-compatibility shim layer removed. The `[Obsolete]` forwarder namespaces introduced in v5 no longer exist. All consuming code must use `RzR.Extensions.Domain.*` namespaces. See [namespace-migration-v5.md](namespace-migration-v5.md) for the full mapping.
3+
* [DEV] - (RzR) -> New `TimeSeqId` generator in `Primitives`: time-ordered, lexicographically sortable 48-character unique identifier. Format: `yyyy-MMdd-HHmmssfff-SSSS-RRRRRRRRRRRRRRRR-HHHHHH` (year / date / time-ms / sequence / 16-hex random / 6-char SHA-256 checksum). Fully thread-safe; IDs sort in generation order by plain string comparison.
4+
5+
16
### **v5.0.0.7637** [[RzR](mailto:108324929+I-RzR-I@users.noreply.github.com)] 24-04-2026
27
* **BREAKING:** Repository reorganized into 11 stable namespace buckets (`Async`, `Collections`, `Cryptography`, `Data`, `Diagnostics`, `Internal`, `IO`, `Linq`, `Models`, `Primitives`, `Reflection`, `Text`, `Validation`). Class names and method signatures are unchanged; only the namespaces moved. See [namespace-migration-v5.md](namespace-migration-v5.md) for the full mapping.
38
* **Backward-compat shim layer** (`_Legacy/` folder): `[Obsolete]` static methods preserve the v4 namespaces so existing user code still compiles. Each call site lights up with the new v5 location.

docs/usage.md

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ Most of these extension methods are self-explanatory. Browse the source for the
44

55
The source code is open, so you can check what is available and pick what is useful for your project.
66

7-
> **Namespaces (v5).** Since v5 the library is with a new namespace (from `DomainCommonExtensions` to `RzR.Extensions.Domain`), and split into focused buckets under `RzR.Extensions.Domain.*`
8-
> (`Async`, `Collections`, `Cryptography`, `Data`, `Diagnostics`, `Internal`, `IO`, `Linq`, `Models`, `Primitives`,
9-
> `Reflection`, `Text`, `Validation`). The old (v4) namespaces still resolve via `[Obsolete]` forwarders
10-
> in [`src/DomainCommonExtensions/_Legacy/`](../src/DomainCommonExtensions/_Legacy/) — see
11-
> [namespace-migration-v5.md](namespace-migration-v5.md) for the full mapping.
7+
> **Namespaces (v5+).** Since v5 the library uses the namespace root `RzR.Extensions.Domain.*`, split into focused
8+
> buckets: `Async`, `Collections`, `Cryptography`, `Data`, `Diagnostics`, `Internal`, `IO`, `Linq`, `Models`,
9+
> `Primitives`, `Reflection`, `Text`, `Validation`. The old v4 `DomainCommonExtensions.*` namespaces and the
10+
> `_Legacy/` shim layer were **removed in v6** — see [namespace-migration-v5.md](namespace-migration-v5.md) for
11+
> the full mapping.
1212
1313
---
1414

@@ -111,3 +111,49 @@ A thread-safe asynchronous lazy initializer with a configurable TTL. After expir
111111
| **Constructor params** | `factory` | `factory` + `ttl` |
112112
| **`IsInitializationStarted` after TTL** | N/A | Still `true` until replaced or `Reset()` |
113113
| **Use when** | Value must be computed once and reused indefinitely | Value needs periodic refresh (tokens, configs, rates, etc) |
114+
115+
---
116+
117+
## Time-ordered unique identifier — `TimeSeqId`
118+
119+
> Namespace: `RzR.Extensions.Domain.Primitives`
120+
>
121+
> ```csharp
122+
> using RzR.Extensions.Domain.Primitives;
123+
> ```
124+
125+
A sortable, collision-resistant string identifiera structured alternative to `Guid`. IDs are lexicographically
126+
sortable by generation time and monotonically ordered within a single process.
127+
128+
### Format
129+
130+
```
131+
yyyy - MMdd - HHmmssfff - SSSS - RRRRRRRRRRRRRRRR - HHHHHH
132+
[0] [1] [2] [3] [4] [5]
133+
```
134+
135+
| Block | Width | Alphabet | Content |
136+
|---|---|---|---|
137+
| `[0]` | 4 | `0-9` | Year (`yyyy`) |
138+
| `[1]` | 4 | `0-9` | Month and day (`MMdd`) |
139+
| `[2]` | 9 | `0-9` | Time to millisecond (`HHmmssfff`) |
140+
| `[3]` | 4 | `0-9` | Per-millisecond monotonic sequence `0000`–`9999` |
141+
| `[4]` | 16 | `0-9 A-F` | 64-bit CSPRNG random salt |
142+
| `[5]` | 6 | `0-9 A-Z` | SHA-256 integrity checksum over blocks `[0]`–`[4]` |
143+
144+
### Usage
145+
146+
```csharp
147+
string id = TimeSeqId.Generate();
148+
// e.g. "2026-0528-143022123-0001-A3F7B2C9D1E50246-X5K3P2"
149+
```
150+
151+
### Guarantees
152+
153+
| Property | Detail |
154+
|---|---|
155+
| **Single-process uniqueness** | The `(timestamp, sequence)` pair is unique within one process; `Generate()` is fully thread-safe. |
156+
| **Distributed uniqueness** | Two replicas hitting the same millisecond + sequence slot collide with probability **1 in 2⁶⁴ ≈ 5 × 10⁻²⁰** via block `[4]`. |
157+
| **Lexicographic sort order** | IDs sort in generation order by plain string comparison — no parsing required. |
158+
| **Monotonicity** | Within one process, IDs are strictly non-decreasing. Burst capacity: 9 999 IDs/ms before the generator waits for the next millisecond (lock is released during the wait). |
159+
| **Integrity** | Block `[5]` is a SHA-256 checksum over blocks `[0]``[4]`. Tampering any preceding block changes the checksum. |

src/DomainCommonExtensions/DomainCommonExtensions.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
<PackageIconUrl />
2626
<RepositoryType>GIT</RepositoryType>
2727
<RootNamespace>RzR.Extensions.Domain</RootNamespace>
28+
<AssemblyName>RzR.Extensions.Domain</AssemblyName>
2829
</PropertyGroup>
2930

3031
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -51,7 +52,7 @@
5152
</ItemGroup>
5253

5354
<ItemGroup>
54-
<PackageReference Include="CodeSource" Version="5.0.0.7373" />
55+
<PackageReference Include=" RzR.Core.CodeSource" Version="6.0.0.94" />
5556
<!--<PackageReference Include="System.Linq.Dynamic.Core" Version="1.3.3" />-->
5657
</ItemGroup>
5758

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
// ***********************************************************************
2+
// Assembly : RzR.Shared.Extensions.DomainCommonExtensions
3+
// Author : RzR
4+
// Created : 28-05-2026 20:05
5+
//
6+
// Last Modified By : RzR
7+
// Last Modified On : 28-05-2026 20:55
8+
// ***********************************************************************
9+
// <copyright file="TimeSeqId.cs" company="RzR SOFT & TECH">
10+
// Copyright (c) RzR. All rights reserved.
11+
// </copyright>
12+
// <contact>
13+
// https://iamrzr.dev/contact
14+
// </contact>
15+
// <summary></summary>
16+
// ***********************************************************************
17+
18+
#region U S I N G
19+
20+
using System;
21+
using System.Security.Cryptography;
22+
using System.Text;
23+
using System.Threading;
24+
25+
#endregion
26+
27+
namespace RzR.Extensions.Domain.Primitives
28+
{
29+
/// -------------------------------------------------------------------------------------------------
30+
/// <summary>
31+
/// A time-ordered, lexicographically sortable 48-character unique identifiers.
32+
/// </summary>
33+
/// =================================================================================================
34+
public static class TimeSeqId
35+
{
36+
/*
37+
* [0] -> 4 digits year yyyy
38+
* [1] -> 4 digits month and day MMdd
39+
* [2] -> 9 digits HHmmssfff
40+
* [3] -> 4 digits sequence for uniqueness
41+
* [4] -> 16 characters(random) { 0 - 9A - F}
42+
* [5] -> 6 characters, hash of the previous block {0-9A-Z}
43+
*/
44+
45+
[ThreadStatic]
46+
private static RandomNumberGenerator _threadRng;
47+
private static RandomNumberGenerator Rng => _threadRng ??= RandomNumberGenerator.Create();
48+
49+
private const int MAX_SEQUENCE = 9999;
50+
private static long _lastTimestamp;
51+
private static int _sequence;
52+
private static readonly object Lock = new();
53+
54+
/// -------------------------------------------------------------------------------------------------
55+
/// <summary>
56+
/// Generates a new time-ordered, lexicographically sortable unique identifier.
57+
/// The result is safe for concurrent use and monotonically ordered within a single process.
58+
/// </summary>
59+
/// <returns>
60+
/// A 48-character string in the format yyyy-MMdd-HHmmssfff-SSSS-RRRRRRRRRRRRRRRR-HHHHHH
61+
/// where SSSS is a per-millisecond sequence, RRRRRRRRRRRRRRRR is 64 bits of CSPRNG entropy,
62+
/// and HHHHHH is a SHA-256-derived integrity checksum.
63+
/// </returns>
64+
/// =================================================================================================
65+
public static string Generate()
66+
{
67+
DateTime current = default;
68+
int sequenceNumber = 0;
69+
long waitUntil = 0;
70+
71+
while (true)
72+
{
73+
if (waitUntil > 0)
74+
{
75+
while (DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond < waitUntil)
76+
Thread.Sleep(1);
77+
waitUntil = 0;
78+
}
79+
80+
var acquired = false;
81+
82+
lock (Lock)
83+
{
84+
current = DateTime.UtcNow;
85+
var currentTimestamp = current.Ticks / TimeSpan.TicksPerMillisecond;
86+
87+
if (currentTimestamp <= _lastTimestamp)
88+
{
89+
_sequence++;
90+
91+
if (_sequence > MAX_SEQUENCE)
92+
{
93+
// Sequence exhausted for this millisecond: release the lock
94+
// immediately and wait for the clock to advance outside it.
95+
waitUntil = _lastTimestamp + 1;
96+
}
97+
else
98+
{
99+
current = new DateTime(_lastTimestamp * TimeSpan.TicksPerMillisecond, DateTimeKind.Utc);
100+
acquired = true;
101+
}
102+
}
103+
else
104+
{
105+
_lastTimestamp = currentTimestamp;
106+
_sequence = 0;
107+
acquired = true;
108+
}
109+
110+
if (acquired)
111+
sequenceNumber = _sequence;
112+
}
113+
114+
if (acquired) break;
115+
}
116+
117+
var block0 = current.ToString("yyyy");
118+
var block1 = current.ToString("MMdd");
119+
var block2 = current.ToString("HHmmssfff");
120+
var block3 = sequenceNumber.ToString("D4");
121+
var block4 = GenerateSecureRandomHex(16);
122+
123+
var combinedBlocks = $"{block0}-{block1}-{block2}-{block3}-{block4}";
124+
var block5 = GenerateHash(combinedBlocks, 6);
125+
126+
return $"{block0}-{block1}-{block2}-{block3}-{block4}-{block5}";
127+
}
128+
129+
/// -------------------------------------------------------------------------------------------------
130+
/// <summary>
131+
/// Generates a cryptographically secure random hex string of the specified length.
132+
/// </summary>
133+
/// <param name="length">The number of hex characters to generate.</param>
134+
/// <returns>
135+
/// A random uppercase hex string of the requested length using characters { 0-9, A-F }.
136+
/// </returns>
137+
/// =================================================================================================
138+
private static string GenerateSecureRandomHex(int length)
139+
{
140+
var bytes = new byte[length];
141+
Rng.GetBytes(bytes);
142+
143+
const string hexChars = "0123456789ABCDEF";
144+
var result = new StringBuilder(length);
145+
146+
foreach (var b in bytes)
147+
result.Append(hexChars[b % 16]);
148+
149+
return result.ToString();
150+
}
151+
152+
/// -------------------------------------------------------------------------------------------------
153+
/// <summary>
154+
/// Computes a SHA-256-based alphanumeric checksum of <paramref name="input"/>,
155+
/// truncated to <paramref name="length"/> characters.
156+
/// Uses rejection sampling to guarantee a uniform distribution over { 0-9, A-Z }.
157+
/// </summary>
158+
/// <param name="input">The string to hash.</param>
159+
/// <param name="length">The desired output length in characters.</param>
160+
/// <returns>
161+
/// An alphanumeric string of exactly <paramref name="length"/> characters derived from the SHA-256 digest.
162+
/// </returns>
163+
/// =================================================================================================
164+
private static string GenerateHash(string input, int length)
165+
{
166+
using (var sha = SHA256.Create())
167+
{
168+
var hashBytes = sha.ComputeHash(Encoding.UTF8.GetBytes(input));
169+
const string alphanumeric = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
170+
var unbiasedCeiling = 256 - 256 % alphanumeric.Length;
171+
var result = new StringBuilder(length);
172+
var byteIdx = 0;
173+
174+
while (result.Length < length)
175+
{
176+
if (byteIdx >= hashBytes.Length)
177+
{
178+
hashBytes = sha.ComputeHash(hashBytes);
179+
byteIdx = 0;
180+
}
181+
182+
var b = hashBytes[byteIdx++];
183+
if (b < unbiasedCeiling)
184+
result.Append(alphanumeric[b % alphanumeric.Length]);
185+
}
186+
187+
return result.ToString();
188+
}
189+
}
190+
191+
}
192+
}

0 commit comments

Comments
 (0)