Skip to content

Commit 69390f5

Browse files
committed
Add support for record constructor parameter annotations
When using C# 9 records for storage (super convenient!), you typically don't declare the properties explicitly, but leverage the concise syntax instead, so now you can do: ``` public record Book([RowKey] string ISBN, [PartitionKey] string Author, string Title); ``` Fixes #43
1 parent ba0c216 commit 69390f5

File tree

4 files changed

+56
-3
lines changed

4 files changed

+56
-3
lines changed

src/TableStorage/PartitionKeyAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace Devlooped
1111
/// Can be applied at the class level instead with a fixed value to persist
1212
/// entities with a fixed partition key value.
1313
/// </summary>
14-
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)]
14+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter)]
1515
partial class PartitionKeyAttribute : TableStorageAttribute
1616
{
1717
/// <summary>

src/TableStorage/RowKeyAttribute.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ namespace Devlooped
77
{
88
/// <summary>
99
/// Flags the property to use as the table storage row key
10-
/// when storing the annotated type using the <see cref="TableRepository{T}"/>.
10+
/// when storing the annotated type using <see cref="TableRepository{T}"/> or
11+
/// <see cref="TablePartition{T}"/>.
1112
/// </summary>
12-
[AttributeUsage(AttributeTargets.Property)]
13+
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter)]
1314
partial class RowKeyAttribute : TableStorageAttribute
1415
{
1516
/// <summary>

src/TableStorage/TableStorageAttribute.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,23 @@ static Expression<Func<TEntity, string>> CreateGetterCore<TEntity, TAttribute>()
3333
var keyProp = typeof(TEntity).GetProperties()
3434
.FirstOrDefault(prop => prop.GetCustomAttribute<TAttribute>() != null);
3535

36+
// See if the attribute is applied to a constructor argument with the same name
37+
// as a prop, like it would in a record type
38+
if (keyProp == null)
39+
{
40+
var keyParam = typeof(TEntity).GetConstructors().SelectMany(ctor => ctor.GetParameters())
41+
.Where(prm => prm.GetCustomAttribute<TAttribute>() != null)
42+
.FirstOrDefault();
43+
44+
if (keyParam != null)
45+
keyProp = typeof(TEntity).GetProperties().FirstOrDefault(prop => prop.Name == keyParam.Name);
46+
}
47+
3648
if (keyProp == null)
3749
{
3850
if (typeof(TAttribute) == typeof(PartitionKeyAttribute))
3951
{
52+
// See if the attribute is applied at the class level with a fixed value
4053
var partitionKey = typeof(TEntity).GetCustomAttribute<PartitionKeyAttribute>()?.PartitionKey;
4154
if (partitionKey == null)
4255
{

src/Tests/RepositoryTests.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,39 @@ public async Task TableEndToEnd()
4141
Assert.False(true, "Did not expect to find any entities");
4242
}
4343

44+
[Fact]
45+
public async Task TableRecordEndToEnd()
46+
{
47+
var repo = TableRepository.Create<AttributedRecordEntity>(CloudStorageAccount.DevelopmentStorageAccount);
48+
var entity = await repo.PutAsync(new AttributedRecordEntity("Book", "1234"));
49+
50+
Assert.Equal("1234", entity.ID);
51+
Assert.Null(entity.Status);
52+
53+
entity.Status = "Pending";
54+
55+
await repo.PutAsync(entity);
56+
57+
var saved = await repo.GetAsync("Book", "1234");
58+
59+
Assert.NotNull(saved);
60+
Assert.Equal("Pending", saved!.Status);
61+
62+
var entities = new List<AttributedRecordEntity>();
63+
64+
await foreach (var e in repo.EnumerateAsync("Book"))
65+
entities.Add(e);
66+
67+
Assert.Single(entities);
68+
69+
await repo.DeleteAsync(saved);
70+
71+
Assert.Null(await repo.GetAsync("Book", "1234"));
72+
73+
await foreach (var _ in repo.EnumerateAsync("Book"))
74+
Assert.False(true, "Did not expect to find any entities");
75+
}
76+
4477
[Fact]
4578
public async Task DoesNotDuplicateKeyProperties()
4679
{
@@ -250,5 +283,11 @@ record RecordEntity(string Kind, string ID)
250283
{
251284
public string? Status { get; set; }
252285
}
286+
287+
[Table("Record")]
288+
record AttributedRecordEntity([PartitionKey] string Kind, [RowKey] string ID)
289+
{
290+
public string? Status { get; set; }
291+
}
253292
}
254293
}

0 commit comments

Comments
 (0)