Skip to content

Commit

Permalink
v3.21.1 (#107)
Browse files Browse the repository at this point in the history
* v3.21.1
- *Fixed:* `Mapper.MapSameTypeWithSourceValue` added (defaults to `true`) to map the source value to the destination value where the types are the same; previously this would result in an exception unless added explicitly. The `Mapper.SameTypeMapper` enables.
- *Fixed:* `ReferenceDataOrchestrator.GetAllTypesInNamespace` added to get all the `IReferenceData` types in the specified namespace. Needed for the likes of the `CosmosDbBatch.ImportValueBatchAsync` where a list of types is required.

* Fix compiler warning as error.
Fix net8 system.text.json version update.
  • Loading branch information
chullybun committed Jul 10, 2024
1 parent c6da97e commit cb9ed1d
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 23 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Represents the **NuGet** versions.

## v3.21.1
- *Fixed:* `Mapper.MapSameTypeWithSourceValue` added (defaults to `true`) to map the source value to the destination value where the types are the same; previously this would result in an exception unless added explicitly. The `Mapper.SameTypeMapper` enables.
- *Fixed:* `ReferenceDataOrchestrator.GetAllTypesInNamespace` added to get all the `IReferenceData` types in the specified namespace. Needed for the likes of the `CosmosDbBatch.ImportValueBatchAsync` where a list of types is required.

## v3.21.0
- *Enhancement*: `CoreEx.Cosmos` improvements:
- Added `CosmosDbArgs` to `CosmosDbContainerBase` to allow per container configuration where required.
Expand Down
4 changes: 2 additions & 2 deletions Common.targets
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>3.21.0</Version>
<Version>3.21.1</Version>
<LangVersion>preview</LangVersion>
<Authors>Avanade</Authors>
<Company>Avanade</Company>
Expand All @@ -27,7 +27,7 @@
<IsPackable>true</IsPackable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>

<ItemGroup>
Expand Down
15 changes: 3 additions & 12 deletions src/CoreEx.Cosmos/CosmosDbQueryBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,17 @@ namespace CoreEx.Cosmos
/// <typeparam name="T">The resultant <see cref="Type"/>.</typeparam>
/// <typeparam name="TModel">The cosmos model <see cref="Type"/>.</typeparam>
/// <typeparam name="TSelf">The <see cref="Type"/> itself.</typeparam>
public abstract class CosmosDbQueryBase<T, TModel, TSelf> where T : class, new() where TModel : class, new() where TSelf : CosmosDbQueryBase<T, TModel, TSelf>
public abstract class CosmosDbQueryBase<T, TModel, TSelf>(ICosmosDbContainer container, CosmosDbArgs dbArgs) where T : class, new() where TModel : class, new() where TSelf : CosmosDbQueryBase<T, TModel, TSelf>
{
/// <summary>
/// Initializes a new instance of the <see cref="CosmosDbQueryBase{T, TModel, TSelf}"/> class.
/// </summary>
protected CosmosDbQueryBase(ICosmosDbContainer container, CosmosDbArgs dbArgs)
{
Container = container.ThrowIfNull(nameof(container));
QueryArgs = dbArgs;
}

/// <summary>
/// Gets the <see cref="ICosmosDbContainer"/>.
/// </summary>
public ICosmosDbContainer Container { get; }
public ICosmosDbContainer Container { get; } = container.ThrowIfNull(nameof(container));

/// <summary>
/// Gets the <see cref="CosmosDbArgs"/>.
/// </summary>
public CosmosDbArgs QueryArgs;
public CosmosDbArgs QueryArgs = dbArgs;

/// <summary>
/// Gets the <see cref="PagingResult"/>.
Expand Down
2 changes: 1 addition & 1 deletion src/CoreEx/CoreEx.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<PackageReference Include="System.Collections.Immutable" Version="8.0.0" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="8.0.1" />
<PackageReference Include="System.Memory.Data" Version="8.0.0" />
<PackageReference Include="System.Text.Json" Version="8.0.3" />
<PackageReference Include="System.Text.Json" Version="8.0.4" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
Expand Down
123 changes: 115 additions & 8 deletions src/CoreEx/Mapping/Mapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,18 @@ public Mapper()
}

/// <summary>
/// Indicates whether to convert empty collections to <c>null</c> where supported. Defaults to <c>true</c>.
/// Indicates whether to convert empty collections to <c>null</c> where supported.
/// </summary>
/// <remarks>Defaults to <c>true</c>.</remarks>
public bool ConvertEmptyCollectionsToNull { get; set; } = true;

/// <summary>
/// Indicates whether to allow same to same type mapping (where explicitly not registered) as always returning the source value.
/// </summary>
/// <remarks>Defaults to <c>true</c>.
/// <para>Creates and registers an instance of the <see cref="SameTypeMapper{TSame}"/> on first use.</para></remarks>
public bool MapSameTypeWithSourceValue { get; set; } = true;

/// <summary>
/// Register (adds) all the <see cref="IMapper{TSource, TDestination}"/> and <see cref="IBidirectionalMapper{TFrom, TTo}"/> types (instances) from the <see cref="Assembly"/> from the specified <typeparamref name="TAssembly"/> <see cref="Type"/>.
/// </summary>
Expand Down Expand Up @@ -152,35 +160,87 @@ private void Register(Type s, Type d, IMapperBase mapper)
/// <exception cref="InvalidOperationException">Thrown where not previously registered.</exception>
public IMapperBase GetMapper(Type source, Type destination)
{
if (_mappers.TryGetValue((source, destination), out var mapper))
if (TryGetMapper(source, destination, out var mapper))
return mapper;

throw new InvalidOperationException($"No mapper has been registered for source '{source.FullName}' and destination '{destination.FullName}' types.");
}

/// <summary>
/// Gets the <see cref="IMapper{TSource, TDestination}"/> for the specified <typeparamref name="TSource"/> and <typeparamref name="TDestination"/> types as previously <see cref="Register{TSource, TDestination}(IMapper{TSource, TDestination})">registered</see>.
/// </summary>
/// <typeparam name="TSource">The source <see cref="Type"/>.</typeparam>
/// <typeparam name="TDestination">The destination <see cref="Type"/>.</typeparam>
/// <returns>The previously registered <see cref="IMapper{TSource, TDestination}"/>.</returns>
/// <exception cref="InvalidOperationException">Thrown where not previously registered.</exception>
public IMapper<TSource, TDestination> GetMapper<TSource, TDestination>() => (IMapper<TSource, TDestination>)GetMapper(typeof(TSource), typeof(TDestination));

/// <summary>
/// Try and get the mapper for the <paramref name="source"/> and <paramref name="destination"/> types.
/// </summary>
/// <param name="source">The source <see cref="Type"/>.</param>
/// <param name="destination">The destination <see cref="Type"/>.</param>
/// <param name="mapper">The previously registered <see cref="IMapper{TSource, TDestination}"/>.</param>
/// <returns><c>true</c> where found; otherwise, <c>false</c>.</returns>
public bool TryGetMapper(Type source, Type destination, [NotNullWhen(true)] out IMapperBase? mapper)
{
if (_mappers.TryGetValue((source, destination), out mapper))
return true;

// Check if the types are collection and automatically create where possible.
var si = TypeReflector.GetCollectionItemType(source);
if (si.TypeCode == TypeReflectorTypeCode.ICollection)
{
var di = TypeReflector.GetCollectionItemType(destination);
if (di.TypeCode == TypeReflectorTypeCode.ICollection)
return _mappers.GetOrAdd((source, destination), _ =>
{
mapper = _mappers.GetOrAdd((source, destination), _ =>
{
var t = typeof(CollectionMapper<,,,>).MakeGenericType(source, si.ItemType!, destination, di.ItemType!);
var mapper = (IMapperBase)Activator.CreateInstance(t)!;
mapper.Owner = this;
return mapper;
});

return true;
}
}

throw new InvalidOperationException($"No mapper has been registered for source '{source.FullName}' and destination '{destination.FullName}' types.");
// Check if the types are the same and automatically create where configured to do so.
if (MapSameTypeWithSourceValue && si.TypeCode == TypeReflectorTypeCode.Complex && source == destination)
{
mapper = _mappers.GetOrAdd((source, destination), _ =>
{
var t = typeof(SameTypeMapper<>).MakeGenericType(source);
var mapper = (IMapperBase)Activator.CreateInstance(t)!;
mapper.Owner = this;
return mapper;
});

return true;
}

return false;
}

/// <summary>
/// Gets the <see cref="IMapper{TSource, TDestination}"/> for the specified <typeparamref name="TSource"/> and <typeparamref name="TDestination"/> types as previously <see cref="Register{TSource, TDestination}(IMapper{TSource, TDestination})">registered</see>.
/// Try and get the mapper for the <typeparamref name="TSource"/> and <typeparamref name="TDestination"/> types.
/// </summary>
/// <typeparam name="TSource">The source <see cref="Type"/>.</typeparam>
/// <typeparam name="TDestination">The destination <see cref="Type"/>.</typeparam>
/// <returns>The previously registered <see cref="IMapper{TSource, TDestination}"/>.</returns>
/// <exception cref="InvalidOperationException">Thrown where not previously registered.</exception>
public IMapper<TSource, TDestination> GetMapper<TSource, TDestination>() => (IMapper<TSource, TDestination>)GetMapper(typeof(TSource), typeof(TDestination));
/// <param name="mapper">The previously registered <see cref="IMapper{TSource, TDestination}"/>.</param>
/// <returns><c>true</c> where found; otherwise, <c>false</c>.</returns>
public bool TryGetMapper<TSource, TDestination>([NotNullWhen(true)] out IMapper<TSource, TDestination>? mapper)
{
if (TryGetMapper(typeof(TSource), typeof(TDestination), out var m))
{
mapper = (IMapper<TSource, TDestination>)m;
return true;
}

mapper = default;
return false;
}

/// <inheritdoc/>
[return: NotNullIfNotNull(nameof(source))]
Expand Down Expand Up @@ -215,6 +275,53 @@ public class EmptyMapper : IMapper
public TDestination? Map<TSource, TDestination>(TSource? source, TDestination? destination, OperationTypes operationType = OperationTypes.Unspecified) => throw new NotImplementedException();
}

/// <summary>
/// Represents a same <see cref="Type"/> <see cref="IMapper"/>; i.e. where both the source and destination <see cref="Type"/> are the same.
/// </summary>
/// <typeparam name="TSame">The source and destination <see cref="Type"/>.</typeparam>
public class SameTypeMapper<TSame> : IMapper<TSame, TSame>
{
private Mapper? _mapper;

/// <inheritdoc/>
public Mapper Owner
{
get => _mapper ?? throw new InvalidOperationException("Owner has not been set to a non-null value; this is automatically performed when registered.");
set => _mapper = _mapper is null ? value : throw new InvalidOperationException("Owner can not be changed once set.");
}

/// <inheritdoc/>
/// <remarks>Throws a <see cref="NotSupportedException"/>.</remarks>
public TSame CreateDestination() => throw new NotSupportedException();

/// <inheritdoc/>
/// <remarks>Throws a <see cref="NotSupportedException"/>.</remarks>
public TSame CreateSource() => throw new NotSupportedException();

/// <inheritdoc/>
public bool InitializeDestination(TSame destination) => false;

/// <inheritdoc/>
/// <remarks>Returns <see cref="Entities.IInitial.IsInitial"/> where implemented; otherwise, <c>false</c>.</remarks>
public bool IsSourceInitial(TSame source) => source is Entities.IInitial ii && ii.IsInitial;

/// <inheritdoc/>
/// <remarks>Always returns the <paramref name="source"/> as-is.</remarks>
public TSame? Map(TSame? source, OperationTypes operationType = OperationTypes.Unspecified) => source;

/// <inheritdoc/>
/// <remarks>Always returns the <paramref name="source"/> as-is.</remarks>
public TSame? Map(TSame? source, TSame? destination, OperationTypes operationType = OperationTypes.Unspecified) => source;

/// <inheritdoc/>
/// <remarks>Always returns the <paramref name="source"/> as-is.</remarks>
object? IMapperBase.Map(object? source, OperationTypes operationType) => source;

/// <inheritdoc/>
/// <remarks>Always returns the <paramref name="source"/> as-is.</remarks>
object? IMapperBase.Map(object? source, object? destination, OperationTypes operationType) => destination;
}

/// <summary>
/// When <paramref name="operationType"/> is a <see cref="OperationTypes.Get"/> then the action is invoked.
/// </summary>
Expand Down
8 changes: 8 additions & 0 deletions src/CoreEx/RefData/ReferenceDataOrchestrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -669,5 +669,13 @@ public async Task<ReferenceDataMultiDictionary> GetNamedAsync(IEnumerable<KeyVal
((IReferenceData)rdx).SetInvalid();
return rdx;
}

/// <summary>
/// Gets all the <see cref="IReferenceData"/> types in the same namespace as <typeparamref name="TNamespace"/>.
/// </summary>
/// <typeparam name="TNamespace">The <see cref="Type"/> to infer the namespace.</typeparam>
/// <returns>The <see cref="Type"/> list.</returns>
public static IEnumerable<Type> GetAllTypesInNamespace<TNamespace>() => typeof(TNamespace).Assembly.GetTypes().Where(t => t.Namespace == typeof(TNamespace).Namespace && t.IsClass && !t.IsAbstract && typeof(IReferenceData).IsAssignableFrom(t));

}
}
34 changes: 34 additions & 0 deletions tests/CoreEx.Test/Framework/Mapping/MapperTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,40 @@ public void BidirectionalMapper()
Assert.That(pa2.Id, Is.EqualTo(88));
}

[Test]
public void MapWithSameType_Allowed()
{
var m = new Mapper();
var r = m.TryGetMapper<PersonA, PersonA>(out var sm);
Assert.Multiple(() =>
{
Assert.That(r, Is.True);
Assert.That(sm, Is.Not.Null);
});

var p = new PersonA { Id = 88, Name = "blah" };
var d = sm!.Map(p);
Assert.That(d, Is.Not.Null);
Assert.That(d, Is.SameAs(p));
}

[Test]
public void MapWithSameType_NotAllowed()
{
var m = new Mapper
{
MapSameTypeWithSourceValue = false
};

var r = m.TryGetMapper<PersonA, PersonA>(out var sm);

Assert.Multiple(() =>
{
Assert.That(r, Is.False);
Assert.That(sm, Is.Null);
});
}

public class PersonAMapper : Mapper<PersonA, PersonB>
{
public PersonAMapper()
Expand Down
7 changes: 7 additions & 0 deletions tests/CoreEx.Test/Framework/RefData/ReferenceDataTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1159,6 +1159,13 @@ public void Serialization_NSJ_WithOrchestrator()
Assert.That(td.RefData.IsValid, Is.True);
});
}

[Test]
public void GetAllTypesInNamespace()
{
var types = ReferenceDataOrchestrator.GetAllTypesInNamespace<RefData>().ToList();
Assert.That(types, Has.Count.EqualTo(5));
}
}

public class RefData : ReferenceDataBaseEx<int, RefData>
Expand Down

0 comments on commit cb9ed1d

Please sign in to comment.