Skip to content

Commit 1512b57

Browse files
committed
change to use ULID
1 parent 8e9c105 commit 1512b57

File tree

12 files changed

+189
-9
lines changed

12 files changed

+189
-9
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ Save or Upsert an item
151151

152152
Azure Table Storage requires both a `RowKey` and `PartitionKey`
153153

154-
The base repository will set the `RowKey` if it hasn't already been set using the `NewRowKey()` method. The default implementation is `Guid.NewGuid().ToString("N")`
154+
The base repository will set the `RowKey` if it hasn't already been set using the `NewRowKey()` method. The default implementation is `Ulid.NewUlid().ToString()`
155155

156156
If `PartitionKey` hasn't been set, `RowKey` will be used.
157157

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace TableStorage.Abstracts.Extensions;
6+
7+
public static class DateTimeExtentions
8+
{
9+
/// <summary>
10+
/// Rounds the date to the specified time span.
11+
/// </summary>
12+
/// <param name="date">The date to round.</param>
13+
/// <param name="span">The time span to round to.</param>
14+
/// <returns>The rounded date</returns>
15+
public static DateTime Round(this DateTime date, TimeSpan span)
16+
{
17+
long ticks = (date.Ticks + (span.Ticks / 2) + 1) / span.Ticks;
18+
return new DateTime(ticks * span.Ticks);
19+
}
20+
21+
/// <summary>
22+
/// Rounds the date to the specified span.
23+
/// </summary>
24+
/// <param name="date">The date to round.</param>
25+
/// <param name="span">The time span to round to.</param>
26+
/// <returns>The rounded date</returns>
27+
public static DateTimeOffset Round(this DateTimeOffset date, TimeSpan span)
28+
{
29+
long ticks = (date.Ticks + (span.Ticks / 2) + 1) / span.Ticks;
30+
return new DateTimeOffset(ticks * span.Ticks, date.Offset);
31+
}
32+
33+
/// <summary>
34+
/// Converts to specified <paramref name="dateTime"/> to its reverse chronological equivalent. DateTime.MaxValue - dateTime
35+
/// </summary>
36+
/// <param name="dateTime">The date time offset.</param>
37+
/// <returns>A <see cref="DateTime"/> chronological reversed.</returns>
38+
public static DateTime ToReverseChronological(this DateTime dateTime)
39+
{
40+
var targetTicks = DateTime.MaxValue.Ticks - dateTime.Ticks;
41+
return new DateTime(targetTicks);
42+
}
43+
44+
/// <summary>
45+
/// Converts to specified <paramref name="dateTimeOffset"/> to its reverse chronological equivalent. DateTimeOffset.MaxValue - dateTimeOffset
46+
/// </summary>
47+
/// <param name="dateTimeOffset">The date time offset.</param>
48+
/// <returns>A <see cref="DateTimeOffset"/> chronological reversed.</returns>
49+
public static DateTimeOffset ToReverseChronological(this DateTimeOffset dateTimeOffset)
50+
{
51+
var targetTicks = DateTimeOffset.MaxValue.Ticks - dateTimeOffset.Ticks;
52+
return new DateTimeOffset(targetTicks, TimeSpan.Zero);
53+
}
54+
}

src/TableStorage.Abstracts/TableRepository.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public TableRepository(ILoggerFactory logFactory, TableServiceClient tableServic
6161
protected ILogger Logger { get; }
6262

6363
/// <inheritdoc/>
64-
public virtual string NewRowKey() => Guid.NewGuid().ToString("N");
64+
public virtual string NewRowKey() => Ulid.NewUlid().ToString();
6565

6666
/// <inheritdoc/>
6767
public Task<TableClient> GetClientAsync() => _lazyTableClient.Value;

src/TableStorage.Abstracts/TableStorage.Abstracts.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<PrivateAssets>all</PrivateAssets>
1515
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1616
</PackageReference>
17+
<PackageReference Include="ulid" Version="1.3.3" />
1718
</ItemGroup>
1819

1920
</Project>

test/TableStorage.Abstracts.Tests/CommentRepositoryTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public CommentRepositoryTest(ITestOutputHelper output, DatabaseFixture databaseF
1717
public async Task FullTest()
1818
{
1919
var generator = new Faker<Comment>()
20-
.RuleFor(p => p.RowKey, _ => Guid.NewGuid().ToString("N"))
20+
.RuleFor(p => p.RowKey, _ => Ulid.NewUlid().ToString())
2121
.RuleFor(p => p.PartitionKey, f => f.PickRandom(Constants.Owners))
2222
.RuleFor(p => p.Name, f => f.Name.FullName())
2323
.RuleFor(p => p.Description, f => f.Lorem.Sentence())

test/TableStorage.Abstracts.Tests/ItemRepositoryTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ public async Task LargeResultOne()
130130
private static Faker<Item> CreateGenerator()
131131
{
132132
return new Faker<Item>()
133-
.RuleFor(p => p.RowKey, _ => Guid.NewGuid().ToString("N"))
133+
.RuleFor(p => p.RowKey, _ => Ulid.NewUlid().ToString())
134134
.RuleFor(p => p.PartitionKey, f => f.PickRandom(Constants.Owners))
135135
.RuleFor(p => p.Name, f => f.Name.FullName())
136136
.RuleFor(p => p.Description, f => f.Lorem.Sentence())
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace TableStorage.Abstracts.Tests.Models;
2+
3+
public class LogEvent : TableEntityBase
4+
{
5+
public string? Level { get; set; }
6+
7+
public string? MessageTemplate { get; set; }
8+
9+
public string? RenderedMessage { get; set; }
10+
11+
public string? Exception { get; set; }
12+
13+
public string? Data { get; set; }
14+
}

test/TableStorage.Abstracts.Tests/RoleRepositoryTest.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public async Task CreateRole()
1818
{
1919
var role = new Role
2020
{
21-
RowKey = Guid.NewGuid().ToString("N"),
21+
RowKey = Ulid.NewUlid().ToString(),
2222
Name = "CreateRole",
2323
NormalizedName = "createrole",
2424
Claims = [new Claim { Type = "Test", Value = "testing" }]
@@ -37,7 +37,7 @@ public async Task SaveRole()
3737
{
3838
var role = new Role
3939
{
40-
RowKey = Guid.NewGuid().ToString("N"),
40+
RowKey = Ulid.NewUlid().ToString(),
4141
Name = "SaveRole",
4242
NormalizedName = "saverole"
4343
};
@@ -55,7 +55,7 @@ public async Task CreateUpdateRole()
5555
{
5656
var role = new Role
5757
{
58-
RowKey = Guid.NewGuid().ToString("N"),
58+
RowKey = Ulid.NewUlid().ToString(),
5959
Name = "CreateRole",
6060
NormalizedName = "createrole"
6161
};
@@ -80,7 +80,7 @@ public async Task CreateReadRole()
8080
{
8181
var role = new Role
8282
{
83-
RowKey = Guid.NewGuid().ToString("N"),
83+
RowKey = Ulid.NewUlid().ToString(),
8484
Name = "CreateReadRole",
8585
NormalizedName = "createreadrole"
8686
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using TableStorage.Abstracts.Tests.Models;
2+
3+
namespace TableStorage.Abstracts.Tests.Services;
4+
5+
public interface ILogEventRepository : ITableRepository<LogEvent>
6+
{
7+
Task<PagedResult<LogEvent>> QueryByDate(
8+
DateOnly date,
9+
string? level = null,
10+
string? continuationToken = null,
11+
int? pageSize = 100,
12+
CancellationToken cancellationToken = default);
13+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using Azure.Data.Tables;
2+
3+
using Microsoft.Extensions.Logging;
4+
5+
using TableStorage.Abstracts.Extensions;
6+
using TableStorage.Abstracts.Tests.Models;
7+
8+
namespace TableStorage.Abstracts.Tests.Services;
9+
10+
public class LogEventRepository : TableRepository<LogEvent>, ILogEventRepository
11+
{
12+
public LogEventRepository(ILoggerFactory logFactory, TableServiceClient tableServiceClient)
13+
: base(logFactory, tableServiceClient)
14+
{
15+
}
16+
17+
public async Task<PagedResult<LogEvent>> QueryByDate(
18+
DateOnly date,
19+
string? level = null,
20+
string? continuationToken = null,
21+
int? pageSize = 100,
22+
CancellationToken cancellationToken = default)
23+
{
24+
var baseDate = date.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc);
25+
26+
var upperDate = baseDate.ToReverseChronological();
27+
var lowwerDate = baseDate.AddDays(1).ToReverseChronological();
28+
29+
var upper = $"{upperDate.Ticks:D19}";
30+
var lower = $"{lowwerDate.Ticks:D19}";
31+
32+
var filter = $"(PartitionKey ge '{lower}') and (PartitionKey lt '{upper}')";
33+
34+
if (level.HasValue())
35+
filter += $" and (Level eq '{level}')";
36+
37+
return await FindPageAsync(filter, continuationToken, pageSize, cancellationToken);
38+
}
39+
40+
public override string NewRowKey()
41+
{
42+
// store newest log first
43+
var timestamp = DateTimeOffset.UtcNow.ToReverseChronological();
44+
return Ulid.NewUlid(timestamp).ToString();
45+
}
46+
47+
protected override void BeforeSave(LogEvent entity)
48+
{
49+
if (entity.RowKey.IsNullOrWhiteSpace())
50+
entity.RowKey = NewRowKey();
51+
52+
if (entity.PartitionKey.IsNullOrWhiteSpace())
53+
{
54+
var timespan = entity.Timestamp ?? DateTimeOffset.UtcNow;
55+
var roundedDate = timespan
56+
.Round(TimeSpan.FromMinutes(5))
57+
.ToReverseChronological();
58+
59+
// create a 19 character String for reverse chronological ordering.
60+
entity.PartitionKey = $"{roundedDate.Ticks:D19}";
61+
}
62+
}
63+
64+
protected override string GetTableName() => "LogEvent";
65+
}

0 commit comments

Comments
 (0)