From 2700e611db61406ad10278598a8a212405353e62 Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Sun, 17 Feb 2019 15:35:37 +0100 Subject: [PATCH 01/32] Add test for ef core issue 13 --- .../NotExistingTests.cs | 99 +++++++++++++++++++ ...lentExpressionAddRemoveCollectionMapper.cs | 13 ++- 2 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 src/AutoMapper.Collection.Tests/NotExistingTests.cs diff --git a/src/AutoMapper.Collection.Tests/NotExistingTests.cs b/src/AutoMapper.Collection.Tests/NotExistingTests.cs new file mode 100644 index 0000000..ca99e41 --- /dev/null +++ b/src/AutoMapper.Collection.Tests/NotExistingTests.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using AutoMapper.EquivalencyExpression; +using FluentAssertions; +using Xunit; + +namespace AutoMapper.Collection +{ + public class NotExistingTests + { + [Fact] + public void Should_not_throw_exception_for_nonexisting_types() + { + var configuration = new MapperConfiguration(x => + { + //x.CreateMissingTypeMaps = false; + x.AddCollectionMappers(); + }); + IMapper mapper = new Mapper(configuration); + + var system = new System + { + Name = "My First System", + Contacts = new List + { + new Contact + { + Name = "John", + Emails = new List() + { + new Email + { + Address = "john@doe.com" + } + } + } + } + }; + + mapper.Map(system); + } + + public class System + { + public int Id { get; set; } + public string Name { get; set; } + + public ICollection Contacts { get; set; } + } + + public class Contact + { + public int Id { get; set; } + public int SystemId { get; set; } + public string Name { get; set; } + + public System System { get; set; } + + public ICollection Emails { get; set; } + } + + public class Email + { + public int Id { get; set; } + + public int ContactId { get; set; } + public string Address { get; set; } + + public Contact Contact { get; set; } + } + + public class SystemViewModel + { + public int Id { get; set; } + public string Name { get; set; } + + public ICollection Contacts { get; set; } + } + + public class ContactViewModel + { + public int Id { get; set; } + public int SystemId { get; set; } + public string Name { get; set; } + + public SystemViewModel System { get; set; } + + public ICollection Emails { get; set; } + } + + public class EmailViewModel + { + public int Id { get; set; } + public int ContactId { get; set; } + public string Address { get; set; } + + public ContactViewModel Contact { get; set; } + } + } +} diff --git a/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs b/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs index 1d7924a..34f78cf 100644 --- a/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs +++ b/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs @@ -66,9 +66,16 @@ public static TDestination Map Date: Mon, 18 Feb 2019 09:10:35 +0100 Subject: [PATCH 02/32] Update test with more asserts --- src/AutoMapper.Collection.Tests/NotExistingTests.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/AutoMapper.Collection.Tests/NotExistingTests.cs b/src/AutoMapper.Collection.Tests/NotExistingTests.cs index ca99e41..748240b 100644 --- a/src/AutoMapper.Collection.Tests/NotExistingTests.cs +++ b/src/AutoMapper.Collection.Tests/NotExistingTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using AutoMapper.EquivalencyExpression; using FluentAssertions; using Xunit; @@ -12,7 +13,6 @@ public void Should_not_throw_exception_for_nonexisting_types() { var configuration = new MapperConfiguration(x => { - //x.CreateMissingTypeMaps = false; x.AddCollectionMappers(); }); IMapper mapper = new Mapper(configuration); @@ -36,7 +36,10 @@ public void Should_not_throw_exception_for_nonexisting_types() } }; - mapper.Map(system); + var model = mapper.Map(system); + model.Name.Should().Be(system.Name); + model.Contacts.Single().Name.Should().Be(system.Contacts.Single().Name); + model.Contacts.Single().Emails.Single().Address.Should().Be(system.Contacts.Single().Emails.Single().Address); } public class System From 4b61cba83804e1fbc0fda7cbfb5e8deb4212b48a Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Sat, 23 Feb 2019 09:14:19 +0100 Subject: [PATCH 03/32] Update test to verify that AM.Collection is using for the mapping and updating the existing entity instead of replacing it. --- .../NotExistingTests.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/AutoMapper.Collection.Tests/NotExistingTests.cs b/src/AutoMapper.Collection.Tests/NotExistingTests.cs index 748240b..328f844 100644 --- a/src/AutoMapper.Collection.Tests/NotExistingTests.cs +++ b/src/AutoMapper.Collection.Tests/NotExistingTests.cs @@ -17,7 +17,7 @@ public void Should_not_throw_exception_for_nonexisting_types() }); IMapper mapper = new Mapper(configuration); - var system = new System + var originalModel = new System { Name = "My First System", Contacts = new List @@ -36,10 +36,16 @@ public void Should_not_throw_exception_for_nonexisting_types() } }; - var model = mapper.Map(system); - model.Name.Should().Be(system.Name); - model.Contacts.Single().Name.Should().Be(system.Contacts.Single().Name); - model.Contacts.Single().Emails.Single().Address.Should().Be(system.Contacts.Single().Emails.Single().Address); + var originalEmail = originalModel.Contacts.Single().Emails.Single(); + + var assertModel = mapper.Map(originalModel); + assertModel.Name.Should().Be(originalModel.Name); + assertModel.Contacts.Single().Name.Should().Be(originalModel.Contacts.Single().Name); + assertModel.Contacts.Single().Emails.Single().Address.Should().Be(originalModel.Contacts.Single().Emails.Single().Address); + + mapper.Map(assertModel, originalModel); + // This tests if equality was found and mapped to pre-existing object and not defaulting to AM and clearing and regenerating the list + originalModel.Contacts.Single().Emails.Single().Should().Be(originalEmail); } public class System From 111c918a0c7f7f2474644feef0f5f57d109ce958 Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Mon, 18 Feb 2019 19:42:22 +0100 Subject: [PATCH 04/32] Use the AutoMapper Features function to add extension into the core. --- .../CollectionMappingExpressionFeature.cs | 29 +++++++ .../GeneratePropertyMapsExpressionFeature.cs | 26 ++++++ .../EquivalentExpressions.cs | 81 +++---------------- .../Execution/CollectionMappingFeature.cs | 18 +++++ .../Execution/GeneratePropertyMapsFeature.cs | 67 +++++++++++++++ 5 files changed, 152 insertions(+), 69 deletions(-) create mode 100644 src/AutoMapper.Collection/Configuration/CollectionMappingExpressionFeature.cs create mode 100644 src/AutoMapper.Collection/Configuration/GeneratePropertyMapsExpressionFeature.cs create mode 100644 src/AutoMapper.Collection/Execution/CollectionMappingFeature.cs create mode 100644 src/AutoMapper.Collection/Execution/GeneratePropertyMapsFeature.cs diff --git a/src/AutoMapper.Collection/Configuration/CollectionMappingExpressionFeature.cs b/src/AutoMapper.Collection/Configuration/CollectionMappingExpressionFeature.cs new file mode 100644 index 0000000..bcce96a --- /dev/null +++ b/src/AutoMapper.Collection/Configuration/CollectionMappingExpressionFeature.cs @@ -0,0 +1,29 @@ +using System; +using System.Linq.Expressions; +using AutoMapper.Collection.Execution; +using AutoMapper.EquivalencyExpression; + +namespace AutoMapper.Collection.Configuration +{ + public class CollectionMappingExpressionFeature : IMappingExpressionFeature + { + private readonly Expression> _expression; + + public CollectionMappingExpressionFeature(Expression> expression) + { + _expression = expression; + } + + public void Configure(TypeMap typeMap) + { + var equivalentExpression = new EquivalentExpression(_expression); + typeMap.Features.Set(new CollectionMappingFeature(equivalentExpression)); + } + + public IMappingExpressionFeature Reverse() + { + var reverseExpression = Expression.Lambda>(_expression.Body, _expression.Parameters[1], _expression.Parameters[0]); + return new CollectionMappingExpressionFeature(reverseExpression); + } + } +} diff --git a/src/AutoMapper.Collection/Configuration/GeneratePropertyMapsExpressionFeature.cs b/src/AutoMapper.Collection/Configuration/GeneratePropertyMapsExpressionFeature.cs new file mode 100644 index 0000000..94db3c9 --- /dev/null +++ b/src/AutoMapper.Collection/Configuration/GeneratePropertyMapsExpressionFeature.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMapper.Collection.Execution; +using AutoMapper.EquivalencyExpression; + +namespace AutoMapper.Collection.Configuration +{ + public class GeneratePropertyMapsExpressionFeature : IMapperConfigurationExpressionFeature + { + private readonly List, IGeneratePropertyMaps>> _generators = new List, IGeneratePropertyMaps>>(); + + public void Add(Func, IGeneratePropertyMaps> creator) + { + _generators.Add(creator); + } + + void IMapperConfigurationExpressionFeature.Configure(IConfigurationProvider configurationProvider) + { + var generators = _generators + .Select(x => x.Invoke(configurationProvider.ServiceCtor)) + .ToList(); + configurationProvider.Features.Set(new GeneratePropertyMapsFeature(generators)); + } + } +} diff --git a/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs b/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs index fe49260..dc481ba 100644 --- a/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs +++ b/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs @@ -1,30 +1,17 @@ using System; -using System.Collections.Concurrent; -using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Reflection; -using AutoMapper.Collection; +using AutoMapper.Collection.Configuration; +using AutoMapper.Collection.Execution; using AutoMapper.Mappers; namespace AutoMapper.EquivalencyExpression { public static class EquivalentExpressions { - private static readonly ConcurrentDictionary> - _equivalentExpressionDictionary = new ConcurrentDictionary>(); - - private static readonly ConcurrentDictionary> - _generatePropertyMapsDictionary = new ConcurrentDictionary>(); - - private static ConcurrentDictionary - _equalityComparisonCache = new ConcurrentDictionary(); - - private static IList, IGeneratePropertyMaps>> - _generatePropertyMapsCache = new List, IGeneratePropertyMaps>>(); - public static void AddCollectionMappers(this IMapperConfigurationExpression cfg) { + cfg.Advanced.Features.Set(new GeneratePropertyMapsExpressionFeature()); cfg.InsertBefore( new ObjectToEquivalencyExpressionByEquivalencyExistingMapper(), new EquivalentExpressionAddRemoveCollectionMapper()); @@ -47,14 +34,6 @@ private static void InsertBefore(this IMapperConfigurationExpress { configurationObjectMapper.ConfigurationProvider = c; } - - var propertyMapsGenerators = _generatePropertyMapsCache.Select(x => x?.Invoke(c.ServiceCtor)).ToList(); - - _equivalentExpressionDictionary.AddOrUpdate(c, _equalityComparisonCache, (_, __) => _equalityComparisonCache); - _equalityComparisonCache = new ConcurrentDictionary(); - - _generatePropertyMapsDictionary.AddOrUpdate(c, propertyMapsGenerators, (_, __) => propertyMapsGenerators); - _generatePropertyMapsCache = new List, IGeneratePropertyMaps>>(); }); } @@ -89,10 +68,8 @@ internal static IEquivalentComparer GetEquivalentExpression(this IConfigurationO internal static IEquivalentComparer GetEquivalentExpression(IConfigurationProvider configurationProvider, TypeMap typeMap) { - return _equivalentExpressionDictionary[configurationProvider].GetOrAdd(typeMap.Types, _ - => _generatePropertyMapsDictionary[configurationProvider] - .Select(x => x.GeneratePropertyMaps(typeMap).CreateEquivalentExpression()) - .FirstOrDefault(x => x != null)); + return typeMap.Features.Get()?.EquivalentComparer + ?? configurationProvider.Features.Get().Get(typeMap); } /// @@ -105,57 +82,23 @@ internal static IEquivalentComparer GetEquivalentExpression(IConfigurationProvid /// public static IMappingExpression EqualityComparison(this IMappingExpression mappingExpression, Expression> EquivalentExpression) { - var typePair = new TypePair(typeof(TSource), typeof(TDestination)); - - _equalityComparisonCache.AddOrUpdate(typePair, - new EquivalentExpression(EquivalentExpression), - (_, __) => new EquivalentExpression(EquivalentExpression)); - + mappingExpression.Features.Set(new CollectionMappingExpressionFeature(EquivalentExpression)); return mappingExpression; } public static void SetGeneratePropertyMaps(this IMapperConfigurationExpression cfg) where TGeneratePropertyMaps : IGeneratePropertyMaps { - _generatePropertyMapsCache.Add(serviceCtor => (IGeneratePropertyMaps)serviceCtor(typeof(TGeneratePropertyMaps))); + (cfg.Advanced.Features.Get() + ?? throw new ArgumentException("Invoke the IMapperConfigurationExpression.AddCollectionMappers() before adding IGeneratePropertyMaps.")) + .Add(serviceCtor => (IGeneratePropertyMaps)serviceCtor(typeof(TGeneratePropertyMaps))); } public static void SetGeneratePropertyMaps(this IMapperConfigurationExpression cfg, IGeneratePropertyMaps generatePropertyMaps) { - _generatePropertyMapsCache.Add(_ => generatePropertyMaps); - } - - private static IEquivalentComparer CreateEquivalentExpression(this IEnumerable propertyMaps) - { - if (!propertyMaps.Any() || propertyMaps.Any(pm => pm.DestinationMember.GetMemberType() != pm.SourceMember.GetMemberType())) - { - return null; - } - - var typeMap = propertyMaps.First().TypeMap; - var srcType = typeMap.SourceType; - var destType = typeMap.DestinationType; - var srcExpr = Expression.Parameter(srcType, "src"); - var destExpr = Expression.Parameter(destType, "dest"); - - var equalExpr = propertyMaps.Select(pm => SourceEqualsDestinationExpression(pm, srcExpr, destExpr)).ToList(); - if (equalExpr.Count == 0) - { - return EquivalentExpression.BadValue; - } - - var finalExpression = equalExpr.Skip(1).Aggregate(equalExpr[0], Expression.And); - - var expr = Expression.Lambda(finalExpression, srcExpr, destExpr); - var genericExpressionType = typeof(EquivalentExpression<,>).MakeGenericType(srcType, destType); - return Activator.CreateInstance(genericExpressionType, expr) as IEquivalentComparer; - } - - private static BinaryExpression SourceEqualsDestinationExpression(PropertyMap propertyMap, Expression srcExpr, Expression destExpr) - { - var srcPropExpr = Expression.Property(srcExpr, propertyMap.SourceMember as PropertyInfo); - var destPropExpr = Expression.Property(destExpr, propertyMap.DestinationMember as PropertyInfo); - return Expression.Equal(srcPropExpr, destPropExpr); + (cfg.Advanced.Features.Get() + ?? throw new ArgumentException("Invoke the IMapperConfigurationExpression.AddCollectionMappers() before adding IGeneratePropertyMaps.")) + .Add(_ => generatePropertyMaps); } } } \ No newline at end of file diff --git a/src/AutoMapper.Collection/Execution/CollectionMappingFeature.cs b/src/AutoMapper.Collection/Execution/CollectionMappingFeature.cs new file mode 100644 index 0000000..c0865d7 --- /dev/null +++ b/src/AutoMapper.Collection/Execution/CollectionMappingFeature.cs @@ -0,0 +1,18 @@ +using AutoMapper.EquivalencyExpression; + +namespace AutoMapper.Collection.Execution +{ + public class CollectionMappingFeature : IFeature + { + public CollectionMappingFeature(IEquivalentComparer equivalentComparer) + { + EquivalentComparer = equivalentComparer; + } + + public IEquivalentComparer EquivalentComparer { get; } + + void IFeature.Seal(IConfigurationProvider configurationProvider) + { + } + } +} diff --git a/src/AutoMapper.Collection/Execution/GeneratePropertyMapsFeature.cs b/src/AutoMapper.Collection/Execution/GeneratePropertyMapsFeature.cs new file mode 100644 index 0000000..cdd903e --- /dev/null +++ b/src/AutoMapper.Collection/Execution/GeneratePropertyMapsFeature.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using AutoMapper.EquivalencyExpression; + +namespace AutoMapper.Collection.Execution +{ + public class GeneratePropertyMapsFeature : IFeature + { + private readonly IList _generators; + private readonly ConcurrentDictionary _comparers = new ConcurrentDictionary(); + + public GeneratePropertyMapsFeature(List generators) + { + _generators = generators.AsReadOnly(); + } + + internal IEquivalentComparer Get(TypeMap typeMap) + { + return _comparers + .GetOrAdd(typeMap.Types, _ => + _generators + .Select(x => CreateEquivalentExpression(x.GeneratePropertyMaps(typeMap))) + .FirstOrDefault(x => x != null)); + } + + void IFeature.Seal(IConfigurationProvider configurationProvider) + { + } + + private IEquivalentComparer CreateEquivalentExpression(IEnumerable propertyMaps) + { + if (!propertyMaps.Any() || propertyMaps.Any(pm => pm.DestinationMember.GetMemberType() != pm.SourceMember.GetMemberType())) + { + return null; + } + + var typeMap = propertyMaps.First().TypeMap; + var srcType = typeMap.SourceType; + var destType = typeMap.DestinationType; + var srcExpr = Expression.Parameter(srcType, "src"); + var destExpr = Expression.Parameter(destType, "dest"); + + var equalExpr = propertyMaps.Select(pm => SourceEqualsDestinationExpression(pm, srcExpr, destExpr)).ToList(); + if (equalExpr.Count == 0) + { + return EquivalentExpression.BadValue; + } + + var finalExpression = equalExpr.Skip(1).Aggregate(equalExpr[0], Expression.And); + + var expr = Expression.Lambda(finalExpression, srcExpr, destExpr); + var genericExpressionType = typeof(EquivalentExpression<,>).MakeGenericType(srcType, destType); + return Activator.CreateInstance(genericExpressionType, expr) as IEquivalentComparer; + } + + private BinaryExpression SourceEqualsDestinationExpression(PropertyMap propertyMap, Expression srcExpr, Expression destExpr) + { + var srcPropExpr = Expression.Property(srcExpr, propertyMap.SourceMember as PropertyInfo); + var destPropExpr = Expression.Property(destExpr, propertyMap.DestinationMember as PropertyInfo); + return Expression.Equal(srcPropExpr, destPropExpr); + } + } +} From 8cf72ca240faa03db357c61d172d7059885c47ff Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Mon, 18 Feb 2019 19:42:45 +0100 Subject: [PATCH 05/32] Use local build automapper --- src/AutoMapper.Collection/AutoMapper.Collection.csproj | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/AutoMapper.Collection/AutoMapper.Collection.csproj b/src/AutoMapper.Collection/AutoMapper.Collection.csproj index e48132d..c8a0ae8 100644 --- a/src/AutoMapper.Collection/AutoMapper.Collection.csproj +++ b/src/AutoMapper.Collection/AutoMapper.Collection.csproj @@ -16,12 +16,7 @@ - - - - - - + From 15720714b7b68607312076ac429efb33fa0a8e5f Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Sat, 23 Feb 2019 09:53:22 +0100 Subject: [PATCH 06/32] Update with the changes in AM POC --- .../EquivalencyExpression/EquivalentExpressions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs b/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs index dc481ba..aefe906 100644 --- a/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs +++ b/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs @@ -11,7 +11,7 @@ public static class EquivalentExpressions { public static void AddCollectionMappers(this IMapperConfigurationExpression cfg) { - cfg.Advanced.Features.Set(new GeneratePropertyMapsExpressionFeature()); + cfg.Features.Set(new GeneratePropertyMapsExpressionFeature()); cfg.InsertBefore( new ObjectToEquivalencyExpressionByEquivalencyExistingMapper(), new EquivalentExpressionAddRemoveCollectionMapper()); @@ -89,14 +89,14 @@ public static IMappingExpression EqualityComparison(this IMapperConfigurationExpression cfg) where TGeneratePropertyMaps : IGeneratePropertyMaps { - (cfg.Advanced.Features.Get() + (cfg.Features.Get() ?? throw new ArgumentException("Invoke the IMapperConfigurationExpression.AddCollectionMappers() before adding IGeneratePropertyMaps.")) .Add(serviceCtor => (IGeneratePropertyMaps)serviceCtor(typeof(TGeneratePropertyMaps))); } public static void SetGeneratePropertyMaps(this IMapperConfigurationExpression cfg, IGeneratePropertyMaps generatePropertyMaps) { - (cfg.Advanced.Features.Get() + (cfg.Features.Get() ?? throw new ArgumentException("Invoke the IMapperConfigurationExpression.AddCollectionMappers() before adding IGeneratePropertyMaps.")) .Add(_ => generatePropertyMaps); } From c00bb10ac7e278ca359bf446eea824641c96a625 Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Sat, 23 Feb 2019 09:53:31 +0100 Subject: [PATCH 07/32] Update proj-reference --- src/AutoMapper.Collection/AutoMapper.Collection.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AutoMapper.Collection/AutoMapper.Collection.csproj b/src/AutoMapper.Collection/AutoMapper.Collection.csproj index c8a0ae8..391ea65 100644 --- a/src/AutoMapper.Collection/AutoMapper.Collection.csproj +++ b/src/AutoMapper.Collection/AutoMapper.Collection.csproj @@ -16,7 +16,7 @@ - + From 7994ddde81e08b8c3b3211a3b85056047bb81ca1 Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Sun, 3 Mar 2019 18:18:09 +0100 Subject: [PATCH 08/32] Update to reflect the changes in features collection inside AM --- src/AutoMapper.Collection/AutoMapper.Collection.csproj | 2 +- .../Configuration/CollectionMappingExpressionFeature.cs | 2 +- .../Configuration/GeneratePropertyMapsExpressionFeature.cs | 2 +- .../EquivalencyExpression/EquivalentExpressions.cs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/AutoMapper.Collection/AutoMapper.Collection.csproj b/src/AutoMapper.Collection/AutoMapper.Collection.csproj index 391ea65..142e867 100644 --- a/src/AutoMapper.Collection/AutoMapper.Collection.csproj +++ b/src/AutoMapper.Collection/AutoMapper.Collection.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/AutoMapper.Collection/Configuration/CollectionMappingExpressionFeature.cs b/src/AutoMapper.Collection/Configuration/CollectionMappingExpressionFeature.cs index bcce96a..c77a732 100644 --- a/src/AutoMapper.Collection/Configuration/CollectionMappingExpressionFeature.cs +++ b/src/AutoMapper.Collection/Configuration/CollectionMappingExpressionFeature.cs @@ -17,7 +17,7 @@ public CollectionMappingExpressionFeature(Expression(_expression); - typeMap.Features.Set(new CollectionMappingFeature(equivalentExpression)); + typeMap.Features.Add(new CollectionMappingFeature(equivalentExpression)); } public IMappingExpressionFeature Reverse() diff --git a/src/AutoMapper.Collection/Configuration/GeneratePropertyMapsExpressionFeature.cs b/src/AutoMapper.Collection/Configuration/GeneratePropertyMapsExpressionFeature.cs index 94db3c9..9c488ec 100644 --- a/src/AutoMapper.Collection/Configuration/GeneratePropertyMapsExpressionFeature.cs +++ b/src/AutoMapper.Collection/Configuration/GeneratePropertyMapsExpressionFeature.cs @@ -20,7 +20,7 @@ void IMapperConfigurationExpressionFeature.Configure(IConfigurationProvider conf var generators = _generators .Select(x => x.Invoke(configurationProvider.ServiceCtor)) .ToList(); - configurationProvider.Features.Set(new GeneratePropertyMapsFeature(generators)); + configurationProvider.Features.Add(new GeneratePropertyMapsFeature(generators)); } } } diff --git a/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs b/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs index aefe906..5854602 100644 --- a/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs +++ b/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs @@ -11,7 +11,7 @@ public static class EquivalentExpressions { public static void AddCollectionMappers(this IMapperConfigurationExpression cfg) { - cfg.Features.Set(new GeneratePropertyMapsExpressionFeature()); + cfg.AddFeature(new GeneratePropertyMapsExpressionFeature()); cfg.InsertBefore( new ObjectToEquivalencyExpressionByEquivalencyExistingMapper(), new EquivalentExpressionAddRemoveCollectionMapper()); @@ -82,7 +82,7 @@ internal static IEquivalentComparer GetEquivalentExpression(IConfigurationProvid /// public static IMappingExpression EqualityComparison(this IMappingExpression mappingExpression, Expression> EquivalentExpression) { - mappingExpression.Features.Set(new CollectionMappingExpressionFeature(EquivalentExpression)); + mappingExpression.AddFeature(new CollectionMappingExpressionFeature(EquivalentExpression)); return mappingExpression; } From cbb39d660e3a8d6c81599426054a2b5b36794807 Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Fri, 26 Apr 2019 23:14:25 +0200 Subject: [PATCH 09/32] Update to AM 8.1.0 and use the new features-function to store the configuration --- src/AutoMapper.Collection/AutoMapper.Collection.csproj | 2 +- .../CollectionMappingExpressionFeature.cs | 9 +++++---- .../GeneratePropertyMapsExpressionFeature.cs | 9 +++++---- .../EquivalencyExpression/EquivalentExpressions.cs | 10 +++++----- .../{Execution => Runtime}/CollectionMappingFeature.cs | 7 ++++--- .../GeneratePropertyMapsnFeature.cs} | 7 ++++--- 6 files changed, 24 insertions(+), 20 deletions(-) rename src/AutoMapper.Collection/{Execution => Runtime}/CollectionMappingFeature.cs (59%) rename src/AutoMapper.Collection/{Execution/GeneratePropertyMapsFeature.cs => Runtime/GeneratePropertyMapsnFeature.cs} (92%) diff --git a/src/AutoMapper.Collection/AutoMapper.Collection.csproj b/src/AutoMapper.Collection/AutoMapper.Collection.csproj index 142e867..cef1dc6 100644 --- a/src/AutoMapper.Collection/AutoMapper.Collection.csproj +++ b/src/AutoMapper.Collection/AutoMapper.Collection.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/AutoMapper.Collection/Configuration/CollectionMappingExpressionFeature.cs b/src/AutoMapper.Collection/Configuration/CollectionMappingExpressionFeature.cs index c77a732..b8a52d0 100644 --- a/src/AutoMapper.Collection/Configuration/CollectionMappingExpressionFeature.cs +++ b/src/AutoMapper.Collection/Configuration/CollectionMappingExpressionFeature.cs @@ -1,11 +1,12 @@ using System; using System.Linq.Expressions; -using AutoMapper.Collection.Execution; +using AutoMapper.Features; +using AutoMapper.Collection.Runtime; using AutoMapper.EquivalencyExpression; namespace AutoMapper.Collection.Configuration { - public class CollectionMappingExpressionFeature : IMappingExpressionFeature + public class CollectionMappingExpressionFeature : IMappingFeature { private readonly Expression> _expression; @@ -17,10 +18,10 @@ public CollectionMappingExpressionFeature(Expression(_expression); - typeMap.Features.Add(new CollectionMappingFeature(equivalentExpression)); + typeMap.Features.Set(new CollectionMappingFeature(equivalentExpression)); } - public IMappingExpressionFeature Reverse() + public IMappingFeature Reverse() { var reverseExpression = Expression.Lambda>(_expression.Body, _expression.Parameters[1], _expression.Parameters[0]); return new CollectionMappingExpressionFeature(reverseExpression); diff --git a/src/AutoMapper.Collection/Configuration/GeneratePropertyMapsExpressionFeature.cs b/src/AutoMapper.Collection/Configuration/GeneratePropertyMapsExpressionFeature.cs index 9c488ec..5c33650 100644 --- a/src/AutoMapper.Collection/Configuration/GeneratePropertyMapsExpressionFeature.cs +++ b/src/AutoMapper.Collection/Configuration/GeneratePropertyMapsExpressionFeature.cs @@ -1,12 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; -using AutoMapper.Collection.Execution; +using AutoMapper.Collection.Runtime; using AutoMapper.EquivalencyExpression; +using AutoMapper.Features; namespace AutoMapper.Collection.Configuration { - public class GeneratePropertyMapsExpressionFeature : IMapperConfigurationExpressionFeature + public class GeneratePropertyMapsnFeature : IGlobalFeature { private readonly List, IGeneratePropertyMaps>> _generators = new List, IGeneratePropertyMaps>>(); @@ -15,12 +16,12 @@ public void Add(Func, IGeneratePropertyMaps> creator) _generators.Add(creator); } - void IMapperConfigurationExpressionFeature.Configure(IConfigurationProvider configurationProvider) + void IGlobalFeature.Configure(IConfigurationProvider configurationProvider) { var generators = _generators .Select(x => x.Invoke(configurationProvider.ServiceCtor)) .ToList(); - configurationProvider.Features.Add(new GeneratePropertyMapsFeature(generators)); + configurationProvider.Features.Set(new GeneratePropertyMapsFeature(generators)); } } } diff --git a/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs b/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs index 5854602..3934e1d 100644 --- a/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs +++ b/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs @@ -2,7 +2,7 @@ using System.Linq; using System.Linq.Expressions; using AutoMapper.Collection.Configuration; -using AutoMapper.Collection.Execution; +using AutoMapper.Collection.Runtime; using AutoMapper.Mappers; namespace AutoMapper.EquivalencyExpression @@ -11,7 +11,7 @@ public static class EquivalentExpressions { public static void AddCollectionMappers(this IMapperConfigurationExpression cfg) { - cfg.AddFeature(new GeneratePropertyMapsExpressionFeature()); + cfg.Features.Set(new GeneratePropertyMapsnFeature()); cfg.InsertBefore( new ObjectToEquivalencyExpressionByEquivalencyExistingMapper(), new EquivalentExpressionAddRemoveCollectionMapper()); @@ -82,21 +82,21 @@ internal static IEquivalentComparer GetEquivalentExpression(IConfigurationProvid /// public static IMappingExpression EqualityComparison(this IMappingExpression mappingExpression, Expression> EquivalentExpression) { - mappingExpression.AddFeature(new CollectionMappingExpressionFeature(EquivalentExpression)); + mappingExpression.Features.Set(new CollectionMappingExpressionFeature(EquivalentExpression)); return mappingExpression; } public static void SetGeneratePropertyMaps(this IMapperConfigurationExpression cfg) where TGeneratePropertyMaps : IGeneratePropertyMaps { - (cfg.Features.Get() + (cfg.Features.Get() ?? throw new ArgumentException("Invoke the IMapperConfigurationExpression.AddCollectionMappers() before adding IGeneratePropertyMaps.")) .Add(serviceCtor => (IGeneratePropertyMaps)serviceCtor(typeof(TGeneratePropertyMaps))); } public static void SetGeneratePropertyMaps(this IMapperConfigurationExpression cfg, IGeneratePropertyMaps generatePropertyMaps) { - (cfg.Features.Get() + (cfg.Features.Get() ?? throw new ArgumentException("Invoke the IMapperConfigurationExpression.AddCollectionMappers() before adding IGeneratePropertyMaps.")) .Add(_ => generatePropertyMaps); } diff --git a/src/AutoMapper.Collection/Execution/CollectionMappingFeature.cs b/src/AutoMapper.Collection/Runtime/CollectionMappingFeature.cs similarity index 59% rename from src/AutoMapper.Collection/Execution/CollectionMappingFeature.cs rename to src/AutoMapper.Collection/Runtime/CollectionMappingFeature.cs index c0865d7..ee8eb1e 100644 --- a/src/AutoMapper.Collection/Execution/CollectionMappingFeature.cs +++ b/src/AutoMapper.Collection/Runtime/CollectionMappingFeature.cs @@ -1,8 +1,9 @@ using AutoMapper.EquivalencyExpression; +using AutoMapper.Features; -namespace AutoMapper.Collection.Execution +namespace AutoMapper.Collection.Runtime { - public class CollectionMappingFeature : IFeature + public class CollectionMappingFeature : IRuntimeFeature { public CollectionMappingFeature(IEquivalentComparer equivalentComparer) { @@ -11,7 +12,7 @@ public CollectionMappingFeature(IEquivalentComparer equivalentComparer) public IEquivalentComparer EquivalentComparer { get; } - void IFeature.Seal(IConfigurationProvider configurationProvider) + void IRuntimeFeature.Seal(IConfigurationProvider configurationProvider) { } } diff --git a/src/AutoMapper.Collection/Execution/GeneratePropertyMapsFeature.cs b/src/AutoMapper.Collection/Runtime/GeneratePropertyMapsnFeature.cs similarity index 92% rename from src/AutoMapper.Collection/Execution/GeneratePropertyMapsFeature.cs rename to src/AutoMapper.Collection/Runtime/GeneratePropertyMapsnFeature.cs index cdd903e..6e7b573 100644 --- a/src/AutoMapper.Collection/Execution/GeneratePropertyMapsFeature.cs +++ b/src/AutoMapper.Collection/Runtime/GeneratePropertyMapsnFeature.cs @@ -5,10 +5,11 @@ using System.Linq.Expressions; using System.Reflection; using AutoMapper.EquivalencyExpression; +using AutoMapper.Features; -namespace AutoMapper.Collection.Execution +namespace AutoMapper.Collection.Runtime { - public class GeneratePropertyMapsFeature : IFeature + public class GeneratePropertyMapsFeature : IRuntimeFeature { private readonly IList _generators; private readonly ConcurrentDictionary _comparers = new ConcurrentDictionary(); @@ -27,7 +28,7 @@ internal IEquivalentComparer Get(TypeMap typeMap) .FirstOrDefault(x => x != null)); } - void IFeature.Seal(IConfigurationProvider configurationProvider) + void IRuntimeFeature.Seal(IConfigurationProvider configurationProvider) { } From 56c722d1674da157538b056b28f93f055c7f44c0 Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Fri, 26 Apr 2019 23:15:43 +0200 Subject: [PATCH 10/32] Enable parallel test executions and use local IMapper-instances instead of the static Automapper.Mapper --- .../AssemblyInfo.cs | 3 - .../EntityFramworkTests.cs | 22 ++-- .../MappingTestBase.cs | 17 +++ .../AssemblyInfo.cs | 3 - .../InheritanceWithCollectionTests.cs | 28 ++--- .../MapCollectionWithEqualityTests.cs | 102 +++++++++--------- .../MappingTestBase.cs | 17 +++ .../NullableIdTests.cs | 12 +-- .../OptionsTests.cs | 10 +- 9 files changed, 112 insertions(+), 102 deletions(-) delete mode 100644 src/AutoMapper.Collection.EntityFramework.Tests/AssemblyInfo.cs create mode 100644 src/AutoMapper.Collection.EntityFramework.Tests/MappingTestBase.cs delete mode 100644 src/AutoMapper.Collection.Tests/AssemblyInfo.cs create mode 100644 src/AutoMapper.Collection.Tests/MappingTestBase.cs diff --git a/src/AutoMapper.Collection.EntityFramework.Tests/AssemblyInfo.cs b/src/AutoMapper.Collection.EntityFramework.Tests/AssemblyInfo.cs deleted file mode 100644 index 7db8497..0000000 --- a/src/AutoMapper.Collection.EntityFramework.Tests/AssemblyInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using Xunit; - -[assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file diff --git a/src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkTests.cs b/src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkTests.cs index b4d3cac..d680579 100644 --- a/src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkTests.cs +++ b/src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkTests.cs @@ -10,22 +10,20 @@ namespace AutoMapper.Collection.EntityFramework.Tests { - public class EntityFramworkTests + public class EntityFramworkTests : MappingTestBase { - public EntityFramworkTests() + private void ConfigureMapper(IMapperConfigurationExpression cfg) { - Mapper.Reset(); - Mapper.Initialize(x => - { - x.AddCollectionMappers(); - x.CreateMap().ReverseMap(); - x.SetGeneratePropertyMaps>(); - }); + cfg.AddCollectionMappers(); + cfg.CreateMap().ReverseMap(); + cfg.SetGeneratePropertyMaps>(); } [Fact] public void Should_Persist_To_Update() { + var mapper = CreateMapper(ConfigureMapper); + var db = new DB(); db.Things.Add(new Thing { Title = "Test2" }); db.Things.Add(new Thing { Title = "Test3" }); @@ -36,7 +34,7 @@ public void Should_Persist_To_Update() var item = db.Things.First(); - db.Things.Persist().InsertOrUpdate(new ThingDto { ID = item.ID, Title = "Test" }); + db.Things.Persist(mapper).InsertOrUpdate(new ThingDto { ID = item.ID, Title = "Test" }); Assert.Equal(1, db.ChangeTracker.Entries().Count(x => x.State == EntityState.Modified)); Assert.Equal(3, db.Things.Count()); @@ -47,6 +45,8 @@ public void Should_Persist_To_Update() [Fact] public void Should_Persist_To_Insert() { + var mapper = CreateMapper(ConfigureMapper); + var db = new DB(); db.Things.Add(new Thing { Title = "Test2" }); db.Things.Add(new Thing { Title = "Test3" }); @@ -55,7 +55,7 @@ public void Should_Persist_To_Insert() Assert.Equal(3, db.Things.Count()); - db.Things.Persist().InsertOrUpdate(new ThingDto { Title = "Test" }); + db.Things.Persist(mapper).InsertOrUpdate(new ThingDto { Title = "Test" }); Assert.Equal(3, db.Things.Count()); Assert.Equal(1, db.ChangeTracker.Entries().Count(x => x.State == EntityState.Added)); diff --git a/src/AutoMapper.Collection.EntityFramework.Tests/MappingTestBase.cs b/src/AutoMapper.Collection.EntityFramework.Tests/MappingTestBase.cs new file mode 100644 index 0000000..31dbfca --- /dev/null +++ b/src/AutoMapper.Collection.EntityFramework.Tests/MappingTestBase.cs @@ -0,0 +1,17 @@ +using System; + +namespace AutoMapper.Collection +{ + public abstract class MappingTestBase + { + protected IMapper CreateMapper(Action cfg) + { + var map = new MapperConfiguration(cfg); + map.CompileMappings(); + + var mapper = map.CreateMapper(); + mapper.ConfigurationProvider.AssertConfigurationIsValid(); + return mapper; + } + } +} diff --git a/src/AutoMapper.Collection.Tests/AssemblyInfo.cs b/src/AutoMapper.Collection.Tests/AssemblyInfo.cs deleted file mode 100644 index 7db8497..0000000 --- a/src/AutoMapper.Collection.Tests/AssemblyInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using Xunit; - -[assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file diff --git a/src/AutoMapper.Collection.Tests/InheritanceWithCollectionTests.cs b/src/AutoMapper.Collection.Tests/InheritanceWithCollectionTests.cs index dc5fe87..d483965 100644 --- a/src/AutoMapper.Collection.Tests/InheritanceWithCollectionTests.cs +++ b/src/AutoMapper.Collection.Tests/InheritanceWithCollectionTests.cs @@ -1,31 +1,19 @@ using AutoMapper.EquivalencyExpression; -using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using FluentAssertions; using Xunit; namespace AutoMapper.Collection { - public abstract class InheritanceWithCollectionTests + public abstract class InheritanceWithCollectionTests : MappingTestBase { - protected abstract void Create(IMapperConfigurationExpression cfg); - - private IMapper CreateMapper() - { - var map = new MapperConfiguration(Create); - map.AssertConfigurationIsValid(); - map.CompileMappings(); - - return map.CreateMapper(); - } + protected abstract void ConfigureMapper(IMapperConfigurationExpression cfg); [Fact] public void TypeMap_Should_include_base_types() { - var mapper = CreateMapper(); + var mapper = CreateMapper(ConfigureMapper); var typeMap = mapper.ConfigurationProvider.ResolveTypeMap(typeof(MailOrderDomain), typeof(OrderEf)); var typePairs = new[]{ @@ -37,7 +25,7 @@ public void TypeMap_Should_include_base_types() [Fact] public void TypeMap_Should_include_derivied_types() { - var mapper = CreateMapper(); + var mapper = CreateMapper(ConfigureMapper); var typeMap = mapper.ConfigurationProvider.ResolveTypeMap(typeof(OrderDomain), typeof(OrderEf)); var typePairs = new[]{ @@ -50,7 +38,7 @@ public void TypeMap_Should_include_derivied_types() [Fact] public void Map_Should_ReturnOnlineOrderEf_When_ListIsOfTypeOrderEf() { - var mapper = CreateMapper(); + var mapper = CreateMapper(ConfigureMapper); //arrange var orderDomain = new OnlineOrderDomain { Id = "Id", Key = "Key" }; @@ -85,7 +73,7 @@ public void Map_Should_ReturnOnlineOrderEf_When_ListIsOfTypeOrderEf() [Fact] public void Map_FromEfToDomain_And_AddAnOnlineOrderInTheDomainObject_And_ThenMapBackToEf_Should_UseTheSameReferenceInTheEfCollection() { - var mapper = CreateMapper(); + var mapper = CreateMapper(ConfigureMapper); //arrange var onlineOrderEf = new OnlineOrderEf { Id = "Id", Key = "Key" }; @@ -193,7 +181,7 @@ public List Resolve(RootDomain source, RootEf destination, List propertyInfo.GetMethod.IsPublic || propertyInfo.GetMethod.IsAssembly || propertyInfo.GetMethod.IsFamily || propertyInfo.GetMethod.IsPrivate; cfg.AddCollectionMappers(); @@ -238,7 +226,7 @@ protected override void Create(IMapperConfigurationExpression cfg) public class IncludeBase : InheritanceWithCollectionTests { - protected override void Create(IMapperConfigurationExpression cfg) + protected override void ConfigureMapper(IMapperConfigurationExpression cfg) { cfg.ShouldMapProperty = propertyInfo => propertyInfo.GetMethod.IsPublic || propertyInfo.GetMethod.IsAssembly || propertyInfo.GetMethod.IsFamily || propertyInfo.GetMethod.IsPrivate; cfg.AddCollectionMappers(); diff --git a/src/AutoMapper.Collection.Tests/MapCollectionWithEqualityTests.cs b/src/AutoMapper.Collection.Tests/MapCollectionWithEqualityTests.cs index 48cbf9e..78382d7 100644 --- a/src/AutoMapper.Collection.Tests/MapCollectionWithEqualityTests.cs +++ b/src/AutoMapper.Collection.Tests/MapCollectionWithEqualityTests.cs @@ -7,21 +7,18 @@ namespace AutoMapper.Collection { - public class MapCollectionWithEqualityTests + public class MapCollectionWithEqualityTests : MappingTestBase { - public MapCollectionWithEqualityTests() + protected virtual void ConfigureMapper(IMapperConfigurationExpression cfg) { - Mapper.Reset(); - Mapper.Initialize(x => - { - x.AddCollectionMappers(); - x.CreateMap().EqualityComparison((dto, entity) => dto.ID == entity.ID); - }); + cfg.AddCollectionMappers(); + cfg.CreateMap().EqualityComparison((dto, entity) => dto.ID == entity.ID); } [Fact] public void Should_Keep_Existing_List() { + var mapper = CreateMapper(ConfigureMapper); var dtos = new List { new ThingDto { ID = 1, Title = "test0" }, @@ -34,12 +31,14 @@ public void Should_Keep_Existing_List() new Thing { ID = 3, Title = "test3" }, }; - Mapper.Map(dtos, items).Should().BeSameAs(items); + mapper.Map(dtos, items).Should().BeSameAs(items); } [Fact] public void Should_Update_Existing_Item() { + var mapper = CreateMapper(ConfigureMapper); + var dtos = new List { new ThingDto { ID = 1, Title = "test0" }, @@ -52,143 +51,141 @@ public void Should_Update_Existing_Item() new Thing { ID = 3, Title = "test3" }, }; - Mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First()); + mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First()); } [Fact] public void Should_Be_Fast_With_Large_Lists() { + var mapper = CreateMapper(ConfigureMapper); + var dtos = new object[100000].Select((_, i) => new ThingDto { ID = i }).ToList(); var items = new object[100000].Select((_, i) => new Thing { ID = i }).ToList(); - Mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First()); + mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First()); } [Fact] public void Should_Be_Fast_With_Large_Reversed_Lists() { + var mapper = CreateMapper(ConfigureMapper); + var dtos = new object[100000].Select((_, i) => new ThingDto { ID = i }).ToList(); dtos.Reverse(); var items = new object[100000].Select((_, i) => new Thing { ID = i }).ToList(); - Mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First()); + mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First()); } [Fact] public void Should_Be_Fast_With_Large_Lists_MultiProperty_Mapping() { - Mapper.Reset(); - Mapper.Initialize(x => + var mapper = CreateMapper(x => { x.AddCollectionMappers(); - x.CreateMap().EqualityComparison((dto, entity) => dto.ID == entity.ID && dto.ID == entity.ID); + x.CreateMap().EqualityComparison((ThingDto dto, Thing entity) => dto.ID == entity.ID && dto.ID == entity.ID); }); var dtos = new object[100000].Select((_, i) => new ThingDto { ID = i }).ToList(); var items = new object[100000].Select((_, i) => new Thing { ID = i }).ToList(); - Mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First()); + mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First()); } [Fact] public void Should_Be_Fast_With_Large_Lists_MultiProperty_Mapping_Cant_Extract() { - Mapper.Reset(); - Mapper.Initialize(x => + var mapper = CreateMapper(x => { x.AddCollectionMappers(); - x.CreateMap().EqualityComparison((dto, entity) => dto.ID == entity.ID || dto.ID == entity.ID); + x.CreateMap().EqualityComparison((ThingDto dto, Thing entity) => dto.ID == entity.ID || dto.ID == entity.ID); }); var dtos = new object[100000].Select((_, i) => new ThingDto { ID = i }).ToList(); var items = new object[100000].Select((_, i) => new Thing { ID = i }).ToList(); - Mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First()); + mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First()); } [Fact] public void Should_Be_Fast_With_Large_Lists_Cant_Extract_Negative() { - Mapper.Reset(); - Mapper.Initialize(x => + var mapper = CreateMapper(x => { x.AddCollectionMappers(); // ReSharper disable once NegativeEqualityExpression - x.CreateMap().EqualityComparison((dto, entity) => !(dto.ID != entity.ID)); + x.CreateMap().EqualityComparison((ThingDto dto, Thing entity) => !(dto.ID != entity.ID)); }); var dtos = new object[100000].Select((_, i) => new ThingDto { ID = i }).ToList(); var items = new object[100000].Select((_, i) => new Thing { ID = i }).ToList(); - Mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First()); + mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First()); } [Fact] public void Should_Be_Fast_With_Large_Lists_MultiProperty_Mapping_Cant_Extract_Negative() { - Mapper.Reset(); - Mapper.Initialize(x => + var mapper = CreateMapper(x => { x.AddCollectionMappers(); // ReSharper disable once NegativeEqualityExpression - x.CreateMap().EqualityComparison((dto, entity) => dto.ID == entity.ID && !(dto.ID != entity.ID)); + x.CreateMap().EqualityComparison((ThingDto dto, Thing entity) => dto.ID == entity.ID && !(dto.ID != entity.ID)); }); var dtos = new object[100000].Select((_, i) => new ThingDto { ID = i }).ToList(); var items = new object[100000].Select((_, i) => new Thing { ID = i }).ToList(); - Mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First()); + mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First()); } [Fact] public void Should_Be_Fast_With_Large_Lists_SubObject() { - Mapper.Reset(); - Mapper.Initialize(x => + var mapper = CreateMapper(x => { x.AddCollectionMappers(); - x.CreateMap().EqualityComparison((source, dest) => dest.ID == (source is ThingSubDto ? ((ThingSubDto)source).ID2 : source.ID)); + x.CreateMap().EqualityComparison((ThingDto source, Thing dest) => dest.ID == (source is ThingSubDto ? ((ThingSubDto)source).ID2 : source.ID)); }); var dtos = new object[100000].Select((_, i) => new ThingSubDto { ID = i + 100000 }).Cast().ToList(); var items = new object[100000].Select((_, i) => new Thing { ID = i }).ToList(); - Mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First()); + mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First()); } [Fact] public void Should_Be_Fast_With_Large_Lists_SubObject_switch_left_and_right_expression() { - Mapper.Reset(); - Mapper.Initialize(x => + var mapper = CreateMapper(x => { x.AddCollectionMappers(); - x.CreateMap().EqualityComparison((source, dest) => (source is ThingSubDto ? ((ThingSubDto)source).ID2 : source.ID) == dest.ID); + x.CreateMap().EqualityComparison((ThingDto source, Thing dest) => (source is ThingSubDto ? ((ThingSubDto)source).ID2 : source.ID) == dest.ID); }); var dtos = new object[100000].Select((_, i) => new ThingSubDto { ID = i + 100000 }).ToList(); var items = new object[100000].Select((_, i) => new Thing { ID = i }).ToList(); - Mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First()); + mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First()); } [Fact] public void Should_Work_With_Conditionals() { - Mapper.Reset(); - Mapper.Initialize(cfg => + var mapper = CreateMapper(cfg => { cfg.AddCollectionMappers(); cfg.CreateMap() - .EqualityComparison((src, dest) => dest.DtoId == 0 ? src.Code == dest.Code : src.Id == dest.DtoId); + .ForMember(x => x.DtoId, m => m.Ignore()) + .EqualityComparison((ClientDto src, Client dest) => dest.DtoId == 0 ? src.Code == dest.Code : src.Id == dest.DtoId); }); var dto = new ClientDto @@ -199,7 +196,7 @@ public void Should_Work_With_Conditionals() var entity = new Client {Code = dto.Code, Id = 42}; var entityCollection = new List {entity}; - Mapper.Map(new[] {dto}, entityCollection); + mapper.Map(new[] { dto }, entityCollection); entity.ShouldBeEquivalentTo(entityCollection[0]); } @@ -221,37 +218,38 @@ public class ClientDto [Fact] public void Should_Work_With_Null_Destination() { + var mapper = CreateMapper(ConfigureMapper); + var dtos = new List { new ThingDto { ID = 1, Title = "test0" }, new ThingDto { ID = 2, Title = "test2" } }; - Mapper.Map>(dtos).Should().HaveSameCount(dtos); + mapper.Map>(dtos).Should().HaveSameCount(dtos); } [Fact] public void Should_Work_With_Comparing_String_Types() { - Mapper.Reset(); - Mapper.Initialize(cfg => + var mapper = CreateMapper(cfg => { cfg.AddCollectionMappers(); cfg.CreateMap() - .ForMember(d => d.SaleId, o => o.Ignore()) - .EqualityComparison((c, sc) => sc.Category == c.Category && sc.Description == c.Description); + .ForMember(d => d.SaleId, (IMemberConfigurationExpression o) => o.Ignore()) + .EqualityComparison((Charge c, SaleCharge sc) => sc.Category == c.Category && sc.Description == c.Description); cfg.CreateMap() .ConstructUsing( (saleCharge => new Charge(saleCharge.Category, saleCharge.Description, saleCharge.Value))) - .EqualityComparison((sc, c) => sc.Category == c.Category && sc.Description == c.Description); + .EqualityComparison((SaleCharge sc, Charge c) => sc.Category == c.Category && sc.Description == c.Description); }); var dto = new Charge("catagory", "description", 5); var entity = new SaleCharge { Category = dto.Category, Description = dto.Description }; var entityCollection = new List { entity }; - Mapper.Map(new[] { dto }, entityCollection); + mapper.Map(new[] { dto }, entityCollection); entity.ShouldBeEquivalentTo(entityCollection[0]); } @@ -313,8 +311,7 @@ public class SaleCharge [Fact] public void Should_Be_Instanced_Based() { - Mapper.Reset(); - Mapper.Initialize(x => + var mapper = CreateMapper(x => { x.AddCollectionMappers(); x.CreateMap().ReverseMap(); @@ -332,13 +329,13 @@ public void Should_Be_Instanced_Based() new Thing { ID = 3, Title = "test3" }, }; - Mapper.Map(dtos, items.ToList()).Should().NotContain(items.First()); + mapper.Map(dtos, items.ToList()).Should().NotContain(items.First()); } [Fact] public void Parent_Should_Be_Same_As_Root_Object() { - var mapper = new MapperConfiguration( + var mapper = CreateMapper( cfg => { cfg.AddCollectionMappers(); @@ -347,8 +344,7 @@ public void Parent_Should_Be_Same_As_Root_Object() cfg.CreateMap() .EqualityComparison((src, dst) => src.ID == dst.ID) .PreserveReferences(); - }) - .CreateMapper(); + }); var root = new ThingWithCollection() { diff --git a/src/AutoMapper.Collection.Tests/MappingTestBase.cs b/src/AutoMapper.Collection.Tests/MappingTestBase.cs new file mode 100644 index 0000000..31dbfca --- /dev/null +++ b/src/AutoMapper.Collection.Tests/MappingTestBase.cs @@ -0,0 +1,17 @@ +using System; + +namespace AutoMapper.Collection +{ + public abstract class MappingTestBase + { + protected IMapper CreateMapper(Action cfg) + { + var map = new MapperConfiguration(cfg); + map.CompileMappings(); + + var mapper = map.CreateMapper(); + mapper.ConfigurationProvider.AssertConfigurationIsValid(); + return mapper; + } + } +} diff --git a/src/AutoMapper.Collection.Tests/NullableIdTests.cs b/src/AutoMapper.Collection.Tests/NullableIdTests.cs index 9a8d946..e9d48ce 100644 --- a/src/AutoMapper.Collection.Tests/NullableIdTests.cs +++ b/src/AutoMapper.Collection.Tests/NullableIdTests.cs @@ -5,13 +5,12 @@ namespace AutoMapper.Collection { - public class NullableIdTests + public class NullableIdTests : MappingTestBase { [Fact] public void Should_Work_With_Null_Id() { - Mapper.Reset(); - Mapper.Initialize(x => + var mapper = CreateMapper(x => { x.AddCollectionMappers(); x.CreateMap().EqualityComparison((dto, entity) => dto.ID == entity.ID); @@ -30,7 +29,7 @@ public void Should_Work_With_Null_Id() new ThingWithStringIdDto { Title = "test3" } }; - Mapper.Map(dtos, original); + mapper.Map(dtos, original); original.Should().HaveSameCount(dtos); } @@ -39,8 +38,7 @@ public void Should_Work_With_Null_Id() [Fact] public void Should_Work_With_Multiple_Null_Id() { - Mapper.Reset(); - Mapper.Initialize(x => + var mapper = CreateMapper(x => { x.AddCollectionMappers(); x.CreateMap().EqualityComparison((dto, entity) => dto.ID == entity.ID); @@ -61,7 +59,7 @@ public void Should_Work_With_Multiple_Null_Id() new ThingWithStringIdDto { Title = "test4" }, }; - Mapper.Map(dtos, original); + mapper.Map(dtos, original); original.Should().HaveSameCount(dtos); } diff --git a/src/AutoMapper.Collection.Tests/OptionsTests.cs b/src/AutoMapper.Collection.Tests/OptionsTests.cs index f4b3634..e9558ef 100644 --- a/src/AutoMapper.Collection.Tests/OptionsTests.cs +++ b/src/AutoMapper.Collection.Tests/OptionsTests.cs @@ -6,23 +6,23 @@ namespace AutoMapper.Collection { - public class OptionsTests + public class OptionsTests : MappingTestBase { [Fact] public void Should_Retain_Options_Passed_In_Map() { var collectionTestValue = 0; - var collectionMapper = new MapperConfiguration(cfg => + var collectionMapper = CreateMapper(cfg => { cfg.AddCollectionMappers(); cfg.CreateMap().EqualityComparison((dto, entity) => dto.ID == entity.ID).AfterMap((_, __, ctx) => collectionTestValue = (int)ctx.Options.Items["Test"]); - }).CreateMapper(); + }); var normalTestValue = 0; - var normalMapper = new MapperConfiguration(cfg => + var normalMapper = CreateMapper(cfg => { cfg.CreateMap().AfterMap((_, __, ctx) => normalTestValue = (int)ctx.Options.Items["Test"]); - }).CreateMapper(); + }); var dtos = new List { From 64d6ab4b9c8e19ae475fdf963b2f0e4c9205f2a8 Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Sat, 27 Apr 2019 09:03:10 +0200 Subject: [PATCH 11/32] Update Readme --- README.md | 98 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 6fde3f3..128aa60 100644 --- a/README.md +++ b/README.md @@ -1,74 +1,88 @@ AutoMapper -AutoMapper.Collection -================================ +# AutoMapper.Collection Adds ability to map collections to existing collections without re-creating the collection object. Will Add/Update/Delete items from a preexisting collection object based on user defined equivalency between the collection's generic item type from the source collection and the destination collection. -How to add to AutoMapper? --------------------------------- +## How to add to AutoMapper? Call AddCollectionMappers when configuring - - Mapper.Initialize(cfg => - { - cfg.AddCollectionMappers(); - // Configuration code - }); +``` +Mapper.Initialize(cfg => +{ + cfg.AddCollectionMappers(); + // Configuration code +}); +``` Will add new IObjectMapper objects into the master mapping list. -Adding equivalency between two classes --------------------------------- +## Adding equivalency between two classes Adding equivalence to objects is done with EqualityComparison extended from the IMappingExpression class. - - cfg.CreateMap().EqualityComparison((odto, o) => odto.ID == o.ID); +``` +cfg.CreateMap().EqualityComparison((odto, o) => odto.ID == o.ID); +``` Mapping OrderDTO back to Order will compare Order items list based on if their ID's match - - Mapper.Map,List>(orderDtos, orders); +``` +Mapper.Map,List>(orderDtos, orders); +``` If ID's match will map OrderDTO to Order If OrderDTO exists and Order doesn't add to collection If Order exists and OrderDTO doesn't remove from collection -Why update collection? Just recreate it -------------------------------- +## Why update collection? Just recreate it ORMs don't like setting the collection, so you need to add and remove from preexisting one. This automates the process by just specifying what is equal to each other. -Can it just figure out the ID equivalency for me in EF? -------------------------------- -Automapper.Collection.EntityFramework can do that for you. - - Mapper.Initialize(cfg => - { - cfg.AddCollectionMappers(); - cfg.SetGeneratePropertyMaps>(); - // Configuration code - }); +## Can it just figure out the ID equivalency for me in Entity Framework? +`Automapper.Collection.EntityFramework` or `Automapper.Collection.EntityFrameworkCore` can do that for you. + +``` +Mapper.Initialize(cfg => +{ + cfg.AddCollectionMappers(); + cfg.SetGeneratePropertyMaps>(); + // Configuration code +}); +``` User defined equality expressions will overwrite primary key expressions. -What about comparing to a single existing Entity for updating? --------------------------------- +## What about comparing to a single existing Entity for updating? Automapper.Collection.EntityFramework does that as well through extension method from of DbSet. Translate equality between dto and EF object to an expression of just the EF using the dto's values as constants. - - dbContext.Orders.Persist().InsertOrUpdate(newOrderDto); - dbContext.Orders.Persist().InsertOrUpdate(existingOrderDto); - dbContext.Orders.Persist().Remove(deletedOrderDto); - dbContext.SubmitChanges(); +``` +dbContext.Orders.Persist().InsertOrUpdate(newOrderDto); +dbContext.Orders.Persist().InsertOrUpdate(existingOrderDto); +dbContext.Orders.Persist().Remove(deletedOrderDto); +dbContext.SubmitChanges(); +``` **Note:** This is done by converting the OrderDTO to Expression> and using that to find matching type in the database. You can also map objects to expressions as well. Persist doesn't call submit changes automatically -How to get it --------------------------------- -On Nuget +## Where can I get it? + +First, [install NuGet](http://docs.nuget.org/docs/start-here/installing-nuget). Then, install [AutoMapper.Collection](https://www.nuget.org/packages/AutoMapper.Collection/) from the package manager console: +``` +PM> Install-Package AutoMapper.Collection +``` + +### Additional packages + +#### AutoMapper Collection for Entity Framework +``` +PM> Install-Package AutoMapper.Collection.EntityFramework +``` - PM> Install-Package AutoMapper.Collection - PM> Install-Package AutoMapper.Collection.EntityFramework -Also have AutoMapper.LinqToSQL +#### AutoMapper Collection for Entity Framework Core +``` +PM> Install-Package AutoMapper.Collection.EntityFrameworkCore +``` - PM> Install-Package AutoMapper.Collection.LinqToSQL +#### AutoMapper Collection for LinqToSQL +``` +PM> Install-Package AutoMapper.Collection.LinqToSQL +``` \ No newline at end of file From 8d3011d726e76c52e91a2569710aa94dd28a7a7e Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Sat, 27 Apr 2019 09:03:28 +0200 Subject: [PATCH 12/32] Bump version to 5.0.1 --- version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.props b/version.props index 93b95f3..d6de0cd 100644 --- a/version.props +++ b/version.props @@ -1,5 +1,5 @@ - 5.0.0 + 5.0.1 From 702ce32ad9545f896f9dd01a08223195b549e06f Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Sat, 27 Apr 2019 11:53:46 +0200 Subject: [PATCH 13/32] Test for mapping resolving when the mapping object not is specified during configuration and have circular references. --- .../NotExistingTests.cs | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/AutoMapper.Collection.Tests/NotExistingTests.cs b/src/AutoMapper.Collection.Tests/NotExistingTests.cs index 328f844..d56589e 100644 --- a/src/AutoMapper.Collection.Tests/NotExistingTests.cs +++ b/src/AutoMapper.Collection.Tests/NotExistingTests.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using AutoMapper.EquivalencyExpression; using FluentAssertions; @@ -43,14 +44,14 @@ public void Should_not_throw_exception_for_nonexisting_types() assertModel.Contacts.Single().Name.Should().Be(originalModel.Contacts.Single().Name); assertModel.Contacts.Single().Emails.Single().Address.Should().Be(originalModel.Contacts.Single().Emails.Single().Address); + assertModel.Contacts.Single().Emails.Add(new EmailViewModel { Address = "jane@doe.com" }); + mapper.Map(assertModel, originalModel); - // This tests if equality was found and mapped to pre-existing object and not defaulting to AM and clearing and regenerating the list - originalModel.Contacts.Single().Emails.Single().Should().Be(originalEmail); } public class System { - public int Id { get; set; } + public Guid Id { get; set; } = Guid.NewGuid(); public string Name { get; set; } public ICollection Contacts { get; set; } @@ -58,8 +59,8 @@ public class System public class Contact { - public int Id { get; set; } - public int SystemId { get; set; } + public Guid Id { get; set; } = Guid.NewGuid(); + public Guid SystemId { get; set; } public string Name { get; set; } public System System { get; set; } @@ -69,9 +70,9 @@ public class Contact public class Email { - public int Id { get; set; } + public Guid Id { get; set; } = Guid.NewGuid(); - public int ContactId { get; set; } + public Guid ContactId { get; set; } public string Address { get; set; } public Contact Contact { get; set; } @@ -79,7 +80,7 @@ public class Email public class SystemViewModel { - public int Id { get; set; } + public Guid Id { get; set; } public string Name { get; set; } public ICollection Contacts { get; set; } @@ -87,8 +88,8 @@ public class SystemViewModel public class ContactViewModel { - public int Id { get; set; } - public int SystemId { get; set; } + public Guid Id { get; set; } + public Guid SystemId { get; set; } public string Name { get; set; } public SystemViewModel System { get; set; } @@ -98,8 +99,8 @@ public class ContactViewModel public class EmailViewModel { - public int Id { get; set; } - public int ContactId { get; set; } + public Guid Id { get; set; } + public Guid ContactId { get; set; } public string Address { get; set; } public ContactViewModel Contact { get; set; } From ec3b19f779fd15ddfa65320acba2ad2eb0e78d2f Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Sat, 27 Apr 2019 11:54:01 +0200 Subject: [PATCH 14/32] Test for mapping resolving when the mapping object not is specified during configuration and have circular references with EF. --- .../EntityFramworkUnmappedTypes.cs | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkUnmappedTypes.cs diff --git a/src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkUnmappedTypes.cs b/src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkUnmappedTypes.cs new file mode 100644 index 0000000..6aa209f --- /dev/null +++ b/src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkUnmappedTypes.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Data.Entity; +using System.Data.SqlServerCe; +using System.Linq; +using AutoMapper.EntityFramework; +using AutoMapper.EquivalencyExpression; +using FluentAssertions; +using Xunit; + +namespace AutoMapper.Collection.EntityFramework.Tests +{ + public class EntityFramworkUnmappedTypes + { + private void ConfigureMapper(IMapperConfigurationExpression cfg) + { + cfg.AddCollectionMappers(); + cfg.SetGeneratePropertyMaps>(); + } + + protected IMapper CreateMapper(Action cfg) + { + var map = new MapperConfiguration(cfg); + map.CompileMappings(); + + var mapper = map.CreateMapper(); + mapper.ConfigurationProvider.AssertConfigurationIsValid(); + return mapper; + } + + + [Fact] + public void Should_not_throw_exception_for_nonexisting_types() + { + var mapper = CreateMapper(ConfigureMapper); + + var originalModel = new System + { + Name = "My First System", + Contacts = new List + { + new Contact + { + Name = "John", + Emails = new List() + { + new Email + { + Address = "john@doe.com" + } + } + } + } + }; + + var originalEmail = originalModel.Contacts.Single().Emails.Single(); + + var assertModel = mapper.Map(originalModel); + assertModel.Name.Should().Be(originalModel.Name); + assertModel.Contacts.Single().Name.Should().Be(originalModel.Contacts.Single().Name); + assertModel.Contacts.Single().Emails.Single().Address.Should().Be(originalModel.Contacts.Single().Emails.Single().Address); + + assertModel.Contacts.Single().Emails.Add(new EmailViewModel { Address = "jane@doe.com" }); + + mapper.Map(assertModel, originalModel); + // This tests if equality was found and mapped to pre-existing object and not defaulting to AM and clearing and regenerating the list + originalModel.Contacts.Single().Emails.First().Should().Be(originalEmail); + } + + public class DB : DbContext + { + public DB() + : base(new SqlCeConnection("Data Source=MyDatabase.sdf;Persist Security Info=False;"), contextOwnsConnection: true) + { + } + + public DbSet Systems { get; set; } + public DbSet Contacts { get; set; } + public DbSet Emails { get; set; } + } + + + public class System + { + public Guid Id { get; set; } = Guid.NewGuid(); + public string Name { get; set; } + + public ICollection Contacts { get; set; } + } + + public class Contact + { + public Guid Id { get; set; } = Guid.NewGuid(); + public Guid SystemId { get; set; } + public string Name { get; set; } + + [ForeignKey("SystemId")] + public System System { get; set; } + + public ICollection Emails { get; set; } + } + + public class Email + { + public Guid Id { get; set; } = Guid.NewGuid(); + + public Guid ContactId { get; set; } + public string Address { get; set; } + + [ForeignKey("ContactId")] + public Contact Contact { get; set; } + } + + public class SystemViewModel + { + public Guid Id { get; set; } + public string Name { get; set; } + + public ICollection Contacts { get; set; } + } + + public class ContactViewModel + { + public Guid Id { get; set; } + public Guid SystemId { get; set; } + public string Name { get; set; } + + public SystemViewModel System { get; set; } + + public ICollection Emails { get; set; } + } + + public class EmailViewModel + { + public Guid Id { get; set; } + public Guid ContactId { get; set; } + public string Address { get; set; } + + public ContactViewModel Contact { get; set; } + } + } +} From 4a89390fc61e690fff74ea28d31a87e51a37da5c Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Sat, 27 Apr 2019 11:54:57 +0200 Subject: [PATCH 15/32] Rewrite the logic to find the correct equivalend expression to avoid circular loop in AM. --- ...lentExpressionAddRemoveCollectionMapper.cs | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs b/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs index 34f78cf..7238d4e 100644 --- a/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs +++ b/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs @@ -10,7 +10,7 @@ namespace AutoMapper.Mappers { public class EquivalentExpressionAddRemoveCollectionMapper : IConfigurationObjectMapper { - private readonly CollectionMapper CollectionMapper = new CollectionMapper(); + private readonly CollectionMapper _collectionMapper = new CollectionMapper(); public IConfigurationProvider ConfigurationProvider { get; set; } @@ -62,37 +62,50 @@ public static TDestination Map _.IsStatic); + private static readonly MethodInfo _mapMethodInfo = typeof(EquivalentExpressionAddRemoveCollectionMapper).GetRuntimeMethods().Single(_ => _.IsStatic && _.Name == nameof(Map)); public bool IsMatch(TypePair typePair) { - if (typePair.SourceType.IsEnumerableType() - && typePair.DestinationType.IsCollectionType()) - { - var realType = new TypePair(TypeHelper.GetElementType(typePair.SourceType), TypeHelper.GetElementType(typePair.DestinationType)); - - return realType != typePair - && this.GetEquivalentExpression(realType.SourceType, realType.DestinationType) != null; - } + return typePair.SourceType.IsEnumerableType() + && typePair.DestinationType.IsCollectionType(); + } - return false; + public TypePair GetAssociatedTypes(TypePair initialTypes) + { + return new TypePair(TypeHelper.GetElementType(initialTypes.SourceType), TypeHelper.GetElementType(initialTypes.DestinationType)); } public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, IMemberMap memberMap, Expression sourceExpression, Expression destExpression, Expression contextExpression) { + IObjectMapper nextMapper = _collectionMapper; + var typePair = new TypePair(sourceExpression.Type, destExpression.Type); + var mappers = new List(configurationProvider.GetMappers()); + for (var i = mappers.IndexOf(this) + 1; i < mappers.Count; i++) + { + var mapper = mappers[i]; + if (mapper.IsMatch(typePair)) + { + nextMapper = mapper; + break; + } + } + var nextMapperExpression = nextMapper.MapExpression(configurationProvider, profileMap, memberMap, sourceExpression, destExpression, contextExpression); + var sourceType = TypeHelper.GetElementType(sourceExpression.Type); var destType = TypeHelper.GetElementType(destExpression.Type); - var method = MapMethodInfo.MakeGenericMethod(sourceExpression.Type, sourceType, destExpression.Type, destType); var equivalencyExpression = this.GetEquivalentExpression(sourceType, destType); + if (equivalencyExpression == null) + { + return nextMapperExpression; + } - var equivalencyExpressionConst = Constant(equivalencyExpression); - var map = Call(null, method, sourceExpression, destExpression, contextExpression, equivalencyExpressionConst); + var method = _mapMethodInfo.MakeGenericMethod(sourceExpression.Type, sourceType, destExpression.Type, destType); + var map = Call(null, method, sourceExpression, destExpression, contextExpression, Constant(equivalencyExpression)); var notNull = NotEqual(destExpression, Constant(null)); - var collectionMap = CollectionMapper.MapExpression(configurationProvider, profileMap, memberMap, sourceExpression, destExpression, contextExpression); - return Condition(notNull, map, Convert(collectionMap, destExpression.Type)); + return Condition(notNull, map, Convert(nextMapperExpression, destExpression.Type)); } } } From cda5165981a9b6f5529f6a7837319798e079b39a Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Tue, 30 Apr 2019 17:33:31 +0200 Subject: [PATCH 16/32] Remove unused code --- .../Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs b/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs index 7238d4e..51c9e8d 100644 --- a/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs +++ b/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs @@ -70,11 +70,6 @@ public bool IsMatch(TypePair typePair) && typePair.DestinationType.IsCollectionType(); } - public TypePair GetAssociatedTypes(TypePair initialTypes) - { - return new TypePair(TypeHelper.GetElementType(initialTypes.SourceType), TypeHelper.GetElementType(initialTypes.DestinationType)); - } - public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, IMemberMap memberMap, Expression sourceExpression, Expression destExpression, Expression contextExpression) { From 606d222e5a26fe912d20bf0d452c499ccf90f3d3 Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Tue, 30 Apr 2019 17:33:57 +0200 Subject: [PATCH 17/32] Correct expression variable name --- .../Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs b/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs index 51c9e8d..4a84ff3 100644 --- a/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs +++ b/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs @@ -62,7 +62,7 @@ public static TDestination Map _.IsStatic && _.Name == nameof(Map)); + private static readonly MethodInfo _mapMethodInfo = typeof(EquivalentExpressionAddRemoveCollectionMapper).GetRuntimeMethods().Single(x => x.IsStatic && x.Name == nameof(Map)); public bool IsMatch(TypePair typePair) { From 724c1bc1faab57e450ad4ad4c21dd6cd061e8fed Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Mon, 6 May 2019 21:54:05 +0200 Subject: [PATCH 18/32] Only find the next IObjectMapper if the equivalencyExpression is null. --- ...lentExpressionAddRemoveCollectionMapper.cs | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs b/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs index 4a84ff3..c787c01 100644 --- a/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs +++ b/src/AutoMapper.Collection/Mappers/EquivalentExpressionAddRemoveCollectionMapper.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -63,6 +64,7 @@ public static TDestination Map x.IsStatic && x.Name == nameof(Map)); + private static readonly ConcurrentDictionary _objectMapperCache = new ConcurrentDictionary(); public bool IsMatch(TypePair typePair) { @@ -73,34 +75,35 @@ public bool IsMatch(TypePair typePair) public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, IMemberMap memberMap, Expression sourceExpression, Expression destExpression, Expression contextExpression) { - IObjectMapper nextMapper = _collectionMapper; - var typePair = new TypePair(sourceExpression.Type, destExpression.Type); - var mappers = new List(configurationProvider.GetMappers()); - for (var i = mappers.IndexOf(this) + 1; i < mappers.Count; i++) - { - var mapper = mappers[i]; - if (mapper.IsMatch(typePair)) - { - nextMapper = mapper; - break; - } - } - var nextMapperExpression = nextMapper.MapExpression(configurationProvider, profileMap, memberMap, sourceExpression, destExpression, contextExpression); - var sourceType = TypeHelper.GetElementType(sourceExpression.Type); var destType = TypeHelper.GetElementType(destExpression.Type); var equivalencyExpression = this.GetEquivalentExpression(sourceType, destType); if (equivalencyExpression == null) { - return nextMapperExpression; + var typePair = new TypePair(sourceExpression.Type, destExpression.Type); + return _objectMapperCache.GetOrAdd(typePair, _ => + { + var mappers = new List(configurationProvider.GetMappers()); + for (var i = mappers.IndexOf(this) + 1; i < mappers.Count; i++) + { + var mapper = mappers[i]; + if (mapper.IsMatch(typePair)) + { + return mapper; + } + } + return _collectionMapper; + }) + .MapExpression(configurationProvider, profileMap, memberMap, sourceExpression, destExpression, contextExpression); } var method = _mapMethodInfo.MakeGenericMethod(sourceExpression.Type, sourceType, destExpression.Type, destType); var map = Call(null, method, sourceExpression, destExpression, contextExpression, Constant(equivalencyExpression)); var notNull = NotEqual(destExpression, Constant(null)); - return Condition(notNull, map, Convert(nextMapperExpression, destExpression.Type)); + var collectionMapperExpression = _collectionMapper.MapExpression(configurationProvider, profileMap, memberMap, sourceExpression, destExpression, contextExpression); + return Condition(notNull, map, Convert(collectionMapperExpression, destExpression.Type)); } } } From 7ecb8f081f61f0b2006b0b5df73b0c18d5b1fff2 Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Fri, 10 May 2019 08:05:15 +0200 Subject: [PATCH 19/32] Fix naming of GeneratePropertyMapsExpressionFeature --- .../Configuration/GeneratePropertyMapsExpressionFeature.cs | 2 +- .../EquivalencyExpression/EquivalentExpressions.cs | 6 +++--- ...opertyMapsnFeature.cs => GeneratePropertyMapsFeature.cs} | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename src/AutoMapper.Collection/Runtime/{GeneratePropertyMapsnFeature.cs => GeneratePropertyMapsFeature.cs} (100%) diff --git a/src/AutoMapper.Collection/Configuration/GeneratePropertyMapsExpressionFeature.cs b/src/AutoMapper.Collection/Configuration/GeneratePropertyMapsExpressionFeature.cs index 5c33650..dfa7ba0 100644 --- a/src/AutoMapper.Collection/Configuration/GeneratePropertyMapsExpressionFeature.cs +++ b/src/AutoMapper.Collection/Configuration/GeneratePropertyMapsExpressionFeature.cs @@ -7,7 +7,7 @@ namespace AutoMapper.Collection.Configuration { - public class GeneratePropertyMapsnFeature : IGlobalFeature + public class GeneratePropertyMapsExpressionFeature : IGlobalFeature { private readonly List, IGeneratePropertyMaps>> _generators = new List, IGeneratePropertyMaps>>(); diff --git a/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs b/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs index 3934e1d..97420ff 100644 --- a/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs +++ b/src/AutoMapper.Collection/EquivalencyExpression/EquivalentExpressions.cs @@ -11,7 +11,7 @@ public static class EquivalentExpressions { public static void AddCollectionMappers(this IMapperConfigurationExpression cfg) { - cfg.Features.Set(new GeneratePropertyMapsnFeature()); + cfg.Features.Set(new GeneratePropertyMapsExpressionFeature()); cfg.InsertBefore( new ObjectToEquivalencyExpressionByEquivalencyExistingMapper(), new EquivalentExpressionAddRemoveCollectionMapper()); @@ -89,14 +89,14 @@ public static IMappingExpression EqualityComparison(this IMapperConfigurationExpression cfg) where TGeneratePropertyMaps : IGeneratePropertyMaps { - (cfg.Features.Get() + (cfg.Features.Get() ?? throw new ArgumentException("Invoke the IMapperConfigurationExpression.AddCollectionMappers() before adding IGeneratePropertyMaps.")) .Add(serviceCtor => (IGeneratePropertyMaps)serviceCtor(typeof(TGeneratePropertyMaps))); } public static void SetGeneratePropertyMaps(this IMapperConfigurationExpression cfg, IGeneratePropertyMaps generatePropertyMaps) { - (cfg.Features.Get() + (cfg.Features.Get() ?? throw new ArgumentException("Invoke the IMapperConfigurationExpression.AddCollectionMappers() before adding IGeneratePropertyMaps.")) .Add(_ => generatePropertyMaps); } diff --git a/src/AutoMapper.Collection/Runtime/GeneratePropertyMapsnFeature.cs b/src/AutoMapper.Collection/Runtime/GeneratePropertyMapsFeature.cs similarity index 100% rename from src/AutoMapper.Collection/Runtime/GeneratePropertyMapsnFeature.cs rename to src/AutoMapper.Collection/Runtime/GeneratePropertyMapsFeature.cs From 2322da2878d60c53d520348518b7bfb2f8719023 Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Fri, 10 May 2019 08:07:40 +0200 Subject: [PATCH 20/32] Make methdo public --- .../Runtime/GeneratePropertyMapsFeature.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AutoMapper.Collection/Runtime/GeneratePropertyMapsFeature.cs b/src/AutoMapper.Collection/Runtime/GeneratePropertyMapsFeature.cs index 6e7b573..049619d 100644 --- a/src/AutoMapper.Collection/Runtime/GeneratePropertyMapsFeature.cs +++ b/src/AutoMapper.Collection/Runtime/GeneratePropertyMapsFeature.cs @@ -19,7 +19,7 @@ public GeneratePropertyMapsFeature(List generators) _generators = generators.AsReadOnly(); } - internal IEquivalentComparer Get(TypeMap typeMap) + public IEquivalentComparer Get(TypeMap typeMap) { return _comparers .GetOrAdd(typeMap.Types, _ => From 95a8969cb36fe7364584fddb2c34eb2d3523e293 Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Sat, 1 Jun 2019 16:41:06 +0200 Subject: [PATCH 21/32] Make the connection string unique per execution --- .../EntityFramworkTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkTests.cs b/src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkTests.cs index d680579..651e161 100644 --- a/src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkTests.cs +++ b/src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkTests.cs @@ -69,7 +69,7 @@ public void Should_Persist_To_Insert() public class DB : DbContext { public DB() - : base(new SqlCeConnection("Data Source=MyDatabase.sdf;Persist Security Info=False;"), contextOwnsConnection: true) + : base(new SqlCeConnection($"Data Source={Guid.NewGuid()}.sdf;Persist Security Info=False;"), contextOwnsConnection: true) { Things.RemoveRange(Things); SaveChanges(); From 8053a1378343d39d602a58e68a8d4806e8cb24fb Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Tue, 13 Aug 2019 11:03:44 +0200 Subject: [PATCH 22/32] Update to AM 9.0.0 --- .../Extensions.cs | 4 +++- .../Persistence.cs | 16 ++++------------ .../Persistence.cs | 16 ++++------------ .../PersistenceExtensions.cs | 4 +++- .../AutoMapper.Collection.csproj | 2 +- 5 files changed, 15 insertions(+), 27 deletions(-) diff --git a/src/AutoMapper.Collection.EntityFramework/Extensions.cs b/src/AutoMapper.Collection.EntityFramework/Extensions.cs index 4f632ae..b419e0b 100644 --- a/src/AutoMapper.Collection.EntityFramework/Extensions.cs +++ b/src/AutoMapper.Collection.EntityFramework/Extensions.cs @@ -9,16 +9,18 @@ namespace AutoMapper.EntityFramework public static class Extensions { /// + /// Obsolete: Use Persist(IMapper) instead. /// Create a Persistence object for the to have data persisted or removed from /// Uses static API's Mapper for finding TypeMap between classes /// /// Source table type to be updated /// DbSet to be updated /// Persistence object to Update or Remove data + [Obsolete("Use Persist(IMapper) instead.", true)] public static IPersistence Persist(this DbSet source) where TSource : class { - return new Persistence(source, Mapper.Instance); + throw new NotSupportedException(); } /// diff --git a/src/AutoMapper.Collection.EntityFramework/Persistence.cs b/src/AutoMapper.Collection.EntityFramework/Persistence.cs index 00c7fbd..7c845b4 100644 --- a/src/AutoMapper.Collection.EntityFramework/Persistence.cs +++ b/src/AutoMapper.Collection.EntityFramework/Persistence.cs @@ -14,7 +14,7 @@ public class Persistence : IPersistence public Persistence(DbSet sourceSet, IMapper mapper) { _sourceSet = sourceSet; - _mapper = mapper; + _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); } public void InsertOrUpdate(TFrom from) @@ -25,10 +25,7 @@ public void InsertOrUpdate(TFrom from) public void InsertOrUpdate(Type type, object from) { - var equivExpr = _mapper == null - ? Mapper.Map(from, type, typeof(Expression>)) as Expression> - : _mapper.Map(from, type, typeof(Expression>)) as Expression>; - if (equivExpr == null) + if (!(_mapper.Map(from, type, typeof(Expression>)) is Expression> equivExpr)) return; var to = _sourceSet.FirstOrDefault(equivExpr); @@ -38,18 +35,13 @@ public void InsertOrUpdate(Type type, object from) to = _sourceSet.Create(); _sourceSet.Add(to); } - if (_mapper == null) - Mapper.Map(from, to); - else - _mapper.Map(from,to); + _mapper.Map(from, to); } public void Remove(TFrom from) where TFrom : class { - var equivExpr = _mapper == null - ? Mapper.Map>>(from) - : _mapper.Map>>(from); + var equivExpr = _mapper.Map>>(from); if (equivExpr == null) return; var to = _sourceSet.FirstOrDefault(equivExpr); diff --git a/src/AutoMapper.Collection.LinqToSQL/Persistence.cs b/src/AutoMapper.Collection.LinqToSQL/Persistence.cs index a88e7ba..96665ce 100644 --- a/src/AutoMapper.Collection.LinqToSQL/Persistence.cs +++ b/src/AutoMapper.Collection.LinqToSQL/Persistence.cs @@ -13,7 +13,7 @@ public class Persistence : IPersistence public Persistence(Table sourceSet, IMapper mapper) { - _mapper = mapper; + _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); _sourceSet = sourceSet; } @@ -25,10 +25,7 @@ public void InsertOrUpdate(TFrom from) public void InsertOrUpdate(Type type, object from) { - var equivExpr = _mapper == null - ? Mapper.Map(from, type, typeof(Expression>)) as Expression> - : _mapper.Map(from, type, typeof(Expression>)) as Expression>; - if (equivExpr == null) + if (!(_mapper.Map(from, type, typeof(Expression>)) is Expression> equivExpr)) return; var to = _sourceSet.FirstOrDefault(equivExpr); @@ -38,18 +35,13 @@ public void InsertOrUpdate(Type type, object from) to = Activator.CreateInstance(); _sourceSet.InsertOnSubmit(to); } - if (_mapper == null) - Mapper.Map(from, to); - else - _mapper.Map(from, to); + _mapper.Map(from, to); } public void Remove(TFrom from) where TFrom : class { - var equivExpr = _mapper == null - ? Mapper.Map>>(from) - : _mapper.Map>>(from); + var equivExpr = _mapper.Map>>(from); if (equivExpr == null) return; var to = _sourceSet.FirstOrDefault(equivExpr); diff --git a/src/AutoMapper.Collection.LinqToSQL/PersistenceExtensions.cs b/src/AutoMapper.Collection.LinqToSQL/PersistenceExtensions.cs index d5ae665..d6a0813 100644 --- a/src/AutoMapper.Collection.LinqToSQL/PersistenceExtensions.cs +++ b/src/AutoMapper.Collection.LinqToSQL/PersistenceExtensions.cs @@ -9,16 +9,18 @@ namespace AutoMapper.Collection.LinqToSQL public static class PersistenceExtensions { /// + /// Obsolete: Use Persist(IMapper) instead. /// Create a Persistence object for the to have data persisted or removed from /// Uses static API's Mapper for finding TypeMap between classes /// /// Source table type to be updated /// Table to be updated /// Persistence object to Update or Remove data + [Obsolete("Use Persist(IMapper) instead.", true)] public static IPersistence Persist(this Table source) where TSource : class { - return new Persistence(source, Mapper.Instance); + throw new NotSupportedException(); } /// diff --git a/src/AutoMapper.Collection/AutoMapper.Collection.csproj b/src/AutoMapper.Collection/AutoMapper.Collection.csproj index cef1dc6..9cb043b 100644 --- a/src/AutoMapper.Collection/AutoMapper.Collection.csproj +++ b/src/AutoMapper.Collection/AutoMapper.Collection.csproj @@ -16,7 +16,7 @@ - + From 45b31c587e1ba9aac3f7346bd98a34992c9f9e54 Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Tue, 13 Aug 2019 11:06:21 +0200 Subject: [PATCH 23/32] Update test to not use the static Mapper.Instance and instead force to use the overload that takes the instance in the test. --- src/AutoMapper.Collection.Tests/ValueTypeTests.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/AutoMapper.Collection.Tests/ValueTypeTests.cs b/src/AutoMapper.Collection.Tests/ValueTypeTests.cs index dd8072e..cc298da 100644 --- a/src/AutoMapper.Collection.Tests/ValueTypeTests.cs +++ b/src/AutoMapper.Collection.Tests/ValueTypeTests.cs @@ -13,8 +13,7 @@ public class ValueTypeTests [Fact] public void MapValueTypes() { - Mapper.Reset(); - Mapper.Initialize(c => + var mapper = new Mapper(new MapperConfiguration(c => { c.AddCollectionMappers(); @@ -24,7 +23,7 @@ public void MapValueTypes() c.CreateMap() .EqualityComparison((src, dest) => dest.NationalityCountryId == src); - }); + })); var persons = new[] { @@ -37,7 +36,7 @@ public void MapValueTypes() var country = new Country { Persons = new List(persons) }; var countryDto = new CountryDto { Nationalities = new List { 104, 103, 105 } }; - Mapper.Map(countryDto, country); + mapper.Map(countryDto, country); Assert.NotStrictEqual(new[] { persons[3], persons[2], country.Persons.Last() }, country.Persons); Assert.Equal(0, country.Persons.Last().PersonId); From bc2301dfe096619c05aef7c85543acce8b597d56 Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Tue, 13 Aug 2019 11:07:01 +0200 Subject: [PATCH 24/32] Remove tests that depend on the removed feature "CreateMissingTypeMaps" (https://github.com/AutoMapper/AutoMapper/issues/3063) --- .../EntityFramworkUnmappedTypes.cs | 143 ------------------ .../NotExistingTests.cs | 109 ------------- 2 files changed, 252 deletions(-) delete mode 100644 src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkUnmappedTypes.cs delete mode 100644 src/AutoMapper.Collection.Tests/NotExistingTests.cs diff --git a/src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkUnmappedTypes.cs b/src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkUnmappedTypes.cs deleted file mode 100644 index 6aa209f..0000000 --- a/src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkUnmappedTypes.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; -using System.Data.Entity; -using System.Data.SqlServerCe; -using System.Linq; -using AutoMapper.EntityFramework; -using AutoMapper.EquivalencyExpression; -using FluentAssertions; -using Xunit; - -namespace AutoMapper.Collection.EntityFramework.Tests -{ - public class EntityFramworkUnmappedTypes - { - private void ConfigureMapper(IMapperConfigurationExpression cfg) - { - cfg.AddCollectionMappers(); - cfg.SetGeneratePropertyMaps>(); - } - - protected IMapper CreateMapper(Action cfg) - { - var map = new MapperConfiguration(cfg); - map.CompileMappings(); - - var mapper = map.CreateMapper(); - mapper.ConfigurationProvider.AssertConfigurationIsValid(); - return mapper; - } - - - [Fact] - public void Should_not_throw_exception_for_nonexisting_types() - { - var mapper = CreateMapper(ConfigureMapper); - - var originalModel = new System - { - Name = "My First System", - Contacts = new List - { - new Contact - { - Name = "John", - Emails = new List() - { - new Email - { - Address = "john@doe.com" - } - } - } - } - }; - - var originalEmail = originalModel.Contacts.Single().Emails.Single(); - - var assertModel = mapper.Map(originalModel); - assertModel.Name.Should().Be(originalModel.Name); - assertModel.Contacts.Single().Name.Should().Be(originalModel.Contacts.Single().Name); - assertModel.Contacts.Single().Emails.Single().Address.Should().Be(originalModel.Contacts.Single().Emails.Single().Address); - - assertModel.Contacts.Single().Emails.Add(new EmailViewModel { Address = "jane@doe.com" }); - - mapper.Map(assertModel, originalModel); - // This tests if equality was found and mapped to pre-existing object and not defaulting to AM and clearing and regenerating the list - originalModel.Contacts.Single().Emails.First().Should().Be(originalEmail); - } - - public class DB : DbContext - { - public DB() - : base(new SqlCeConnection("Data Source=MyDatabase.sdf;Persist Security Info=False;"), contextOwnsConnection: true) - { - } - - public DbSet Systems { get; set; } - public DbSet Contacts { get; set; } - public DbSet Emails { get; set; } - } - - - public class System - { - public Guid Id { get; set; } = Guid.NewGuid(); - public string Name { get; set; } - - public ICollection Contacts { get; set; } - } - - public class Contact - { - public Guid Id { get; set; } = Guid.NewGuid(); - public Guid SystemId { get; set; } - public string Name { get; set; } - - [ForeignKey("SystemId")] - public System System { get; set; } - - public ICollection Emails { get; set; } - } - - public class Email - { - public Guid Id { get; set; } = Guid.NewGuid(); - - public Guid ContactId { get; set; } - public string Address { get; set; } - - [ForeignKey("ContactId")] - public Contact Contact { get; set; } - } - - public class SystemViewModel - { - public Guid Id { get; set; } - public string Name { get; set; } - - public ICollection Contacts { get; set; } - } - - public class ContactViewModel - { - public Guid Id { get; set; } - public Guid SystemId { get; set; } - public string Name { get; set; } - - public SystemViewModel System { get; set; } - - public ICollection Emails { get; set; } - } - - public class EmailViewModel - { - public Guid Id { get; set; } - public Guid ContactId { get; set; } - public string Address { get; set; } - - public ContactViewModel Contact { get; set; } - } - } -} diff --git a/src/AutoMapper.Collection.Tests/NotExistingTests.cs b/src/AutoMapper.Collection.Tests/NotExistingTests.cs deleted file mode 100644 index d56589e..0000000 --- a/src/AutoMapper.Collection.Tests/NotExistingTests.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AutoMapper.EquivalencyExpression; -using FluentAssertions; -using Xunit; - -namespace AutoMapper.Collection -{ - public class NotExistingTests - { - [Fact] - public void Should_not_throw_exception_for_nonexisting_types() - { - var configuration = new MapperConfiguration(x => - { - x.AddCollectionMappers(); - }); - IMapper mapper = new Mapper(configuration); - - var originalModel = new System - { - Name = "My First System", - Contacts = new List - { - new Contact - { - Name = "John", - Emails = new List() - { - new Email - { - Address = "john@doe.com" - } - } - } - } - }; - - var originalEmail = originalModel.Contacts.Single().Emails.Single(); - - var assertModel = mapper.Map(originalModel); - assertModel.Name.Should().Be(originalModel.Name); - assertModel.Contacts.Single().Name.Should().Be(originalModel.Contacts.Single().Name); - assertModel.Contacts.Single().Emails.Single().Address.Should().Be(originalModel.Contacts.Single().Emails.Single().Address); - - assertModel.Contacts.Single().Emails.Add(new EmailViewModel { Address = "jane@doe.com" }); - - mapper.Map(assertModel, originalModel); - } - - public class System - { - public Guid Id { get; set; } = Guid.NewGuid(); - public string Name { get; set; } - - public ICollection Contacts { get; set; } - } - - public class Contact - { - public Guid Id { get; set; } = Guid.NewGuid(); - public Guid SystemId { get; set; } - public string Name { get; set; } - - public System System { get; set; } - - public ICollection Emails { get; set; } - } - - public class Email - { - public Guid Id { get; set; } = Guid.NewGuid(); - - public Guid ContactId { get; set; } - public string Address { get; set; } - - public Contact Contact { get; set; } - } - - public class SystemViewModel - { - public Guid Id { get; set; } - public string Name { get; set; } - - public ICollection Contacts { get; set; } - } - - public class ContactViewModel - { - public Guid Id { get; set; } - public Guid SystemId { get; set; } - public string Name { get; set; } - - public SystemViewModel System { get; set; } - - public ICollection Emails { get; set; } - } - - public class EmailViewModel - { - public Guid Id { get; set; } - public Guid ContactId { get; set; } - public string Address { get; set; } - - public ContactViewModel Contact { get; set; } - } - } -} From 350b579e122e517c39f6d838f020acf892c63edf Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Tue, 13 Aug 2019 11:08:28 +0200 Subject: [PATCH 25/32] Bump version to 6.0.0 to reflect the breaking change with update AutoMapper --- version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.props b/version.props index d6de0cd..adfe983 100644 --- a/version.props +++ b/version.props @@ -1,5 +1,5 @@ - 5.0.1 + 6.0.0 From 87cff980922e53e870000b8492ef82d2fece0cbc Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Tue, 13 Aug 2019 11:29:29 +0200 Subject: [PATCH 26/32] /Nologo need to be last parameter, otherwise build will fail in some dotnet versions --- build.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.ps1 b/build.ps1 index 49b7a97..c03d83b 100644 --- a/build.ps1 +++ b/build.ps1 @@ -45,9 +45,9 @@ task compile -depends clean { # restore all project references (creating project.assets.json for each project) exec { dotnet restore $base_dir\AutoMapper.Collection.sln /nologo } - exec { dotnet build $base_dir\AutoMapper.Collection.sln -c $config $buildParam /nologo --no-restore } + exec { dotnet build $base_dir\AutoMapper.Collection.sln -c $config $buildParam --no-restore /nologo } - exec { dotnet pack $base_dir\AutoMapper.Collection.sln -c $config --include-symbols --no-build --no-restore --output $artifacts_dir $packageParam /nologo} + exec { dotnet pack $base_dir\AutoMapper.Collection.sln -c $config --include-symbols --no-build --no-restore --output $artifacts_dir $packageParam /nologo } } task test { From 4959f0304d60c06b6757183bf3ccd0967b45e870 Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Tue, 13 Aug 2019 13:01:56 +0200 Subject: [PATCH 27/32] Use Effort.EF6 instead of SqlServerCompact (Sql server compact is not compatible with .net core). --- Directory.Build.props | 5 ++-- .../App.config | 24 ------------------- ...er.Collection.EntityFramework.Tests.csproj | 8 +------ .../EntityFramworkTests.cs | 3 +-- ...toMapper.Collection.EntityFramework.csproj | 7 +----- 5 files changed, 6 insertions(+), 41 deletions(-) delete mode 100644 src/AutoMapper.Collection.EntityFramework.Tests/App.config diff --git a/Directory.Build.props b/Directory.Build.props index 7b2a983..fc40855 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,7 +5,8 @@ 6.1.3 4.15.0 15.5.0 - 2.3.1 - 4.0.8876.1 + 2.4.1 + 2.2.1 + \ No newline at end of file diff --git a/src/AutoMapper.Collection.EntityFramework.Tests/App.config b/src/AutoMapper.Collection.EntityFramework.Tests/App.config deleted file mode 100644 index 59e34a0..0000000 --- a/src/AutoMapper.Collection.EntityFramework.Tests/App.config +++ /dev/null @@ -1,24 +0,0 @@ - - - - -
- - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/AutoMapper.Collection.EntityFramework.Tests/AutoMapper.Collection.EntityFramework.Tests.csproj b/src/AutoMapper.Collection.EntityFramework.Tests/AutoMapper.Collection.EntityFramework.Tests.csproj index b71d942..af0e876 100644 --- a/src/AutoMapper.Collection.EntityFramework.Tests/AutoMapper.Collection.EntityFramework.Tests.csproj +++ b/src/AutoMapper.Collection.EntityFramework.Tests/AutoMapper.Collection.EntityFramework.Tests.csproj @@ -10,18 +10,12 @@ - - - - - - - + diff --git a/src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkTests.cs b/src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkTests.cs index 651e161..828f11b 100644 --- a/src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkTests.cs +++ b/src/AutoMapper.Collection.EntityFramework.Tests/EntityFramworkTests.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Data.Entity; -using System.Data.SqlServerCe; using System.Linq; using AutoMapper.EntityFramework; using AutoMapper.EquivalencyExpression; @@ -69,7 +68,7 @@ public void Should_Persist_To_Insert() public class DB : DbContext { public DB() - : base(new SqlCeConnection($"Data Source={Guid.NewGuid()}.sdf;Persist Security Info=False;"), contextOwnsConnection: true) + : base(Effort.DbConnectionFactory.CreateTransient(), contextOwnsConnection: true) { Things.RemoveRange(Things); SaveChanges(); diff --git a/src/AutoMapper.Collection.EntityFramework/AutoMapper.Collection.EntityFramework.csproj b/src/AutoMapper.Collection.EntityFramework/AutoMapper.Collection.EntityFramework.csproj index 6a224af..1305068 100644 --- a/src/AutoMapper.Collection.EntityFramework/AutoMapper.Collection.EntityFramework.csproj +++ b/src/AutoMapper.Collection.EntityFramework/AutoMapper.Collection.EntityFramework.csproj @@ -3,7 +3,7 @@ Collection updating support for EntityFramework with AutoMapper. Extends DBSet<T> with Persist<TDto>().InsertUpdate(dto) and Persist<TDto>().Delete(dto). Will find the matching object and will Insert/Update/Delete. Tyler Carlson - net461 + net461 AutoMapper.Collection.EntityFramework AutoMapper.Collection.EntityFramework https://s3.amazonaws.com/automapper/icon.png @@ -23,9 +23,4 @@ - - - - - From 759c055cab0acb28ad59f744c60cbc0e45e47e53 Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Tue, 13 Aug 2019 13:04:29 +0200 Subject: [PATCH 28/32] Add support for EF 6.3 preview --- Directory.Build.props | 9 ++++++++- .../AutoMapper.Collection.EntityFramework.Tests.csproj | 2 +- .../AutoMapper.Collection.EntityFramework.csproj | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index fc40855..b2c62df 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,11 +2,18 @@ - 6.1.3 4.15.0 15.5.0 2.4.1 2.2.1 + + 6.1.3 + + + + 6.3.0-preview7-* + + \ No newline at end of file diff --git a/src/AutoMapper.Collection.EntityFramework.Tests/AutoMapper.Collection.EntityFramework.Tests.csproj b/src/AutoMapper.Collection.EntityFramework.Tests/AutoMapper.Collection.EntityFramework.Tests.csproj index af0e876..9339fc3 100644 --- a/src/AutoMapper.Collection.EntityFramework.Tests/AutoMapper.Collection.EntityFramework.Tests.csproj +++ b/src/AutoMapper.Collection.EntityFramework.Tests/AutoMapper.Collection.EntityFramework.Tests.csproj @@ -1,7 +1,7 @@  - net461 + net461;netcoreapp3.0 AutoMapper.Collection.EntityFramework.Tests false diff --git a/src/AutoMapper.Collection.EntityFramework/AutoMapper.Collection.EntityFramework.csproj b/src/AutoMapper.Collection.EntityFramework/AutoMapper.Collection.EntityFramework.csproj index 1305068..6522955 100644 --- a/src/AutoMapper.Collection.EntityFramework/AutoMapper.Collection.EntityFramework.csproj +++ b/src/AutoMapper.Collection.EntityFramework/AutoMapper.Collection.EntityFramework.csproj @@ -3,7 +3,7 @@ Collection updating support for EntityFramework with AutoMapper. Extends DBSet<T> with Persist<TDto>().InsertUpdate(dto) and Persist<TDto>().Delete(dto). Will find the matching object and will Insert/Update/Delete. Tyler Carlson - net461 + net461;netstandard2.1 AutoMapper.Collection.EntityFramework AutoMapper.Collection.EntityFramework https://s3.amazonaws.com/automapper/icon.png From 1075cd17f55901985ee4c27b796e7f2db5e54166 Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Tue, 13 Aug 2019 13:38:33 +0200 Subject: [PATCH 29/32] Use latest dotnet core --- appveyor.yml | 3 +++ build.ps1 | 39 ++++++++++++++++++++------------------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index cd74da1..c70c1ce 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,6 +8,9 @@ branches: image: Visual Studio 2017 nuget: disable_publish_on_pr: true +environment: + DOTNET_CLI_VERSION: 3.0.100-preview6-012264 + DOTNET_CLI_TELEMETRY_OPTOUT: true build_script: - cmd: .\build.cmd test: off diff --git a/build.ps1 b/build.ps1 index c03d83b..1f95a67 100644 --- a/build.ps1 +++ b/build.ps1 @@ -38,9 +38,9 @@ task compile -depends clean { $buildParam = @{ $true = ""; $false = "--version-suffix=$buildSuffix"}[$tag -ne $NULL -and $revision -ne "local"] $packageParam = @{ $true = ""; $false = "--version-suffix=$suffix"}[$tag -ne $NULL -and $revision -ne "local"] - echo "build: Tag is $tag" - echo "build: Package version suffix is $suffix" - echo "build: Build version suffix is $buildSuffix" + Write-Output "build: Tag is $tag" + Write-Output "build: Package version suffix is $suffix" + Write-Output "build: Build version suffix is $buildSuffix" # restore all project references (creating project.assets.json for each project) exec { dotnet restore $base_dir\AutoMapper.Collection.sln /nologo } @@ -60,29 +60,30 @@ task test { function Install-Dotnet { - $dotnetcli = where-is('dotnet') + $dotnetCli = (where-is "dotnet" | Select-Object -First 1) + $install = ($null -eq $dotnetCli -or ($null -ne $env:DOTNET_CLI_VERSION -and $null -eq (&"$dotnetCli" --info | Where-Object { $_ -like " $env:DOTNET_CLI_VERSION*" }))) - if($dotnetcli -eq $null) - { + if ($install) + { $dotnetPath = "$pwd\.dotnet" - $dotnetCliVersion = if ($env:DOTNET_CLI_VERSION -eq $null) { 'Latest' } else { $env:DOTNET_CLI_VERSION } - $dotnetInstallScriptUrl = 'https://raw.githubusercontent.com/dotnet/cli/rel/1.0.0/scripts/obtain/install.ps1' - $dotnetInstallScriptPath = '.\scripts\obtain\install.ps1' + $dotnetCliVersion = if ($null -eq $env:DOTNET_CLI_VERSION) { 'Latest' } else { $env:DOTNET_CLI_VERSION } + $dotnetInstallScriptUrl = 'https://raw.githubusercontent.com/dotnet/cli/v2.1.4/scripts/obtain/dotnet-install.ps1' + $dotnetInstallScriptPath = '.\scripts\obtain\dotnet-install.ps1' - md -Force ".\scripts\obtain\" | Out-Null - curl $dotnetInstallScriptUrl -OutFile $dotnetInstallScriptPath - & .\scripts\obtain\install.ps1 -Channel "preview" -version $dotnetCliVersion -InstallDir $dotnetPath -NoPath + mkdir -Force ".\scripts\obtain\" | Out-Null + Invoke-WebRequest $dotnetInstallScriptUrl -OutFile $dotnetInstallScriptPath + & .\scripts\obtain\dotnet-install.ps1 -Channel "preview" -version $dotnetCliVersion -InstallDir $dotnetPath -NoPath $env:Path = "$dotnetPath;$env:Path" } } function where-is($command) { - (ls env:\path).Value.split(';') | ` - where { $_ } | ` - %{ [System.Environment]::ExpandEnvironmentVariables($_) } | ` - where { test-path $_ } |` - %{ ls "$_\*" -include *.bat,*.exe,*cmd } | ` - %{ $file = $_.Name; ` + (Get-ChildItem env:\path).Value.split(';') | ` + Where-Object { $_ } | ` + ForEach-Object{ [System.Environment]::ExpandEnvironmentVariables($_) } | ` + Where-Object { test-path $_ } |` + ForEach-Object{ Get-ChildItem "$_\*" -include *.bat,*.exe,*cmd } | ` + ForEach-Object{ $file = $_.Name; ` if($file -and ($file -eq $command -or ` $file -eq ($command + '.exe') -or ` $file -eq ($command + '.bat') -or ` @@ -91,5 +92,5 @@ function where-is($command) { $_.FullName ` } ` } | ` - select -unique + Select-Object -unique } \ No newline at end of file From fa47a49281022ee70d97c2b8bb7412ead9bef464 Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Tue, 22 Oct 2019 14:22:14 +0200 Subject: [PATCH 30/32] Use released EF 6.3 version --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index b2c62df..60f31fe 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -13,7 +13,7 @@ - 6.3.0-preview7-* + 6.3.0 \ No newline at end of file From e16a86bf8eb28ee8770002f249df5b0ea5911314 Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Tue, 22 Oct 2019 14:25:10 +0200 Subject: [PATCH 31/32] Update to non prerelease of .net core 3 --- AutoMapper.Collection.sln | 9 +++++---- appveyor.yml | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/AutoMapper.Collection.sln b/AutoMapper.Collection.sln index d106b91..f96c70f 100644 --- a/AutoMapper.Collection.sln +++ b/AutoMapper.Collection.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2010 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29411.108 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{19AAEE83-5EEC-4EAA-9CF7-16F8ED58B50E}" ProjectSection(SolutionItems) = preProject @@ -10,6 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{19AAEE EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{578F2483-CF08-409D-A316-31BCB7C5D9D0}" ProjectSection(SolutionItems) = preProject + appveyor.yml = appveyor.yml Directory.Build.props = Directory.Build.props global.json = global.json README.md = README.md @@ -22,9 +23,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoMapper.Collection.Entit EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoMapper.Collection.LinqToSQL", "src\AutoMapper.Collection.LinqToSQL\AutoMapper.Collection.LinqToSQL.csproj", "{A0A023B6-D02A-4CD3-9B3D-3B3022DB001A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoMapper.Collection.Tests", "src\AutoMapper.Collection.Tests\AutoMapper.Collection.Tests.csproj", "{2D3D34AD-6A0A-4382-9A2F-894F52D184A7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoMapper.Collection.Tests", "src\AutoMapper.Collection.Tests\AutoMapper.Collection.Tests.csproj", "{2D3D34AD-6A0A-4382-9A2F-894F52D184A7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoMapper.Collection.EntityFramework.Tests", "src\AutoMapper.Collection.EntityFramework.Tests\AutoMapper.Collection.EntityFramework.Tests.csproj", "{BDE127AB-AC3F-44DF-BC33-210DAFD12E15}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoMapper.Collection.EntityFramework.Tests", "src\AutoMapper.Collection.EntityFramework.Tests\AutoMapper.Collection.EntityFramework.Tests.csproj", "{BDE127AB-AC3F-44DF-BC33-210DAFD12E15}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/appveyor.yml b/appveyor.yml index c70c1ce..917e6b6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,7 +9,7 @@ image: Visual Studio 2017 nuget: disable_publish_on_pr: true environment: - DOTNET_CLI_VERSION: 3.0.100-preview6-012264 + DOTNET_CLI_VERSION: 3.0.100 DOTNET_CLI_TELEMETRY_OPTOUT: true build_script: - cmd: .\build.cmd From 5a3c9e95b4db4e7cd5f1c0c5c8196440e5cd8ed0 Mon Sep 17 00:00:00 2001 From: Patric Forsgard Date: Tue, 22 Oct 2019 14:26:12 +0200 Subject: [PATCH 32/32] Use Visual Studio Community 2019 version 16.3.4 image --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 917e6b6..6033d35 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,7 +5,7 @@ branches: only: - master - development -image: Visual Studio 2017 +image: Visual Studio 2019 nuget: disable_publish_on_pr: true environment: