From 8c8a9ff46d59e130627a1d237fa182f87388e0e7 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Fri, 21 Aug 2020 19:28:34 +0100 Subject: [PATCH 1/4] Bugs/204 (#205) * Handling unmappable complex types in query projection * Filtering out injected service values in query projections, re: #204 * Only filtering empty mappings in element projections --- .../WhenCreatingProjections.cs | 76 +++++++++++++++++++ .../ExpressionExtensions.CanBeProjected.cs | 10 +++ AgileMapper/MappingRuleSetSettings.cs | 3 + .../ObjectPopulation/MapperDataContext.cs | 3 + .../MappingExpressionFactoryBase.cs | 14 +++- 5 files changed, 104 insertions(+), 2 deletions(-) diff --git a/AgileMapper.UnitTests.Orms.EfCore2/WhenCreatingProjections.cs b/AgileMapper.UnitTests.Orms.EfCore2/WhenCreatingProjections.cs index a9162a669..c3c76a426 100644 --- a/AgileMapper.UnitTests.Orms.EfCore2/WhenCreatingProjections.cs +++ b/AgileMapper.UnitTests.Orms.EfCore2/WhenCreatingProjections.cs @@ -1,5 +1,6 @@ namespace AgileObjects.AgileMapper.UnitTests.Orms.EfCore2 { + using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -67,5 +68,80 @@ public Task ShouldMapAQueryableAsAnEnumerable() result.ShouldHaveSingleItem().Value.ShouldBeTrue(); }); } + + // See https://github.com/agileobjects/AgileMapper/issues/204 + [Fact] + public void ShouldHandleEnumerableQueryableMappingUnmappableElements() + { + var source = + new[] { new PublicBool { Value = true } } + .AsQueryable().Where(b => b.Value); + + var result = source.Project().To().First(); + + result.ShouldNotBeNull().CanWrite.ShouldBeFalse(); + } + + // See https://github.com/agileobjects/AgileMapper/issues/204 + [Fact] + public void ShouldIgnoreConfiguredServiceProviderInEnumerableQueryableMapping() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .UseServiceProvider(new Issue204.ServiceProvider()); + + mapper.WhenMapping + .From() + .To() + .Map(ctx => ctx.GetService().HasAccess()) + .To(dto => dto.CanWrite); + + var source = + new List { new Issue204.Entity { Id = 1 } } + .AsQueryable().Where(e => e.Id == 1); + + var result = source.ProjectUsing(mapper).To().First(); + + result.ShouldNotBeNull(); + result.Id.ShouldBe(1); + result.CanWrite.ShouldBeFalse(); + } + } + + #region Helper Classes + + private static class Issue204 + { + public class ServiceProvider : IServiceProvider + { + public object GetService(Type serviceType) + => Activator.CreateInstance(serviceType); + } + + public class User + { + public bool HasAccess() => true; + } + + public class Entity + { + public long Id { get; set; } + } + + public class Dto + { + public bool CanWrite { get; set; } + } + + public class EntityDto + { + public long Id { get; set; } + + public bool CanWrite { get; set; } + } + } + + #endregion } } diff --git a/AgileMapper/Extensions/Internal/ExpressionExtensions.CanBeProjected.cs b/AgileMapper/Extensions/Internal/ExpressionExtensions.CanBeProjected.cs index e33939805..20cbe290c 100644 --- a/AgileMapper/Extensions/Internal/ExpressionExtensions.CanBeProjected.cs +++ b/AgileMapper/Extensions/Internal/ExpressionExtensions.CanBeProjected.cs @@ -62,6 +62,16 @@ private static bool IsNonSourceMappingDataMember(MemberExpression memberAccess) return false; } + protected override Expression VisitMethodCall(MethodCallExpression methodCall) + { + if (methodCall.Method.DeclaringType == typeof(IServiceProviderAccessor)) + { + _isNotProjectable = true; + } + + return base.VisitMethodCall(methodCall); + } + protected override Expression VisitParameter(ParameterExpression parameter) { if (_lambdaParameters.IndexOf(parameter) > 0) diff --git a/AgileMapper/MappingRuleSetSettings.cs b/AgileMapper/MappingRuleSetSettings.cs index 45d62c50a..d3369e77b 100644 --- a/AgileMapper/MappingRuleSetSettings.cs +++ b/AgileMapper/MappingRuleSetSettings.cs @@ -23,6 +23,7 @@ public static MappingRuleSetSettings ForInMemoryMapping( CheckDerivedSourceTypes = true, AllowGuardedBindings = allowGuardedBindings, AllowCloneEntityKeyMapping = allowCloneEntityKeyMapping, + RemoveEmptyElementMappings = true, GuardAccessTo = value => true, ExpressionIsSupported = value => true, AllowObjectTracking = true, @@ -52,6 +53,8 @@ public static MappingRuleSetSettings ForInMemoryMapping( public bool AllowCloneEntityKeyMapping { get; set; } + public bool RemoveEmptyElementMappings { get; set; } + public Func GuardAccessTo { get; set; } public Func ExpressionIsSupported { get; set; } diff --git a/AgileMapper/ObjectPopulation/MapperDataContext.cs b/AgileMapper/ObjectPopulation/MapperDataContext.cs index f6287ea91..2e4ae3bc7 100644 --- a/AgileMapper/ObjectPopulation/MapperDataContext.cs +++ b/AgileMapper/ObjectPopulation/MapperDataContext.cs @@ -95,6 +95,9 @@ private void BubbleRuntimeTypedMappingNeededToEntryPoint() public bool UseLocalVariable { get; } + public bool RemoveEmptyEntryPointMappings + => _mapperData.RuleSet.Settings.RemoveEmptyElementMappings; + public bool UseMappingTryCatch => _mapperData.RuleSet.Settings.UseTryCatch && (_mapperData.IsEntryPoint || !IsPartOfUserStructMapping()); diff --git a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs index eee848757..1428322e8 100644 --- a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs @@ -49,12 +49,12 @@ public Expression Create(IObjectMappingData mappingData) AddPopulationsAndCallbacks(context); - if (NothingIsBeingMapped(context)) + if (RemoveEmptyMappings(context) && NothingIsBeingMapped(context)) { return mapperData.IsEntryPoint ? mapperData.TargetObject : Constants.EmptyExpression; } - CompleteMappingBlock: + CompleteMappingBlock: InsertShortCircuitReturns(context); var mappingBlock = GetMappingBlock(context); @@ -319,6 +319,16 @@ private static bool NothingIsBeingMapped(MappingCreationContext context) return objectNewing.Arguments.None() && (objectNewing.Type != typeof(object)); } + private static bool RemoveEmptyMappings(MappingCreationContext context) + { + if (context.MapperData.TargetMemberIsEnumerableElement()) + { + return context.RuleSet.Settings.RemoveEmptyElementMappings; + } + + return true; + } + private Expression GetMappingBlock(MappingCreationContext context) { var mappingExpressions = context.MappingExpressions; From d4eb33caae8fcc038b01edd9d86bca969cf37e19 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Fri, 21 Aug 2020 19:32:40 +0100 Subject: [PATCH 2/4] Updating release number + documentation --- Directory.Build.props | 6 +++--- docs/src/query-projection/Configuration.md | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index f2e031791..252555c70 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -11,9 +11,9 @@ true git https://github.com/AgileObjects/AgileMapper - 1.7.1 - 1.7.1.0 - 1.7.1.0 + 1.7.2 + 1.7.2.0 + 1.7.2.0 \ No newline at end of file diff --git a/docs/src/query-projection/Configuration.md b/docs/src/query-projection/Configuration.md index c44858844..acb332f4b 100644 --- a/docs/src/query-projection/Configuration.md +++ b/docs/src/query-projection/Configuration.md @@ -68,6 +68,8 @@ Because projections are performed by [IQueryProvider](https://docs.microsoft.com - Object [factories](/configuration/Object-Construction) +- [Injected](/configuration/Dependency-Injection) values + - Null [mapping results](/configuration/Null-Results) Most notably, [callbacks](/configuration/Mapping-Callbacks), [object tracking](/configuration/Mapped-Object-Tracking) and [derived type pairing](/configuration/Pairing-Derived-Types) are not supported - although you can [project to derived types](/query-projection/Derived-Types) conditionally. From 1567b00c717430d9edda1a31f8115fc886a04ddc Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Mon, 24 Aug 2020 08:44:31 +0100 Subject: [PATCH 3/4] Bugs/203 (#206) * Support for mapping between complex-type enumerable Dictionaries, re: #203 * Tidying --- .../WhenMappingToNewDictionaryMembers.cs | 46 +++++++++++++++++-- .../WhenMappingToNewEnumerableMembers.cs | 2 + .../Dictionaries/DictionaryTargetMember.cs | 23 +++++----- .../DictionaryPopulationBuilder.cs | 8 ++-- .../EnumerablePopulationBuilder.cs | 40 +++++++++------- .../ObjectPopulation/MappingFactory.cs | 15 +++--- 6 files changed, 93 insertions(+), 41 deletions(-) diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaryMembers.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaryMembers.cs index 57cbe0dad..9066b94be 100644 --- a/AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaryMembers.cs +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaryMembers.cs @@ -341,7 +341,7 @@ public void ShouldFlattenAComplexTypeCollectionToANestedObjectDictionaryImplemen // See https://github.com/agileobjects/AgileMapper/issues/200 [Fact] - public void ShouldMapListDictionaries() + public void ShouldMapBetweenListDictionaries() { var source = new PublicField>> { @@ -364,7 +364,7 @@ public void ShouldMapListDictionaries() } [Fact] - public void ShouldMapArrayDictionaries() + public void ShouldMapBetweenArrayDictionaries() { var source = new PublicField> { @@ -384,7 +384,7 @@ public void ShouldMapArrayDictionaries() .Value .ShouldNotBeNull(); - resultDictionary.Count.ShouldBe(3); ; + resultDictionary.Count.ShouldBe(3); resultDictionary["1"].ShouldHaveSingleItem().ShouldBe(1L); resultDictionary["2"].Length.ShouldBe(2); @@ -397,6 +397,46 @@ public void ShouldMapArrayDictionaries() resultDictionary["3"][2].ShouldBe(3L); } + // See https://github.com/agileobjects/AgileMapper/issues/203 + [Fact] + public void ShouldMapBetweenComplexTypeArrayDictionaries() + { + var source = new PublicField> + { + Value = new Dictionary + { + ["1"] = new[] { new Address { Line1 = "1.1.1" } }, + ["2"] = new[] + { + new Address { Line1 = "2.1.1" }, + new Address { Line1 = "2.2.1", Line2 = "2.2.2" } + } + } + }; + + var result = Mapper.Map(source) + .ToANew>>(); + + var resultDictionary = result + .ShouldNotBeNull() + .Value + .ShouldNotBeNull(); + + resultDictionary.Count.ShouldBe(2); + var address11 = resultDictionary["1"].ShouldHaveSingleItem().ShouldNotBeNull(); + address11.Line1.ShouldBe("1.1.1"); + address11.Line2.ShouldBeNull(); + + resultDictionary["2"].Length.ShouldBe(2); + var address21 = resultDictionary["2"][0].ShouldNotBeNull(); + address21.Line1.ShouldBe("2.1.1"); + address21.Line2.ShouldBeNull(); + + var address22 = resultDictionary["2"][1].ShouldNotBeNull(); + address22.Line1.ShouldBe("2.2.1"); + address22.Line2.ShouldBe("2.2.2"); + } + #region Helper Members private static class Issue97 diff --git a/AgileMapper.UnitTests/WhenMappingToNewEnumerableMembers.cs b/AgileMapper.UnitTests/WhenMappingToNewEnumerableMembers.cs index 7e873c529..8400194bf 100644 --- a/AgileMapper.UnitTests/WhenMappingToNewEnumerableMembers.cs +++ b/AgileMapper.UnitTests/WhenMappingToNewEnumerableMembers.cs @@ -2,7 +2,9 @@ { using System.Collections.Generic; using System.Collections.ObjectModel; +#if !NETCOREAPP1_0 using System.ComponentModel; +#endif using System.Linq; using Common; using TestClasses; diff --git a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs index 61ffc236f..30aaa39de 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs @@ -190,7 +190,8 @@ private Expression GetKey(IMemberMapperData mapperData) private Expression GetDictionaryAccess(IMemberMapperData mapperData) { - var parentContextAccess = mapperData.GetAppropriateMappingContextAccess(typeof(object), _rootDictionaryMember.Type); + var parentContextAccess = mapperData + .GetAppropriateMappingContextAccess(typeof(object), _rootDictionaryMember.Type); if (parentContextAccess.NodeType != Parameter) { @@ -207,7 +208,8 @@ private Expression GetDictionaryAccess(IMemberMapperData mapperData) return dictionaryMapperData.TargetInstance; } - public override bool CheckExistingElementValue => !HasObjectEntries && !HasSimpleEntries; + public override bool CheckExistingElementValue + => !(HasObjectEntries || HasSimpleEntries || HasEnumerableEntries); public override Expression GetHasDefaultValueCheck(IMemberMapperData mapperData) { @@ -346,7 +348,7 @@ public DictionaryTargetMember WithTypeOf(Member sourceMember) public override void MapCreating(Type sourceType) { - if (CreateNonDictionaryChildMembers(sourceType)) + if (DoNotFlattenSourceObjects(sourceType)) { _createDictionaryChildMembers = false; } @@ -354,15 +356,14 @@ public override void MapCreating(Type sourceType) base.MapCreating(sourceType); } - private bool CreateNonDictionaryChildMembers(Type sourceType) + private bool DoNotFlattenSourceObjects(Type sourceType) { - // If this DictionaryTargetMember represents an object-typed dictionary - // entry and we're mapping from a source of type object, we switch from - // mapping to flattened entries to mapping entire objects: - return HasObjectEntries && - this.IsEnumerableElement() && - (MemberChain[Depth - 2] == _rootDictionaryMember.LeafMember) && - (sourceType == typeof(object)); + // If this target Dictionary member's type matches the type of source + // objects being mapped into it, we switch from flattening source + // objects into Dictionary entries to mapping entire objects: + return this.IsEnumerableElement() && + (ValueType == sourceType) && + (MemberChain[Depth - 2] == _rootDictionaryMember.LeafMember); } #region ExcludeFromCodeCoverage diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs index 1551da816..f3a848076 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs @@ -124,7 +124,7 @@ private Expression AssignDictionaryEntry( return GetPopulation(loopData, dictionaryEntryMember, mappingData); } - mappingData = GetMappingData(mappingData); + mappingData = GetEntryMappingData(mappingData); if (dictionaryEntryMember.HasComplexEntries) { @@ -275,14 +275,12 @@ private Expression GetPopulation( return populationExpression; } - private IObjectMappingData GetMappingData(IObjectMappingData mappingData) + private IObjectMappingData GetEntryMappingData(IObjectMappingData mappingData) { var sourceElementType = _wrappedBuilder.Context.SourceElementType; var targetElementType = _targetDictionaryMember.GetElementType(sourceElementType); - mappingData = ObjectMappingDataFactory.ForElement(sourceElementType, targetElementType, mappingData); - - return mappingData; + return ObjectMappingDataFactory.ForElement(sourceElementType, targetElementType, mappingData); } private static Expression GetDerivedTypeMapping( diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs index f4d1989e7..f4900bda7 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs @@ -321,12 +321,15 @@ private void CreateSourceTypeHelper(Expression sourceValue) #region Target Variable Population - public void PopulateTargetVariableFromSourceObjectOnly(IObjectMappingData mappingData = null) - => AssignTargetVariableTo(GetSourceOnlyReturnValue(mappingData)); + public void PopulateTargetVariableFromSourceObjectOnly(IObjectMappingData enumerableMappingData = null) + => AssignTargetVariableTo(GetSourceOnlyReturnValue(enumerableMappingData)); - private Expression GetSourceOnlyReturnValue(IObjectMappingData mappingData) + private Expression GetSourceOnlyReturnValue(IObjectMappingData enumerableMappingData) { - var convertedSourceItems = _sourceItemsSelector.SourceItemsProjectedToTargetType(mappingData).GetResult(); + var convertedSourceItems = _sourceItemsSelector + .SourceItemsProjectedToTargetType(enumerableMappingData) + .GetResult(); + var returnValue = ConvertForReturnValue(convertedSourceItems); return returnValue; @@ -510,7 +513,7 @@ public void RemoveAllTargetItems() _populationExpressions.Add(GetTargetMethodCall("Clear")); } - public void AddNewItemsToTargetVariable(IObjectMappingData mappingData) + public void AddNewItemsToTargetVariable(IObjectMappingData enumerableMappingData) { if (TargetElementsAreSimple && Context.ElementTypesAreTheSame && TargetTypeHelper.IsList) { @@ -518,7 +521,7 @@ public void AddNewItemsToTargetVariable(IObjectMappingData mappingData) return; } - BuildPopulationLoop(GetElementPopulation, mappingData); + BuildPopulationLoop(GetElementPopulation, enumerableMappingData); } public void BuildPopulationLoop( @@ -535,9 +538,11 @@ public void BuildPopulationLoop( _populationExpressions.AddUnlessNullOrEmpty(populationLoop); } - private Expression GetElementPopulation(IPopulationLoopData loopData, IObjectMappingData mappingData) + private Expression GetElementPopulation( + IPopulationLoopData loopData, + IObjectMappingData enumerableMappingData) { - var elementMapping = loopData.GetElementMapping(mappingData); + var elementMapping = loopData.GetElementMapping(enumerableMappingData); if (elementMapping == Constants.EmptyExpression) { @@ -568,24 +573,26 @@ private bool InsertSourceObjectElementNullCheck(IPopulationLoopData loopData, ou return sourceElement.Type == typeof(object); } - public Expression GetElementConversion(Expression sourceElement, IObjectMappingData mappingData) + public Expression GetElementConversion( + Expression sourceElement, + IObjectMappingData enumerableMappingData) { if (TargetElementsAreSimple) { return GetSimpleElementConversion(sourceElement); } - var targetMember = mappingData.MapperData.TargetMember; + var targetMember = enumerableMappingData.MapperData.TargetMember; Expression existingElementValue; - if (targetMember.CheckExistingElementValue && mappingData.MapperData.TargetCouldBePopulated()) + if (targetMember.CheckExistingElementValue && enumerableMappingData.MapperData.TargetCouldBePopulated()) { - var existingElementValueCheck = targetMember.GetAccessChecked(mappingData.MapperData); + var existingElementValueCheck = targetMember.GetAccessChecked(enumerableMappingData.MapperData); if (existingElementValueCheck.Variables.Any()) { - return GetValueCheckedElementMapping(sourceElement, existingElementValueCheck, mappingData); + return GetValueCheckedElementMapping(sourceElement, existingElementValueCheck, enumerableMappingData); } existingElementValue = existingElementValueCheck; @@ -595,7 +602,7 @@ public Expression GetElementConversion(Expression sourceElement, IObjectMappingD existingElementValue = Context.TargetElementType.ToDefaultExpression(); } - return GetElementMapping(sourceElement, existingElementValue, mappingData); + return GetElementMapping(sourceElement, existingElementValue, enumerableMappingData); } private static Expression GetValueCheckedElementMapping( @@ -797,7 +804,8 @@ internal SourceItemsSelector(EnumerablePopulationBuilder builder) _builder = builder; } - public SourceItemsSelector SourceItemsProjectedToTargetType(IObjectMappingData mappingData = null) + public SourceItemsSelector SourceItemsProjectedToTargetType( + IObjectMappingData enumerableMappingData = null) { var context = _builder.Context; var sourceEnumerableValue = _builder._sourceAdapter.GetSourceValues(); @@ -811,7 +819,7 @@ public SourceItemsSelector SourceItemsProjectedToTargetType(IObjectMappingData m _result = _builder.GetSourceItemsProjection( sourceEnumerableValue, - sourceElement => _builder.GetElementConversion(sourceElement, mappingData)); + sourceElement => _builder.GetElementConversion(sourceElement, enumerableMappingData)); return this; } diff --git a/AgileMapper/ObjectPopulation/MappingFactory.cs b/AgileMapper/ObjectPopulation/MappingFactory.cs index 9074d8629..5c94cd8b2 100644 --- a/AgileMapper/ObjectPopulation/MappingFactory.cs +++ b/AgileMapper/ObjectPopulation/MappingFactory.cs @@ -6,10 +6,10 @@ using System.Linq.Expressions; #endif using Caching.Dictionaries; - using Extensions; using Extensions.Internal; using Members; using Members.MemberExtensions; + using NetStandardPolyfills; internal static class MappingFactory { @@ -88,7 +88,7 @@ public static Expression GetElementMapping( { var mapperData = mappingData.MapperData; - if (CreateElementMappingDataFor(mapperData)) + if (CreateElementMappingDataFor(mapperData, sourceElementValue)) { mappingData = ObjectMappingDataFactory.ForElement(mappingData); } @@ -103,16 +103,19 @@ public static Expression GetElementMapping( return GetElementMapping(mappingData, sourceElementValue, targetElementValue); } - private static bool CreateElementMappingDataFor(IQualifiedMemberContext context) + private static bool CreateElementMappingDataFor( + ObjectMapperData mapperData, + Expression sourceElementValue) { - if (!context.TargetMemberIsEnumerableElement()) + if (!mapperData.TargetMemberIsEnumerableElement()) { return true; } - if (context.TargetMember.IsEnumerable) + if (mapperData.TargetMember.IsEnumerable) { - return !context.TargetMember.ElementType.IsSimple(); + return !mapperData.EnumerablePopulationBuilder.TargetElementsAreSimple && + sourceElementValue.Type.IsAssignableTo(mapperData.SourceMember.ElementType); } return false; From 03ba972410855161a78caf3eb88a24dcfd574f56 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Thu, 3 Sep 2020 08:14:43 +0100 Subject: [PATCH 4/4] Updating release notes / Adding v1.7.2 NuGet package --- AgileMapper/AgileMapper.csproj | 4 ++-- NuGet/AgileObjects.AgileMapper.1.7.2.nupkg | Bin 0 -> 1158256 bytes 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 NuGet/AgileObjects.AgileMapper.1.7.2.nupkg diff --git a/AgileMapper/AgileMapper.csproj b/AgileMapper/AgileMapper.csproj index 1e143265b..88b116af3 100644 --- a/AgileMapper/AgileMapper.csproj +++ b/AgileMapper/AgileMapper.csproj @@ -20,8 +20,8 @@ $(PackageTargetFallback);dnxcore50 MIT ./Icon.png - - Fixing Dictionary-of-collections mapping, re: #200 -- Updating to ReadableExpressions v2.5.1 + - Fixing complex type collection Dictionary mapping, re: #203 +- Removing service injections from projections, re: #204 diff --git a/NuGet/AgileObjects.AgileMapper.1.7.2.nupkg b/NuGet/AgileObjects.AgileMapper.1.7.2.nupkg new file mode 100644 index 0000000000000000000000000000000000000000..2de75283792e1b0739339cc71fb25c8481b6487a GIT binary patch literal 1158256 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