Skip to content

Commit

Permalink
Rework binding configuration in Table extension (#26111)
Browse files Browse the repository at this point in the history
  • Loading branch information
pakrym authored Jan 3, 2022
1 parent 5238cf7 commit 2cb0387
Show file tree
Hide file tree
Showing 333 changed files with 174,417 additions and 19,518 deletions.
1 change: 1 addition & 0 deletions sdk/core/Azure.Core.TestFramework/src/TestProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ private TestProxy(string proxyPath)
EnvironmentVariables =
{
["ASPNETCORE_URLS"] = $"http://{IpAddress}:0;https://{IpAddress}:0",
["Logging__LogLevel__Default"] = "Error",
["Logging__LogLevel__Microsoft.Hosting.Lifetime"] = "Information",
["ASPNETCORE_Kestrel__Certificates__Default__Path"] = Path.Combine(
TestEnvironment.RepositoryRoot,
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Azure;
using Azure.Data.Tables;
Expand All @@ -12,8 +11,16 @@
namespace Microsoft.Azure.WebJobs.Extensions.Tables
{
internal class PocoEntityArgumentBinding<TElement> : IArgumentBinding<TableEntityContext>
where TElement : new()
{
private readonly FuncAsyncConverter _pocoToEntityConverter;
private readonly FuncAsyncConverter _entityToPocoConverter;

public PocoEntityArgumentBinding(FuncAsyncConverter entityToPocoConverter, FuncAsyncConverter pocoToEntityConverter)
{
_pocoToEntityConverter = pocoToEntityConverter;
_entityToPocoConverter = entityToPocoConverter;
}

public Type ValueType => typeof(TElement);

public async Task<IValueProvider> BindAsync(TableEntityContext value, ValueBindingContext context)
Expand All @@ -22,27 +29,16 @@ public async Task<IValueProvider> BindAsync(TableEntityContext value, ValueBindi
TableEntity entity;
try
{
var result = await table.GetEntityAsync<TableEntity>(
entity = await table.GetEntityAsync<TableEntity>(
value.PartitionKey, value.RowKey).ConfigureAwait(false);
entity = result.Value;
}
catch (RequestFailedException e) when
(e.Status == 404 && (e.ErrorCode == TableErrorCode.TableNotFound || e.ErrorCode == TableErrorCode.ResourceNotFound))
{
return new NullEntityValueProvider<TElement>(value);
}

TElement userEntity;
if (typeof(TElement) == typeof(TableEntity))
{
userEntity = (TElement)(object) entity;
}
else
{
userEntity = PocoTypeBinder.Shared.Deserialize<TElement>(entity);
}

return new PocoEntityValueBinder<TElement>(value, entity.ETag.ToString(), userEntity);
return new PocoEntityValueBinder<TElement>(value, context, entity, _entityToPocoConverter, _pocoToEntityConverter);
}
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Azure;
using Azure.Data.Tables;
using Microsoft.Azure.WebJobs.Host.Bindings;
using Microsoft.Azure.WebJobs.Host.Protocols;
Expand All @@ -14,50 +13,55 @@ namespace Microsoft.Azure.WebJobs.Extensions.Tables
{
internal class PocoEntityValueBinder<TElement> : IValueBinder, IWatchable, IWatcher
{
private static readonly PocoToTableEntityConverter<TElement> Converter = new();

private readonly TableEntityContext _entityContext;
private readonly string _eTag;
private readonly TElement _value;
private readonly TableEntity _originalProperties;

public PocoEntityValueBinder(TableEntityContext entityContext, string eTag, TElement value)
private readonly ValueBindingContext _valueBindingContext;
private readonly TableEntity _originalEntity;
private readonly FuncAsyncConverter _entityToPocoConverter;
private readonly FuncAsyncConverter _pocoToEntityConverter;

public PocoEntityValueBinder(
TableEntityContext entityContext,
ValueBindingContext context,
TableEntity originalEntity,
FuncAsyncConverter entityToPocoConverter,
FuncAsyncConverter pocoToEntityConverter)
{
_entityContext = entityContext;
_eTag = eTag;
_value = value;
_originalProperties = Converter.Convert(value);
_valueBindingContext = context;
_originalEntity = originalEntity;
_entityToPocoConverter = entityToPocoConverter;
_pocoToEntityConverter = pocoToEntityConverter;
}

public Type Type => typeof(TElement);

public IWatcher Watcher => this;

public bool HasChanged => HasChanges(Converter.Convert(_value));

public Task<object> GetValueAsync()
public async Task<object> GetValueAsync()
{
return Task.FromResult<object>(_value);
var conversionResult = await _entityToPocoConverter(_originalEntity, null, _valueBindingContext).ConfigureAwait(false);
// When bound to TableEntity the _entityToPocoConverter will no-op
// when it happens make a copy of entity so the change tracking still works
if (ReferenceEquals(conversionResult, _originalEntity))
{
return new TableEntity(_originalEntity);
}

return conversionResult;
}

public Task SetValueAsync(object value, CancellationToken cancellationToken)
public async Task SetValueAsync(object value, CancellationToken cancellationToken)
{
// Not ByRef, so can ignore value argument.
TableEntity entity = Converter.Convert(_value);
if (!Converter.ConvertsPartitionKey)
{
entity.PartitionKey = _entityContext.PartitionKey;
}
TableEntity entity = (TableEntity)await _pocoToEntityConverter(value, null, _valueBindingContext).ConfigureAwait(false);

if (!Converter.ConvertsRowKey)
if (entity.ETag == default)
{
entity.RowKey = _entityContext.RowKey;
entity.ETag = _originalEntity.ETag;
}

if (!Converter.ConvertsETag)
{
entity.ETag = new ETag(_eTag);
}
entity.RowKey ??= _originalEntity.RowKey;
entity.PartitionKey ??= _originalEntity.PartitionKey;

if (entity.PartitionKey != _entityContext.PartitionKey)
{
Expand All @@ -71,27 +75,55 @@ public Task SetValueAsync(object value, CancellationToken cancellationToken)
"When binding to a table entity, the row key must not be changed.");
}

if (HasChanges(entity))
if (HasChanges(_originalEntity, entity))
{
return _entityContext.Table.UpdateEntityAsync(entity, entity.ETag, TableUpdateMode.Replace, cancellationToken: cancellationToken);
await _entityContext.Table.UpdateEntityAsync(entity, entity.ETag, TableUpdateMode.Replace, cancellationToken: cancellationToken).ConfigureAwait(false);
}

return Task.FromResult(0);
}

public string ToInvokeString()
{
return _entityContext.ToInvokeString();
}

public ParameterLog GetStatus()
internal static bool HasChanges(TableEntity originalProperties, TableEntity currentProperties)
{
return HasChanged ? new TableParameterLog { EntitiesWritten = 1 } : null;
var allKeys = new HashSet<string>();
allKeys.UnionWith(originalProperties.Keys);
allKeys.UnionWith(currentProperties.Keys);
// Ignore timestamp in matching
allKeys.Remove("Timestamp");

foreach (string key in allKeys)
{
originalProperties.TryGetValue(key, out var originalValue);
currentProperties.TryGetValue(key, out var newValue);
if (originalValue == null)
{
if (newValue != null)
{
return true;
}
else
{
continue;
}
}

if (!originalValue.Equals(newValue))
{
return true;
}
}

return false;
}

private bool HasChanges(TableEntity current)
public ParameterLog GetStatus()
{
return TableEntityValueBinder.HasChanges(_originalProperties, current);
// TODO: check if we still need the log feature and fix
// To take entity changes into account
return new TableParameterLog { EntitiesWritten = 1 };
}
}
}
Loading

0 comments on commit 2cb0387

Please sign in to comment.