Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework binding configuration in Table extension #26111

  •  
  •  
  •  
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",
pakrym marked this conversation as resolved.
Show resolved Hide resolved
["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
pakrym marked this conversation as resolved.
Show resolved Hide resolved
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 };
pakrym marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Loading