Skip to content

Commit

Permalink
Improve dynamic entity support by exposing TableEntity directly
Browse files Browse the repository at this point in the history
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
  • Loading branch information
kzu committed Aug 15, 2022
1 parent 8750fb3 commit 21d7a98
Show file tree
Hide file tree
Showing 8 changed files with 39 additions and 31 deletions.
28 changes: 14 additions & 14 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,9 @@ it with `[Browsable(false)]` and it will be skipped when persisting and reading
### TableEntity Support

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

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

// Enumerate all regions within the partition as plain TableEntities
await foreach (ITableEntity region in repo.EnumerateAsync())
await foreach (TableEntity region in repo.EnumerateAsync())
Console.WriteLine(region.RowKey);
```

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

```csharp
await repo.PutAsync(new DynamicTableEntity("Book", "9781473217386", "*", new Dictionary<string, EntityProperty>
{
{ "Title", EntityProperty.GeneratePropertyForString("Neuromancer") },
{ "Price", EntityProperty.GeneratePropertyForDouble(7.32) },
}));
await repo.PutAsync(
new TableEntity("Book", "9781473217386")
{
["Title"] = "Neuromancer",
["Price"] = 7.32
});

var entity = (DynamicTableEntity)await repo.GetAsync("Book", "9781473217386");
var entity = await repo.GetAsync("Book", "9781473217386");

Assert.Equal("Title", entity.Properties["Neuromancer"].StringValue);
Assert.Equal(7.32, entity.Properties["Price"].DoubleValue);
Assert.Equal("Neuromancer", entity["Title"]);
Assert.Equal(7.32, (double)entity["Price"]);
```

## Installation
Expand Down
2 changes: 1 addition & 1 deletion src/TableStorage.Newtonsoft/JsonDocumentSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public JsonDocumentSerializer()
class DateOnlyJsonConverter : JsonConverter<DateOnly>
{
public override DateOnly ReadJson(JsonReader reader, Type objectType, DateOnly existingValue, bool hasExistingValue, JsonSerializer serializer)
=> DateOnly.Parse((string)reader.Value, CultureInfo.InvariantCulture);
=> DateOnly.Parse((string)reader.Value!, CultureInfo.InvariantCulture);

public override void WriteJson(JsonWriter writer, DateOnly value, JsonSerializer serializer)
=> writer.WriteValue(value.ToString("O", CultureInfo.InvariantCulture));
Expand Down
12 changes: 6 additions & 6 deletions src/TableStorage/TableEntityPartition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
namespace Devlooped
{
/// <inheritdoc />
partial class TableEntityPartition : ITablePartition<ITableEntity>
partial class TableEntityPartition : ITablePartition<TableEntity>
{
readonly TableEntityRepository repository;

Expand Down Expand Up @@ -51,10 +51,10 @@ public UpdateStrategy UpdateStrategy
}

/// <inheritdoc />
public IQueryable<ITableEntity> CreateQuery() => repository.CreateQuery().Where(x => x.PartitionKey == PartitionKey);
public IQueryable<TableEntity> CreateQuery() => repository.CreateQuery().Where(x => x.PartitionKey == PartitionKey);

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

/// <inheritdoc />
public IAsyncEnumerable<ITableEntity> EnumerateAsync(CancellationToken cancellation = default)
public IAsyncEnumerable<TableEntity> EnumerateAsync(CancellationToken cancellation = default)
=> repository.EnumerateAsync(PartitionKey, cancellation);

/// <inheritdoc />
public Task<ITableEntity?> GetAsync(string rowKey, CancellationToken cancellation = default)
public Task<TableEntity?> GetAsync(string rowKey, CancellationToken cancellation = default)
=> repository.GetAsync(PartitionKey, rowKey, cancellation);

/// <inheritdoc />
public Task<ITableEntity> PutAsync(ITableEntity entity, CancellationToken cancellation = default)
public Task<TableEntity> PutAsync(TableEntity entity, CancellationToken cancellation = default)
{
if (!PartitionKey.Equals(entity.PartitionKey, StringComparison.Ordinal))
throw new ArgumentException("Entity does not belong to the partition.");
Expand Down
12 changes: 6 additions & 6 deletions src/TableStorage/TableEntityRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
namespace Devlooped
{
/// <inheritdoc />
partial class TableEntityRepository : ITableRepository<ITableEntity>
partial class TableEntityRepository : ITableRepository<TableEntity>
{
readonly CloudStorageAccount storageAccount;
readonly Task<TableClient> table;
Expand Down Expand Up @@ -50,7 +50,7 @@ public UpdateStrategy UpdateStrategy
}

/// <inheritdoc />
public IQueryable<ITableEntity> CreateQuery()
public IQueryable<TableEntity> CreateQuery()
=> throw new NotImplementedException();
//=> storageAccount.CreateCloudTableClient().GetTableReference(TableName).CreateQuery<DynamicTableEntity>();

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

/// <inheritdoc />
public Task<bool> DeleteAsync(ITableEntity entity, CancellationToken cancellation = default)
public Task<bool> DeleteAsync(TableEntity entity, CancellationToken cancellation = default)
=> DeleteAsync(entity.PartitionKey, entity.RowKey, cancellation);

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

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

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

/// <inheritdoc />
public async Task<ITableEntity> PutAsync(ITableEntity entity, CancellationToken cancellation = default)
public async Task<TableEntity> PutAsync(TableEntity entity, CancellationToken cancellation = default)
{
var table = await this.table.ConfigureAwait(false);

Expand Down
2 changes: 1 addition & 1 deletion src/TableStorage/TablePartition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ static partial class TablePartition
/// <param name="partitionKey">Fixed partition key to scope entity persistence.</param>
/// <param name="updateMode">Update mode for existing entities. Defaults to <see cref="TableUpdateMode.Merge"/>.</param>
/// <returns>The new <see cref="ITablePartition{TEntity}"/>.</returns>
public static ITablePartition<ITableEntity> Create(CloudStorageAccount storageAccount, string tableName, string partitionKey, TableUpdateMode updateMode = TableUpdateMode.Merge)
public static ITablePartition<TableEntity> Create(CloudStorageAccount storageAccount, string tableName, string partitionKey, TableUpdateMode updateMode = TableUpdateMode.Merge)
=> new TableEntityPartition(storageAccount, tableName, partitionKey) { UpdateMode = updateMode };

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions src/TableStorage/TableRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ static partial class TableRepository
static readonly ConcurrentDictionary<Type, string> defaultTableNames = new();

/// <summary>
/// Creates an <see cref="ITableRepository{ITableEntity}"/> repository.
/// Creates an <see cref="ITableRepository{TableEntity}"/> repository.
/// </summary>
/// <param name="storageAccount">The storage account to use.</param>
/// <param name="tableName">Table name to use.</param>
/// <param name="updateMode">Update mode for existing entities. Defaults to <see cref="TableUpdateMode.Merge"/>.</param>
/// <returns>The new <see cref="ITableRepository{ITableEntity}"/>.</returns>
public static ITableRepository<ITableEntity> Create(
public static ITableRepository<TableEntity> Create(
CloudStorageAccount storageAccount,
string tableName,
TableUpdateMode updateMode = TableUpdateMode.Merge)
Expand Down
6 changes: 5 additions & 1 deletion src/Tests/RepositoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,14 @@ public void DefaultTableNameUsesAttribute()
public async Task TableEntityEndToEnd()
{
var repo = TableRepository.Create(CloudStorageAccount.DevelopmentStorageAccount, "Entities");
var entity = await repo.PutAsync(new TableEntity("123", "Foo"));
var entity = await repo.PutAsync(new TableEntity("123", "Foo")
{
{ "Bar", "Baz" }
});

Assert.Equal("123", entity.PartitionKey);
Assert.Equal("Foo", entity.RowKey);
Assert.Equal("Baz", entity["Bar"]);

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

Expand Down
4 changes: 4 additions & 0 deletions src/Tests/xunit.runner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"methodDisplay": "method",
"preEnumerateTheories": true
}

0 comments on commit 21d7a98

Please sign in to comment.