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

DynamoDBEntryConversion AOT compatibility #3300

Open
wants to merge 12 commits into
base: v4-development
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using Amazon.DynamoDBv2.DataModel;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.Util.Internal;

#if NETSTANDARD
using Amazon.Runtime.Internal.Util;
Expand Down Expand Up @@ -71,9 +71,6 @@ internal enum ConversionSchema
/// A collection of converters capable of converting between
/// .NET and DynamoDB objects.
/// </summary>
#if NET8_0_OR_GREATER
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(Amazon.DynamoDBv2.Custom.Internal.InternalConstants.RequiresUnreferencedCodeMessage)]
#endif
public class DynamoDBEntryConversion
{
#region Static members
Expand Down Expand Up @@ -360,6 +357,7 @@ internal IEnumerable<DynamoDBEntry> ConvertToEntries<T>(IEnumerable<T> values)
//foreach (var value in values)
// yield return ConvertToEntry(value);
}

internal IEnumerable<object> ConvertFromEntries(Type elementType, IEnumerable<DynamoDBEntry> entries)
{
if (entries == null) throw new ArgumentNullException("entries");
Expand All @@ -368,6 +366,14 @@ internal IEnumerable<object> ConvertFromEntries(Type elementType, IEnumerable<Dy
yield return ConvertFromEntry(elementType, entry);
}

internal IEnumerable<T> ConvertFromEntries<T>(IEnumerable<DynamoDBEntry> entries)
Copy link
Contributor Author

@Dreamescaper Dreamescaper Aug 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an existing generic method ConvertFromEntry<T>, but there was no generic method for multiple entries

{
if (entries == null) throw new ArgumentNullException("entries");

foreach (var entry in entries)
yield return ConvertFromEntry<T>(entry);
}

internal PrimitiveList ItemsToPrimitiveList(IEnumerable items)
{
var inputType = items.GetType();
Expand All @@ -384,59 +390,24 @@ internal PrimitiveList ItemsToPrimitiveList(IEnumerable items)
private ConverterCache ConverterCache = new ConverterCache();
private ConversionSchema OriginalConversion;

internal void AddConverter(Converter converter)
internal void AddConverterFactory(ConverterFactory factory)
{
ConverterCache.AddConverter(converter, this);
if (IsImmutable)
throw new InvalidOperationException("Adding converters to immutable conversion is not supported. The conversion must be cloned first.");

ConverterCache.AddConverterFactory(factory);
}

private void SetV1Converters()
{
AddConverter(new ByteConverterV1());
AddConverter(new SByteConverterV1());
AddConverter(new UInt16ConverterV1());
AddConverter(new Int16ConverterV1());
AddConverter(new UInt32ConverterV1());
AddConverter(new Int32ConverterV1());
AddConverter(new UInt64ConverterV1());
AddConverter(new Int64ConverterV1());
AddConverter(new SingleConverterV1());
AddConverter(new DoubleConverterV1());
AddConverter(new DecimalConverterV1());
AddConverter(new CharConverterV1());
AddConverter(new StringConverterV1());
AddConverter(new DateTimeConverterV1());
AddConverter(new GuidConverterV1());
AddConverter(new BytesConverterV1());
AddConverter(new MemoryStreamConverterV1());
AddConverter(new EnumConverterV1());
AddConverter(new BoolConverterV1());
AddConverter(new PrimitiveCollectionConverterV1());
AddConverter(new DictionaryConverterV1());
AddConverterFactory(new DefaultConverterFactoryV1(this));
AddConverterFactory(new CollectionConverterFactoryV1(this));
}

private void SetV2Converters()
{
AddConverter(new ByteConverterV2());
AddConverter(new SByteConverterV2());
AddConverter(new UInt16ConverterV2());
AddConverter(new Int16ConverterV2());
AddConverter(new UInt32ConverterV2());
AddConverter(new Int32ConverterV2());
AddConverter(new UInt64ConverterV2());
AddConverter(new Int64ConverterV2());
AddConverter(new SingleConverterV2());
AddConverter(new DoubleConverterV2());
AddConverter(new DecimalConverterV2());
AddConverter(new CharConverterV2());
AddConverter(new StringConverterV2());
AddConverter(new DateTimeConverterV2());
AddConverter(new GuidConverterV2());
AddConverter(new BytesConverterV2());
AddConverter(new MemoryStreamConverterV2());
AddConverter(new DictionaryConverterV2());
AddConverter(new EnumConverterV2());
AddConverter(new BoolConverterV2());
AddConverter(new CollectionConverterV2());
AddConverterFactory(new DefaultConverterFactoryV2(this));
AddConverterFactory(new CollectionConverterFactoryV2(this));
}

// Converts items to Primitives.
Expand Down Expand Up @@ -468,14 +439,30 @@ private IEnumerable<Primitive> ToPrimitives(IEnumerable items, Type elementType)
#endregion
}

internal abstract class Converter
internal abstract class ConverterFactory
{
/// <summary>
/// Returns all types for which it can be used.
/// </summary>
/// <returns></returns>
public abstract IEnumerable<Type> GetTargetTypes();
private readonly DynamoDBEntryConversion _conversion;

protected ConverterFactory(DynamoDBEntryConversion conversion)
{
_conversion = conversion;
}

public Converter GetConverter(Type type)
{
var converter = CreateConverter(type);

if(converter != null)
converter.Conversion = _conversion;

return converter;
}

protected abstract Converter CreateConverter(Type type);
}

internal abstract class Converter
{
/// <summary>
/// Conversion that this converter is part of.
/// This field is set by DynamoDBEntryConversion when the Converter
Expand Down Expand Up @@ -651,19 +638,6 @@ public virtual bool TryFrom(Document d, Type targetType, out object result)

internal abstract class Converter<T> : Converter
{
public override IEnumerable<Type> GetTargetTypes()
{
var type = typeof(T);
yield return type;

if (type.IsValueType)
{
//yield return typeof(Nullable<T>);
var nullableType = typeof(Nullable<>).MakeGenericType(type);
yield return nullableType;
}
}

public override bool TryTo(object value, out DynamoDBBool b)
{
return TryTo((T)value, out b);
Expand Down Expand Up @@ -714,60 +688,60 @@ protected virtual bool TryTo(T value, out Document d)
public override bool TryFrom(DynamoDBBool b, Type targetType, out object result)
{
T t;
var output = TryFrom(b, targetType, out t);
var output = TryFrom(b, out t);
result = t;
return output;
}
public override bool TryFrom(Primitive p, Type targetType, out object result)
{
T t;
var output = TryFrom(p, targetType, out t);
var output = TryFrom(p, out t);
result = t;
return output;
}
public override bool TryFrom(PrimitiveList pl, Type targetType, out object result)
{
T t;
var output = TryFrom(pl, targetType, out t);
var output = TryFrom(pl, out t);
result = t;
return output;
}
public override bool TryFrom(DynamoDBList l, Type targetType, out object result)
{
T t;
var output = TryFrom(l, targetType, out t);
var output = TryFrom(l, out t);
result = t;
return output;
}
public override bool TryFrom(Document d, Type targetType, out object result)
{
T t;
var output = TryFrom(d, targetType, out t);
var output = TryFrom(d, out t);
result = t;
return output;
}

protected virtual bool TryFrom(DynamoDBBool b, Type targetType, out T result)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a separate targetType parameter for a generic Converter kindof defeats the purpose, and not really AOT-friendly, therefore I have removed it.

The only Converter to actually use it is EnumConverter: Converter<Enum>. But it doesn't make any value to have an Enum generic argument, so I've updated it to inherit from non-generic Converter.

(Not a public API, so no breaking changes here)

protected virtual bool TryFrom(DynamoDBBool b, out T result)
{
result = default(T);
return false;
}
protected virtual bool TryFrom(Primitive p, Type targetType, out T result)
protected virtual bool TryFrom(Primitive p, out T result)
{
result = default(T);
return false;
}
protected virtual bool TryFrom(PrimitiveList pl, Type targetType, out T result)
protected virtual bool TryFrom(PrimitiveList pl, out T result)
{
result = default(T);
return false;
}
protected virtual bool TryFrom(DynamoDBList l, Type targetType, out T result)
protected virtual bool TryFrom(DynamoDBList l, out T result)
{
result = default(T);
return false;
}
protected virtual bool TryFrom(Document d, Type targetType, out T result)
protected virtual bool TryFrom(Document d, out T result)
{
result = default(T);
return false;
Expand All @@ -777,29 +751,20 @@ protected virtual bool TryFrom(Document d, Type targetType, out T result)
internal class ConverterCache
{
private static Type EnumType = typeof(Enum);
private Dictionary<Type, Converter> Cache = new Dictionary<Type, Converter>();
private readonly ConcurrentDictionary<Type, Converter> Cache = new ConcurrentDictionary<Type, Converter>();
private readonly List<ConverterFactory> Factories = new List<ConverterFactory>();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously all the converters were created upfront, but now they are created lazily, when requested. Therefore, ConcurrentDictionary is used for cache, as it is possible that converter is requested from multiple threads at the same time.


public bool HasConverter(Type type)
{
Converter converter;
return TryGetConverter(type, out converter);
return TryGetConverter(type, out _);
}
public void AddConverter(Converter converter, DynamoDBEntryConversion conversion)
{
if (converter == null)
throw new ArgumentNullException("converter");
if (conversion == null)
throw new ArgumentNullException("conversion");

if (conversion.IsImmutable)
throw new InvalidOperationException("Adding converters to immutable conversion is not supported. The conversion must be cloned first.");
public void AddConverterFactory(ConverterFactory factory)
{
if (factory == null)
throw new ArgumentNullException(nameof(factory));

converter.Conversion = conversion;
var types = converter.GetTargetTypes();
foreach (var type in types)
{
Cache[type] = converter;
}
Factories.Add(factory);
}

public Converter GetConverter(Type type)
Expand All @@ -817,9 +782,23 @@ public bool TryGetConverter(Type type, out Converter converter)

// all enums use the same converter, one for Enum
if (type.IsEnum)
return Cache.TryGetValue(EnumType, out converter);
type = EnumType;

return Cache.TryGetValue(type, out converter);
if (Cache.TryGetValue(type, out converter))
return converter != null;

foreach (var factory in Factories)
{
converter = factory.GetConverter(type);
if (converter != null)
{
Cache[type] = converter;
return true;
}
}

Cache[type] = null;
return false;
}
}
}
Loading