Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
9d2dfd9
consider member value resolvers and value converters for source valid…
lbargaoanu Jun 29, 2022
29f1beb
simplify
lbargaoanu Jun 29, 2022
8552e2e
re-enable configuration only tests
lbargaoanu Jun 30, 2022
18c7f73
try to cover all cases
lbargaoanu Jul 1, 2022
704e5ba
cosmetic
lbargaoanu Jul 6, 2022
ba60290
polymorphic implementation for type converters
lbargaoanu Jul 6, 2022
7eff43f
remove ConstructDestinationUsingServiceLocator
lbargaoanu Jul 7, 2022
3c2ec11
simplify
lbargaoanu Jul 7, 2022
884e22a
remove TypeMap.AsProxy
lbargaoanu Jul 7, 2022
a2c4ba0
remove DestinationTypeToUse
lbargaoanu Jul 7, 2022
42ef755
code reuse
lbargaoanu Jul 7, 2022
821d4fa
add MemberMap.Resolver
lbargaoanu Jul 8, 2022
c38b3dc
code reuse
lbargaoanu Jul 8, 2022
ed3ac6e
Resolver.GetSourceMember
lbargaoanu Jul 8, 2022
99092f4
ClassValueResolver
lbargaoanu Jul 8, 2022
2fa05d6
FuncResolver
lbargaoanu Jul 8, 2022
ce51167
ExpressionResolver
lbargaoanu Jul 8, 2022
8ce0961
remove storage for CustomCtorExpression
lbargaoanu Jul 8, 2022
40cecce
remove storage for TypeMap.CustomMapExpression
lbargaoanu Jul 8, 2022
6cd3631
cosmetic
lbargaoanu Jul 8, 2022
854fa31
implement the convention based resolver in MemberMap
lbargaoanu Jul 9, 2022
1eab5f1
implement CanResolveValue in MemberMap
lbargaoanu Jul 9, 2022
787ae9d
cosmetic
lbargaoanu Jul 9, 2022
9b872ef
remove TypeMap.IsValid
lbargaoanu Jul 10, 2022
63aeef3
DestinationTypeOverride is considered separately
lbargaoanu Jul 10, 2022
c366182
cosmetic
lbargaoanu Jul 10, 2022
8d87e4e
MemberMapDetails
lbargaoanu Jul 10, 2022
20e379d
TypeMapDetails
lbargaoanu Jul 10, 2022
a1c02c7
cosmetic
lbargaoanu Jul 10, 2022
415c1d4
remove TypeMap._orderedPropertyMaps
lbargaoanu Jul 11, 2022
8b733b1
cosmetic
lbargaoanu Jul 11, 2022
999d65f
reuse the constructor map object
lbargaoanu Jul 12, 2022
79fd635
cosmetic
lbargaoanu Jul 12, 2022
449a50b
remove QueryMapperVisitor
lbargaoanu Jul 13, 2022
5b0cf17
hard code the map based projection mappers
lbargaoanu Jul 14, 2022
6fdc82b
IPojectionMapper.IsMatch(TypePair context)
lbargaoanu Jul 15, 2022
ddc934f
cosmetic
lbargaoanu Jul 15, 2022
798ebf2
constructor parameters default values with ProjectTo
lbargaoanu Jul 17, 2022
6024cf3
remove ResolutionContext.Options
lbargaoanu Jul 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
remove ResolutionContext.Options
  • Loading branch information
lbargaoanu committed Jul 24, 2022
commit 6024cf32497a0962bbceb87836678dab29f00ff2
20 changes: 20 additions & 0 deletions docs/12.0-Upgrade-Guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# 12.0 Upgrade Guide

[Release notes](https://github.com/AutoMapper/AutoMapper/releases/tag/v12.0.0).

## Equivalent settings overwrite each other

That applies per map and also per member. For example, you can have only one type converter per map and only one resolver per member.

It might not be obvious that some settings are equivalent. For example, a value converter is a special kind of resolver, so a `ConvertUsing` will overwrite a `MapFrom`
for the same member.

You also cannot have for the same map/member separate configurations for `Map` and `ProjectTo`.

Another possible occurence is with `ForAllMaps` and `ForAllPropertyMaps` when it's possible to overwrite things already set in a particular map.

## `ResolutionContext.Options` was removed

You should use `ResolutionContext.Items` to access the items passed in the `Map` call.

Instead of `ServiceCtor` you should use dependency injection or pass the needed objects in the `Map` call.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ New to AutoMapper? Check out the :doc:`Getting-started` page first.
:caption: Upgrading

API-Changes
12.0-Upgrade-Guide
11.0-Upgrade-Guide
10.0-Upgrade-Guide
9.0-Upgrade-Guide
Expand Down
3 changes: 2 additions & 1 deletion src/AutoMapper/ApiCompatBaseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMappe
InterfacesShouldHaveSameMembers : Interface member 'public TMappingExpression AutoMapper.IMappingExpressionBase<TSource, TDestination, TMappingExpression>.AsProxy()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void AutoMapper.IMappingExpressionBase<TSource, TDestination, TMappingExpression>.AsProxy()' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void AutoMapper.IMappingExpressionBase<TSource, TDestination, TMappingExpression>.AsProxy()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public AutoMapper.IMappingOperationOptions AutoMapper.ResolutionContext.Options.get()' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'AutoMapper.ValueResolverConfiguration' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void AutoMapper.Configuration.MappingExpressionBase<TSource, TDestination, TMappingExpression>.AsProxy()' does not exist in the implementation but it does exist in the contract.
CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMapper.Configuration.Annotations.IgnoreAttribute' changed from '[AttributeUsageAttribute(384)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)]' in the implementation.
Expand All @@ -16,4 +17,4 @@ CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMappe
MembersMustExist : Member 'public System.Linq.IQueryable<TDestination> AutoMapper.QueryableExtensions.Extensions.Map<TSource, TDestination>(System.Linq.IQueryable<TSource>, System.Linq.IQueryable<TDestination>, AutoMapper.IConfigurationProvider)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Collections.Generic.IReadOnlyCollection<System.Reflection.MemberInfo> AutoMapper.QueryableExtensions.MemberVisitor.MemberPath.get()' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'AutoMapper.QueryableExtensions.Impl.MemberAccessQueryMapperVisitor' does not exist in the implementation but it does exist in the contract.
Total Issues: 17
Total Issues: 18
19 changes: 12 additions & 7 deletions src/AutoMapper/Mapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace AutoMapper
{
using QueryableExtensions;
using IObjectMappingOperationOptions = IMappingOperationOptions<object, object>;
using Factory = Func<Type, object>;
using Internal;
public interface IMapperBase
{
Expand Down Expand Up @@ -140,18 +141,22 @@ internal interface IInternalRuntimeMapper : IRuntimeMapper
{
TDestination Map<TSource, TDestination>(TSource source, TDestination destination, ResolutionContext context, Type sourceType = null, Type destinationType = null, MemberMap memberMap = null);
ResolutionContext DefaultContext { get; }
Factory ServiceCtor { get; }
}
public class Mapper : IMapper, IInternalRuntimeMapper
{
private readonly IGlobalConfiguration _configurationProvider;
private readonly Factory _serviceCtor;
public Mapper(IConfigurationProvider configurationProvider) : this(configurationProvider, configurationProvider.Internal().ServiceCtor) { }
public Mapper(IConfigurationProvider configurationProvider, Func<Type, object> serviceCtor)
public Mapper(IConfigurationProvider configurationProvider, Factory serviceCtor)
{
_configurationProvider = (IGlobalConfiguration)configurationProvider ?? throw new ArgumentNullException(nameof(configurationProvider));
DefaultContext = new ResolutionContext(new MappingOperationOptions<object, object>(serviceCtor ?? throw new NullReferenceException(nameof(serviceCtor))), this);
_serviceCtor = serviceCtor ?? throw new NullReferenceException(nameof(serviceCtor));
DefaultContext = new(this);
}
internal ResolutionContext DefaultContext { get; }
ResolutionContext IInternalRuntimeMapper.DefaultContext => DefaultContext;
Factory IInternalRuntimeMapper.ServiceCtor => _serviceCtor;
public IConfigurationProvider ConfigurationProvider => _configurationProvider;
public TDestination Map<TDestination>(object source) => Map(source, default(TDestination));
public TDestination Map<TDestination>(object source, Action<IMappingOperationOptions<object, TDestination>> opts) => Map(source, default, opts);
Expand Down Expand Up @@ -181,19 +186,19 @@ TDestination IInternalRuntimeMapper.Map<TSource, TDestination>(TSource source, T
private TDestination MapWithOptions<TSource, TDestination>(TSource source, TDestination destination, Action<IMappingOperationOptions<TSource, TDestination>> opts,
Type sourceType = null, Type destinationType = null)
{
var typedOptions = new MappingOperationOptions<TSource, TDestination>(DefaultContext.Options.ServiceCtor);
MappingOperationOptions<TSource, TDestination> typedOptions = new(_serviceCtor);
opts(typedOptions);
typedOptions.BeforeMapAction?.Invoke(source, destination);
destination = MapCore(source, destination, new ResolutionContext(typedOptions, this), sourceType, destinationType);
destination = MapCore(source, destination, new(this, typedOptions), sourceType, destinationType);
typedOptions.AfterMapAction?.Invoke(source, destination);
return destination;
}
private TDestination MapCore<TSource, TDestination>(
TSource source, TDestination destination, ResolutionContext context, Type sourceType = null, Type destinationType = null, MemberMap memberMap = null)
{
var runtimeTypes = new TypePair(source?.GetType() ?? sourceType ?? typeof(TSource), destination?.GetType() ?? destinationType ?? typeof(TDestination));
var requestedTypes = new TypePair(typeof(TSource), typeof(TDestination));
var mapRequest = new MapRequest(requestedTypes, runtimeTypes, memberMap);
TypePair requestedTypes = new(typeof(TSource), typeof(TDestination));
TypePair runtimeTypes = new(source?.GetType() ?? sourceType ?? typeof(TSource), destination?.GetType() ?? destinationType ?? typeof(TDestination));
MapRequest mapRequest = new(requestedTypes, runtimeTypes, memberMap);
return _configurationProvider.GetExecutionPlan<TSource, TDestination>(mapRequest)(source, destination, context);
}
}
Expand Down
45 changes: 23 additions & 22 deletions src/AutoMapper/ResolutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,24 @@ public class ResolutionContext : IInternalRuntimeMapper
private Dictionary<ContextCacheKey, object> _instanceCache;
private Dictionary<TypePair, int> _typeDepth;
private readonly IInternalRuntimeMapper _mapper;
internal ResolutionContext(IMappingOperationOptions options, IInternalRuntimeMapper mapper)
private readonly IMappingOperationOptions _options;
internal ResolutionContext(IInternalRuntimeMapper mapper, IMappingOperationOptions options = null)
{
Options = options;
_mapper = mapper;
_options = options;
}
internal ResolutionContext(IInternalRuntimeMapper mapper) : this(mapper.DefaultContext.Options, mapper) { }
/// <summary>
/// Mapping operation options
/// </summary>
public IMappingOperationOptions Options { get; }
/// <summary>
/// Context items from <see cref="Options"/>
/// The items passed in the options of the Map call.
/// </summary>
public IDictionary<string, object> Items
{
get
{
CheckDefault();
return Options.Items;
if (_options == null)
{
ThrowInvalidMap();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could simply return null here but I'm guessing people would keep asking why Items is null.

}
return _options.Items;
}
}
/// <summary>
Expand All @@ -45,7 +44,7 @@ public Dictionary<ContextCacheKey, object> InstanceCache
get
{
CheckDefault();
return _instanceCache ??= new Dictionary<ContextCacheKey, object>();
return _instanceCache ??= new();
}
}
/// <summary>
Expand All @@ -56,26 +55,26 @@ private Dictionary<TypePair, int> TypeDepth
get
{
CheckDefault();
return _typeDepth ??= new Dictionary<TypePair, int>();
return _typeDepth ??= new();
}
}
TDestination IMapperBase.Map<TDestination>(object source) => ((IMapperBase)this).Map(source, default(TDestination));
TDestination IMapperBase.Map<TSource, TDestination>(TSource source) => _mapper.Map(source, default(TDestination), this);
TDestination IMapperBase.Map<TSource, TDestination>(TSource source, TDestination destination) => _mapper.Map(source, destination, this);
object IMapperBase.Map(object source, Type sourceType, Type destinationType) => _mapper.Map(source, (object)null, this, sourceType, destinationType);
TDestination IMapperBase.Map<TSource, TDestination>(TSource source, TDestination destination) => _mapper.Map(source, destination, this);
object IMapperBase.Map(object source, Type sourceType, Type destinationType) => _mapper.Map(source, (object)null, this, sourceType, destinationType);
object IMapperBase.Map(object source, object destination, Type sourceType, Type destinationType) => _mapper.Map(source, destination, this, sourceType, destinationType);
TDestination IInternalRuntimeMapper.Map<TSource, TDestination>(TSource source, TDestination destination, ResolutionContext context,
Type sourceType, Type destinationType, MemberMap memberMap) => _mapper.Map(source, destination, context, sourceType, destinationType, memberMap);
internal object CreateInstance(Type type) => Options.ServiceCtor(type) ?? throw new AutoMapperMappingException("Cannot create an instance of type " + type);
internal object GetDestination(object source, Type destinationType) =>
source == null ? null : InstanceCache.GetValueOrDefault(new ContextCacheKey(source, destinationType));
internal object CreateInstance(Type type) => ServiceCtor()(type) ?? throw new AutoMapperMappingException("Cannot create an instance of type " + type);
private Func<Type, object> ServiceCtor() => _options?.ServiceCtor ?? _mapper.ServiceCtor;
internal object GetDestination(object source, Type destinationType) => source == null ? null : InstanceCache.GetValueOrDefault(new(source, destinationType));
internal void CacheDestination(object source, Type destinationType, object destination)
{
if (source == null)
{
return;
}
InstanceCache[new ContextCacheKey(source, destinationType)] = destination;
InstanceCache[new(source, destinationType)] = destination;
}
internal void IncrementTypeDepth(TypeMap typeMap) => TypeDepth[typeMap.Types]++;
internal void DecrementTypeDepth(TypeMap typeMap) => TypeDepth[typeMap.Types]--;
Expand All @@ -89,24 +88,26 @@ internal bool OverTypeDepth(TypeMap typeMap)
return depth > typeMap.MaxDepth;
}
internal bool IsDefault => this == _mapper.DefaultContext;
Func<Type, object> IInternalRuntimeMapper.ServiceCtor => ServiceCtor();
internal static void CheckContext(ref ResolutionContext resolutionContext)
{
if (resolutionContext.IsDefault)
{
resolutionContext = new ResolutionContext(resolutionContext._mapper);
resolutionContext = new(resolutionContext._mapper);
}
}
internal TDestination MapInternal<TSource, TDestination>(TSource source, TDestination destination, MemberMap memberMap) =>
internal TDestination MapInternal<TSource, TDestination>(TSource source, TDestination destination, MemberMap memberMap) =>
_mapper.Map(source, destination, this, memberMap: memberMap);
internal object Map(object source, object destination, Type sourceType, Type destinationType, MemberMap memberMap) =>
internal object Map(object source, object destination, Type sourceType, Type destinationType, MemberMap memberMap) =>
_mapper.Map(source, destination, this, sourceType, destinationType, memberMap);
private void CheckDefault()
{
if (IsDefault)
{
throw new InvalidOperationException("You must use a Map overload that takes Action<IMappingOperationOptions>!");
ThrowInvalidMap();
}
}
private static void ThrowInvalidMap() => throw new InvalidOperationException("Context.Items are only available when using a Map overload that takes Action<IMappingOperationOptions>!");
}
public readonly struct ContextCacheKey : IEquatable<ContextCacheKey>
{
Expand Down
6 changes: 3 additions & 3 deletions src/UnitTests/ContextItems.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class ContextResolver : IMemberValueResolver<Source, Dest, int, int>
{
public int Resolve(Source src, Dest d, int source, int dest, ResolutionContext context)
{
return source + (int)context.Options.Items["Item"];
return source + (int)context.Items["Item"];
}
}

Expand Down Expand Up @@ -69,7 +69,7 @@ public void Should_report_error()
{
var inner = ex.InnerException;
inner.ShouldBeOfType<InvalidOperationException>();
inner.Message.ShouldBe("You must use a Map overload that takes Action<IMappingOperationOptions>!");
inner.Message.ShouldBe("Context.Items are only available when using a Map overload that takes Action<IMappingOperationOptions>!");
});
}
}
Expand Down Expand Up @@ -119,7 +119,7 @@ public void Should_use_value_passed_in()
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Source, Dest>()
.ForMember(d => d.Value1, opt => opt.MapFrom((source, d, dMember, context) => (int)context.Options.Items["Item"] + source.Value1));
.ForMember(d => d.Value1, opt => opt.MapFrom((source, d, dMember, context) => (int)context.Items["Item"] + source.Value1));
});

var dest = config.CreateMapper().Map<Source, Dest>(new Source { Value1 = 5 }, opt => { opt.Items["Item"] = 10; });
Expand Down