Skip to content

Commit 21d7a98

Browse files
committed
Improve dynamic entity support by exposing TableEntity directly
Expose the new and richer TableEntity class from the underlying azure tables API so it's a more effective addition on top of it. #127
1 parent 8750fb3 commit 21d7a98

File tree

8 files changed

+39
-31
lines changed

8 files changed

+39
-31
lines changed

readme.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,9 @@ it with `[Browsable(false)]` and it will be skipped when persisting and reading
227227
### TableEntity Support
228228

229229
Since these repository APIs are quite a bit more intuitive than working directly against a
230-
`TableClient`, you might want to retrieve/enumerate entities just by their built-in `ITableEntity`
230+
`TableClient`, you might want to retrieve/enumerate entities just by their built-in `TableEntity`
231231
properties, like `PartitionKey`, `RowKey`, `Timestamp` and `ETag`. For this scenario, we
232-
also support creating `ITableRepository<ITableEntity>` and `ITablePartition<ITableEntity>`
232+
also support creating `ITableRepository<TableEntity>` and `ITablePartition<TableEntity>`
233233
by using the factory methods `TableRepository.Create(...)` and `TablePartition.Create(...)`
234234
without a (generic) entity type argument.
235235

@@ -243,25 +243,25 @@ var repo = TablePartition.Create(storageAccount,
243243
partitionKey: "Region");
244244

245245
// Enumerate all regions within the partition as plain TableEntities
246-
await foreach (ITableEntity region in repo.EnumerateAsync())
246+
await foreach (TableEntity region in repo.EnumerateAsync())
247247
Console.WriteLine(region.RowKey);
248248
```
249249

250-
Moverover, the returned `ITableEntity` is actually an instance of `DynamicTableEntity`, so
251-
you can downcast and access any additional stored properties, which you can persist by
252-
passing a `DynamicTableEntity` to `PutAsync`:
250+
You can access and add additional properties by just using the entity indexer, which you can
251+
later persist by calling `PutAsync`:
253252

254253
```csharp
255-
await repo.PutAsync(new DynamicTableEntity("Book", "9781473217386", "*", new Dictionary<string, EntityProperty>
256-
{
257-
{ "Title", EntityProperty.GeneratePropertyForString("Neuromancer") },
258-
{ "Price", EntityProperty.GeneratePropertyForDouble(7.32) },
259-
}));
254+
await repo.PutAsync(
255+
new TableEntity("Book", "9781473217386")
256+
{
257+
["Title"] = "Neuromancer",
258+
["Price"] = 7.32
259+
});
260260

261-
var entity = (DynamicTableEntity)await repo.GetAsync("Book", "9781473217386");
261+
var entity = await repo.GetAsync("Book", "9781473217386");
262262

263-
Assert.Equal("Title", entity.Properties["Neuromancer"].StringValue);
264-
Assert.Equal(7.32, entity.Properties["Price"].DoubleValue);
263+
Assert.Equal("Neuromancer", entity["Title"]);
264+
Assert.Equal(7.32, (double)entity["Price"]);
265265
```
266266

267267
## Installation

src/TableStorage.Newtonsoft/JsonDocumentSerializer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public JsonDocumentSerializer()
7777
class DateOnlyJsonConverter : JsonConverter<DateOnly>
7878
{
7979
public override DateOnly ReadJson(JsonReader reader, Type objectType, DateOnly existingValue, bool hasExistingValue, JsonSerializer serializer)
80-
=> DateOnly.Parse((string)reader.Value, CultureInfo.InvariantCulture);
80+
=> DateOnly.Parse((string)reader.Value!, CultureInfo.InvariantCulture);
8181

8282
public override void WriteJson(JsonWriter writer, DateOnly value, JsonSerializer serializer)
8383
=> writer.WriteValue(value.ToString("O", CultureInfo.InvariantCulture));

src/TableStorage/TableEntityPartition.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
namespace Devlooped
1212
{
1313
/// <inheritdoc />
14-
partial class TableEntityPartition : ITablePartition<ITableEntity>
14+
partial class TableEntityPartition : ITablePartition<TableEntity>
1515
{
1616
readonly TableEntityRepository repository;
1717

@@ -51,10 +51,10 @@ public UpdateStrategy UpdateStrategy
5151
}
5252

5353
/// <inheritdoc />
54-
public IQueryable<ITableEntity> CreateQuery() => repository.CreateQuery().Where(x => x.PartitionKey == PartitionKey);
54+
public IQueryable<TableEntity> CreateQuery() => repository.CreateQuery().Where(x => x.PartitionKey == PartitionKey);
5555

5656
/// <inheritdoc />
57-
public Task<bool> DeleteAsync(ITableEntity entity, CancellationToken cancellation = default)
57+
public Task<bool> DeleteAsync(TableEntity entity, CancellationToken cancellation = default)
5858
{
5959
if (!PartitionKey.Equals(entity.PartitionKey, StringComparison.Ordinal))
6060
throw new ArgumentException("Entity does not belong to the partition.");
@@ -67,15 +67,15 @@ public Task<bool> DeleteAsync(string rowKey, CancellationToken cancellation = de
6767
=> repository.DeleteAsync(PartitionKey, rowKey, cancellation);
6868

6969
/// <inheritdoc />
70-
public IAsyncEnumerable<ITableEntity> EnumerateAsync(CancellationToken cancellation = default)
70+
public IAsyncEnumerable<TableEntity> EnumerateAsync(CancellationToken cancellation = default)
7171
=> repository.EnumerateAsync(PartitionKey, cancellation);
7272

7373
/// <inheritdoc />
74-
public Task<ITableEntity?> GetAsync(string rowKey, CancellationToken cancellation = default)
74+
public Task<TableEntity?> GetAsync(string rowKey, CancellationToken cancellation = default)
7575
=> repository.GetAsync(PartitionKey, rowKey, cancellation);
7676

7777
/// <inheritdoc />
78-
public Task<ITableEntity> PutAsync(ITableEntity entity, CancellationToken cancellation = default)
78+
public Task<TableEntity> PutAsync(TableEntity entity, CancellationToken cancellation = default)
7979
{
8080
if (!PartitionKey.Equals(entity.PartitionKey, StringComparison.Ordinal))
8181
throw new ArgumentException("Entity does not belong to the partition.");

src/TableStorage/TableEntityRepository.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
namespace Devlooped
1414
{
1515
/// <inheritdoc />
16-
partial class TableEntityRepository : ITableRepository<ITableEntity>
16+
partial class TableEntityRepository : ITableRepository<TableEntity>
1717
{
1818
readonly CloudStorageAccount storageAccount;
1919
readonly Task<TableClient> table;
@@ -50,7 +50,7 @@ public UpdateStrategy UpdateStrategy
5050
}
5151

5252
/// <inheritdoc />
53-
public IQueryable<ITableEntity> CreateQuery()
53+
public IQueryable<TableEntity> CreateQuery()
5454
=> throw new NotImplementedException();
5555
//=> storageAccount.CreateCloudTableClient().GetTableReference(TableName).CreateQuery<DynamicTableEntity>();
5656

@@ -65,11 +65,11 @@ public async Task<bool> DeleteAsync(string partitionKey, string rowKey, Cancella
6565
}
6666

6767
/// <inheritdoc />
68-
public Task<bool> DeleteAsync(ITableEntity entity, CancellationToken cancellation = default)
68+
public Task<bool> DeleteAsync(TableEntity entity, CancellationToken cancellation = default)
6969
=> DeleteAsync(entity.PartitionKey, entity.RowKey, cancellation);
7070

7171
/// <inheritdoc />
72-
public async IAsyncEnumerable<ITableEntity> EnumerateAsync(string? partitionKey = default, [EnumeratorCancellation] CancellationToken cancellation = default)
72+
public async IAsyncEnumerable<TableEntity> EnumerateAsync(string? partitionKey = default, [EnumeratorCancellation] CancellationToken cancellation = default)
7373
{
7474
var table = await this.table;
7575
var filter = default(string);
@@ -83,7 +83,7 @@ public async IAsyncEnumerable<ITableEntity> EnumerateAsync(string? partitionKey
8383
}
8484

8585
/// <inheritdoc />
86-
public async Task<ITableEntity?> GetAsync(string partitionKey, string rowKey, CancellationToken cancellation = default)
86+
public async Task<TableEntity?> GetAsync(string partitionKey, string rowKey, CancellationToken cancellation = default)
8787
{
8888
var table = await this.table.ConfigureAwait(false);
8989

@@ -101,7 +101,7 @@ public async IAsyncEnumerable<ITableEntity> EnumerateAsync(string? partitionKey
101101
}
102102

103103
/// <inheritdoc />
104-
public async Task<ITableEntity> PutAsync(ITableEntity entity, CancellationToken cancellation = default)
104+
public async Task<TableEntity> PutAsync(TableEntity entity, CancellationToken cancellation = default)
105105
{
106106
var table = await this.table.ConfigureAwait(false);
107107

src/TableStorage/TablePartition.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ static partial class TablePartition
3030
/// <param name="partitionKey">Fixed partition key to scope entity persistence.</param>
3131
/// <param name="updateMode">Update mode for existing entities. Defaults to <see cref="TableUpdateMode.Merge"/>.</param>
3232
/// <returns>The new <see cref="ITablePartition{TEntity}"/>.</returns>
33-
public static ITablePartition<ITableEntity> Create(CloudStorageAccount storageAccount, string tableName, string partitionKey, TableUpdateMode updateMode = TableUpdateMode.Merge)
33+
public static ITablePartition<TableEntity> Create(CloudStorageAccount storageAccount, string tableName, string partitionKey, TableUpdateMode updateMode = TableUpdateMode.Merge)
3434
=> new TableEntityPartition(storageAccount, tableName, partitionKey) { UpdateMode = updateMode };
3535

3636
/// <summary>

src/TableStorage/TableRepository.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ static partial class TableRepository
1717
static readonly ConcurrentDictionary<Type, string> defaultTableNames = new();
1818

1919
/// <summary>
20-
/// Creates an <see cref="ITableRepository{ITableEntity}"/> repository.
20+
/// Creates an <see cref="ITableRepository{TableEntity}"/> repository.
2121
/// </summary>
2222
/// <param name="storageAccount">The storage account to use.</param>
2323
/// <param name="tableName">Table name to use.</param>
2424
/// <param name="updateMode">Update mode for existing entities. Defaults to <see cref="TableUpdateMode.Merge"/>.</param>
2525
/// <returns>The new <see cref="ITableRepository{ITableEntity}"/>.</returns>
26-
public static ITableRepository<ITableEntity> Create(
26+
public static ITableRepository<TableEntity> Create(
2727
CloudStorageAccount storageAccount,
2828
string tableName,
2929
TableUpdateMode updateMode = TableUpdateMode.Merge)

src/Tests/RepositoryTests.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,14 @@ public void DefaultTableNameUsesAttribute()
200200
public async Task TableEntityEndToEnd()
201201
{
202202
var repo = TableRepository.Create(CloudStorageAccount.DevelopmentStorageAccount, "Entities");
203-
var entity = await repo.PutAsync(new TableEntity("123", "Foo"));
203+
var entity = await repo.PutAsync(new TableEntity("123", "Foo")
204+
{
205+
{ "Bar", "Baz" }
206+
});
204207

205208
Assert.Equal("123", entity.PartitionKey);
206209
Assert.Equal("Foo", entity.RowKey);
210+
Assert.Equal("Baz", entity["Bar"]);
207211

208212
var saved = await repo.GetAsync("123", "Foo");
209213

src/Tests/xunit.runner.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"methodDisplay": "method",
3+
"preEnumerateTheories": true
4+
}

0 commit comments

Comments
 (0)