Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="InheritanceWithCollectionTests.cs" />
<Compile Include="MapCollectionWithEqualityTests.cs" />
<Compile Include="OptionsTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
Expand Down
206 changes: 206 additions & 0 deletions src/AutoMapper.Collection.Tests/InheritanceWithCollectionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
using AutoMapper.EquivalencyExpression;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;

namespace AutoMapper.Collection
{
public class InheritanceWithCollectionTests
{
private IMapper CreateMapper()
{
void Create(IMapperConfigurationExpression cfg)
{
cfg.ShouldMapProperty = propertyInfo => propertyInfo.GetMethod.IsPublic || propertyInfo.GetMethod.IsAssembly || propertyInfo.GetMethod.IsFamily || propertyInfo.GetMethod.IsPrivate;
cfg.AddCollectionMappers();

//DOMAIN --> EF
cfg.CreateMap<RootDomain, RootEf>()
.ForMember(rootEf => rootEf.Orders, opt => opt.ResolveUsing<MergeDomainOrdersToEfOrdersValueResolver>())
;

//collection type
cfg.CreateMap<OrderDomain, OrderEf>()
.EqualityComparison((oo, dto) => BaseEquals(oo, dto))
;

cfg.CreateMap<OnlineOrderDomain, OnlineOrderEf>()
.EqualityComparison((ood, ooe) => DerivedEquals(ood, ooe))
.IncludeBase<OrderDomain, OrderEf>()
;

cfg.CreateMap<MailOrderDomain, MailOrderEf>()
.IncludeBase<OrderDomain, OrderEf>()
;

//EF --> DOMAIN
cfg.CreateMap<RootEf, RootDomain>()
.ForMember(rootDomain => rootDomain.OnlineOrders, opt => opt.MapFrom(rootEf => rootEf.Orders.Where(orderEf => orderEf.GetType() == typeof(OnlineOrderEf))))
.ForMember(rootDomain => rootDomain.MailOrders, opt => opt.MapFrom(rootEf => rootEf.Orders.Where(orderEf => orderEf.GetType() == typeof(MailOrderEf))))
;

cfg.CreateMap<OrderEf, OrderDomain>()
;

cfg.CreateMap<OnlineOrderEf, OnlineOrderDomain>()
.IncludeBase<OrderEf, OrderDomain>()
;

cfg.CreateMap<MailOrderEf, MailOrderDomain>()
.IncludeBase<OrderEf, OrderDomain>()
;
}

var map = new MapperConfiguration(Create);
map.AssertConfigurationIsValid();
map.CompileMappings();

return map.CreateMapper();
}

public void Map_Should_ReturnOnlineOrderEf_When_ListIsOfTypeOrderEf()
{
var mapper = CreateMapper();

//arrange
var orderDomain = new OnlineOrderDomain { Id = "Id", Key = "Key" };
var rootDomain = new RootDomain { OnlineOrders = { orderDomain } };

//act
RootEf mappedRootEf = mapper.Map<RootDomain, RootEf>(rootDomain);

//assert
OrderEf orderEf = mappedRootEf.Orders[0];

orderEf.Should().BeOfType<OnlineOrderEf>();
orderEf.Id.ShouldBeEquivalentTo(orderDomain.Id);

var onlineOrderEf = (OnlineOrderEf)orderEf;
onlineOrderEf.Key.ShouldBeEquivalentTo(orderDomain.Key);

// ------------------------------------------------------------- //

//arrange again
mappedRootEf.Orders.Add(new OnlineOrderEf { Id = "NewId" });

mapper.Map(mappedRootEf, rootDomain);

//assert again
rootDomain.OnlineOrders.Count.ShouldBeEquivalentTo(2);
rootDomain.OnlineOrders.Last().Should().BeOfType<OnlineOrderDomain>();

//Assert.AreSame(rootDomain.OnlineOrders.First(), orderDomain); that doesn't matter when we map from EF to Domain
}

public void Map_FromEfToDomain_And_AddAnOnlineOrderInTheDomainObject_And_ThenMapBackToEf_Should_UseTheSameReferenceInTheEfCollection()
{
var mapper = CreateMapper();

//arrange
var onlineOrderEf = new OnlineOrderEf { Id = "Id", Key = "Key" };
var mailOrderEf = new MailOrderEf { Id = "MailOrderId" };
var rootEf = new RootEf { Orders = { onlineOrderEf, mailOrderEf } };

//act
RootDomain mappedRootDomain = mapper.Map<RootEf, RootDomain>(rootEf);

//assert
OnlineOrderDomain onlineOrderDomain = mappedRootDomain.OnlineOrders[0];

onlineOrderDomain.Should().BeOfType<OnlineOrderDomain>();
onlineOrderEf.Id.ShouldBeEquivalentTo(onlineOrderEf.Id);

// IMPORTANT ASSERT ------------------------------------------------------------- IMPORTANT ASSERT //

//arrange again
mappedRootDomain.OnlineOrders.Add(new OnlineOrderDomain { Id = "NewOnlineOrderId", Key = "NewKey" });
mappedRootDomain.MailOrders.Add(new MailOrderDomain { Id = "NewMailOrderId", });
onlineOrderDomain.Id = "Hi";

//act again
mapper.Map(mappedRootDomain, rootEf);

//assert again
OrderEf existingMailOrderEf = rootEf.Orders.Single(orderEf => orderEf.Id == mailOrderEf.Id);
OrderEf existingOnlineOrderEf = rootEf.Orders.Single(orderEf => orderEf.Id == onlineOrderEf.Id);

OrderEf newOnlineOrderEf = rootEf.Orders.Single(orderEf => orderEf.Id == "NewOnlineOrderId");
OrderEf newMailOrderEf = rootEf.Orders.Single(orderEf => orderEf.Id == "NewMailOrderId");

rootEf.Orders.Count.ShouldBeEquivalentTo(4);
onlineOrderEf.Should().BeSameAs(existingOnlineOrderEf);
mailOrderEf.Should().BeSameAs(existingMailOrderEf);

newOnlineOrderEf.Should().BeOfType<OnlineOrderEf>();
newMailOrderEf.Should().BeOfType<MailOrderEf>();
}

private static bool BaseEquals(OrderDomain oo, OrderEf dto)
{
return oo.Id == dto.Id;
}

private static bool DerivedEquals(OnlineOrderDomain ood, OnlineOrderEf ooe)
{
return ood.Key == ooe.Key;
}

public class MailOrderDomain : OrderDomain
{
}

public class MailOrderEf : OrderEf
{
}

public class OnlineOrderDomain : OrderDomain
{
public string Key { get; set; }
}

public class OnlineOrderEf : OrderEf
{
public string Key { get; set; }
}

public abstract class OrderDomain
{
public string Id { get; set; }
}

public abstract class OrderEf
{
public string Id { get; set; }
}

public class RootDomain
{
public List<OnlineOrderDomain> OnlineOrders { get; set; } = new List<OnlineOrderDomain>();
public List<MailOrderDomain> MailOrders { get; set; } = new List<MailOrderDomain>();
}

public class RootEf
{
public List<OrderEf> Orders { get; set; } = new List<OrderEf>();
}

public class MergeDomainOrdersToEfOrdersValueResolver : IValueResolver<RootDomain, RootEf, List<OrderEf>>
{
public List<OrderEf> Resolve(RootDomain source, RootEf destination, List<OrderEf> destMember, ResolutionContext context)
{
var mappedOnlineOrders = new List<OrderEf>(destination.Orders);
var mappedMailOrders = new List<OrderEf>(destination.Orders);

context.Mapper.Map(source.OnlineOrders, mappedOnlineOrders, context);
context.Mapper.Map(source.MailOrders, mappedMailOrders, context);

var efOrders = mappedOnlineOrders.Union(mappedMailOrders).ToList();

return efOrders;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public void Should_Be_Fast_With_Large_Lists_SubObject()
Mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First());
}

public void Should_Be_Fast_With_Large_Lists_SubObject_WrongCollectionType_Should_Throw()
public void Should_Be_Fast_With_Large_Lists_SubObject_switch_left_and_right_expression()
{
Mapper.Reset();
Mapper.Initialize(x =>
Expand All @@ -166,8 +166,7 @@ public void Should_Be_Fast_With_Large_Lists_SubObject_WrongCollectionType_Should

var items = new object[100000].Select((_, i) => new Thing { ID = i }).ToList();

Action a = () => Mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First());
a.ShouldThrow<ArgumentException>().Where(x => x.Message.Contains(typeof(ThingSubDto).FullName) && x.Message.Contains(typeof(ThingDto).FullName));
Mapper.Map(dtos, items.ToList()).Should().HaveElementAt(0, items.First());
}

public void Should_Work_With_Conditionals()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public int GetHashCode(object obj)
{
throw new Exception("How'd you get here");
}

public bool IsEquivalent(object source, object destination)
{
return false;
}
}

internal class EquivalentExpression<TSource,TDestination> : IEquivalentComparer<TSource, TDestination>
Expand All @@ -43,9 +48,22 @@ public EquivalentExpression(Expression<Func<TSource,TDestination,bool>> equivale
_destinationHashCodeFunc = members.Item2.GetHashCodeExpression<TDestination>(destinationParameter).Compile();
}

public bool IsEquivalent(TSource source, TDestination destination)
public bool IsEquivalent(object source, object destination)
{
return _equivalentFunc(source, destination);
var src = source as TSource;
var dest = destination as TDestination;

if (src == null && dest == null)
{
return true;
}

if (src == null || dest == null)
{
return false;
}

return _equivalentFunc(src, dest);
}

public Expression<Func<TDestination, bool>> ToSingleSourceExpression(TSource source)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,37 @@ private static void InsertBefore<TObjectMapper>(this IMapperConfigurationExpress
internal static IEquivalentComparer GetEquivalentExpression(this IConfigurationObjectMapper mapper, Type sourceType, Type destinationType)
{
var typeMap = mapper.ConfigurationProvider.ResolveTypeMap(sourceType, destinationType);
return typeMap == null ? null : GetEquivalentExpression(mapper.ConfigurationProvider, typeMap);
if (typeMap == null)
{
return null;
}

var comparer = GetEquivalentExpression(mapper.ConfigurationProvider, typeMap);
if (comparer == null)
{
foreach (var item in typeMap.IncludedBaseTypes)
{
var baseTypeMap = mapper.ConfigurationProvider.ResolveTypeMap(item.SourceType, item.DestinationType);
if (baseTypeMap == null)
{
continue;
}

comparer = GetEquivalentExpression(mapper.ConfigurationProvider, baseTypeMap);
if (comparer != null)
{
break;
}
}
}
return comparer;
}

internal static IEquivalentComparer GetEquivalentExpression(IConfigurationProvider configurationProvider, TypeMap typeMap)
{
return EquivalentExpressionDictionary[configurationProvider].GetOrAdd(typeMap.Types,
tp =>
GeneratePropertyMapsDictionary[configurationProvider].Select(_ =>_.GeneratePropertyMaps(typeMap).CreateEquivalentExpression()).FirstOrDefault(_ => _ != null));
GeneratePropertyMapsDictionary[configurationProvider].Select(_ => _.GeneratePropertyMaps(typeMap).CreateEquivalentExpression()).FirstOrDefault(_ => _ != null));
}

/// <summary>
Expand All @@ -70,8 +93,8 @@ internal static IEquivalentComparer GetEquivalentExpression(IConfigurationProvid
/// <param name="mappingExpression">Base Mapping Expression</param>
/// <param name="EquivalentExpression">Equivalent Expression between <typeparamref name="TSource"/> and <typeparamref name="TDestination"/></param>
/// <returns></returns>
public static IMappingExpression<TSource, TDestination> EqualityComparison<TSource, TDestination>(this IMappingExpression<TSource, TDestination> mappingExpression, Expression<Func<TSource, TDestination, bool>> EquivalentExpression)
where TSource : class
public static IMappingExpression<TSource, TDestination> EqualityComparison<TSource, TDestination>(this IMappingExpression<TSource, TDestination> mappingExpression, Expression<Func<TSource, TDestination, bool>> EquivalentExpression)
where TSource : class
where TDestination : class
{
var typePair = new TypePair(typeof(TSource), typeof(TDestination));
Expand All @@ -91,7 +114,7 @@ public static void SetGeneratePropertyMaps(this IMapperConfigurationExpression c
{
_generatePropertyMapsCache.Add(generatePropertyMaps);
}

private static IEquivalentComparer CreateEquivalentExpression(this IEnumerable<PropertyMap> propertyMaps)
{
if (!propertyMaps.Any() || propertyMaps.Any(pm => pm.DestinationProperty.GetMemberType() != pm.SourceMember.GetMemberType()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ namespace AutoMapper.EquivalencyExpression
public interface IEquivalentComparer
{
int GetHashCode(object obj);
bool IsEquivalent(object source, object destination);
}

public interface IEquivalentComparer<TSource, TDestination> : IEquivalentComparer
{
bool IsEquivalent(TSource source, TDestination destination);
Expression<Func<TDestination, bool>> ToSingleSourceExpression(TSource destination);
}
}
Loading