Skip to content

Commit

Permalink
Merge pull request #61 from BlaiseD/master
Browse files Browse the repository at this point in the history
Fix for Issue 33: Type Binary and Conversion expressions are not being mapped as expected.
  • Loading branch information
BlaiseD authored Mar 7, 2020
2 parents 58f647c + 6600b12 commit bebad43
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 4 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<Authors>Jimmy Bogard</Authors>
<LangVersion>latest</LangVersion>
<VersionPrefix>3.1.0-preview01</VersionPrefix>
<VersionPrefix>3.1.0-preview02</VersionPrefix>
<WarningsAsErrors>true</WarningsAsErrors>
<NoWarn>$(NoWarn);1701;1702;1591</NoWarn>
</PropertyGroup>
Expand Down
18 changes: 18 additions & 0 deletions src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -272,12 +272,30 @@ public static Dictionary<Type, Type> AddTypeMapping(this Dictionary<Type, Type>
{
typeMappings.AddUnderlyingTypes(configurationProvider, sourceType, destType);
typeMappings.FindChildPropertyTypeMaps(configurationProvider, sourceType, destType);
typeMappings.AddIncludedTypeMaps(configurationProvider, sourceType, destType);
}
}

return typeMappings;
}

private static void AddIncludedTypeMaps(this Dictionary<Type, Type> typeMappings, IConfigurationProvider configurationProvider, Type source, Type dest)
{
AddTypeMaps(configurationProvider.ResolveTypeMap(source, dest));

void AddTypeMaps(TypeMap typeMap)
{
if (typeMap == null)
return;

foreach (var includedBase in typeMap.IncludedBaseTypes)
typeMappings.AddTypeMapping(configurationProvider, includedBase.SourceType, includedBase.DestinationType);

foreach (var includedDerived in typeMap.IncludedDerivedTypes)
typeMappings.AddTypeMapping(configurationProvider, includedDerived.SourceType, includedDerived.DestinationType);
}
}

/// <summary>
/// Replaces a type in the source expression with the corresponding destination type.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,28 @@ Expression DoVisitConditional(Expression test, Expression ifTrue, Expression ifF
}
}

protected override Expression VisitTypeBinary(TypeBinaryExpression node)
{
if (this.TypeMappings.TryGetValue(node.TypeOperand, out Type mappedType))
return MapTypeBinary(this.Visit(node.Expression));

return base.VisitTypeBinary(node);

Expression MapTypeBinary(Expression mapped)
{
if (mapped == node.Expression)
return base.VisitTypeBinary(node);

switch (node.NodeType)
{
case ExpressionType.TypeIs:
return Expression.TypeIs(mapped, mappedType);
}

return base.VisitTypeBinary(node);
}
}

protected override Expression VisitUnary(UnaryExpression node)
{
return DoVisitUnary(Visit(node.Operand));
Expand All @@ -214,10 +236,10 @@ Expression DoVisitUnary(Expression updated)
if (this.TypeMappings.TryGetValue(node.Type, out Type mappedType))
return Expression.MakeUnary
(
node.NodeType,
node.NodeType,
updated != node.Operand
? updated
: node.Operand,
: node.Operand,
mappedType
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using Shouldly;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Xunit;

namespace AutoMapper.Extensions.ExpressionMapping.UnitTests
{
public class ExpressionMappingPropertyFromDerviedType : AutoMapperSpecBase
{
private List<BaseEntity> _source;
private IQueryable<BaseEntity> entityQuery;

public class BaseDTO
{
public Guid Id { get; set; }
}

public class BaseEntity
{
public Guid Id { get; set; }
}

public class DTO : BaseDTO
{
public string Name { get; set; }
public string Description { get; set; }
}

public class Entity : BaseEntity
{
public string Name { get; set; }
}

protected override MapperConfiguration Configuration
{
get
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddExpressionMapping();

cfg.CreateMap<BaseEntity, BaseDTO>();
cfg.CreateMap<BaseDTO, BaseEntity>();

cfg.CreateMap<Entity, DTO>()
.ForMember(dest => dest.Description, opts => opts.MapFrom(src => string.Concat(src.Id.ToString(), " - ", src.Name)))
.IncludeBase<BaseEntity, BaseDTO>();
cfg.CreateMap<DTO, Entity>()
.IncludeBase<BaseDTO, BaseEntity>();
});
return config;
}
}

protected override void Because_of()
{
//Arrange
_source = new List<BaseEntity> {
new Entity { Id = Guid.NewGuid(), Name = "Sofia" },
new Entity { Id = Guid.NewGuid(), Name = "Rafael" },
new BaseEntity { Id = Guid.NewGuid() }
};
}

[Fact]
public void Should_support_propertypath_expressions_with_properties_from_sub_types_using_explicit_cast()
{
// Act
Expression<Func<BaseDTO, bool>> dtoQueryExpression = r => (r is DTO ? ((DTO)r).Name : "") == "Sofia";
Expression<Func<BaseEntity, bool>> entityQueryExpression = Mapper.MapExpression<Expression<Func<BaseEntity, bool>>>(dtoQueryExpression);
entityQuery = _source.AsQueryable().Where(entityQueryExpression);

// Assert
entityQuery.ToList().Count().ShouldBe(1);
}

[Fact]
public void Should_support_propertypath_expressions_with_properties_from_sub_types_using_as_keyword()
{
// Act
Expression<Func<BaseDTO, bool>> dtoQueryExpression = r => (r is DTO ? (r as DTO).Name : "") == "Sofia";
Expression<Func<BaseEntity, bool>> entityQueryExpression = Mapper.MapExpression<Expression<Func<BaseEntity, bool>>>(dtoQueryExpression);
entityQuery = _source.AsQueryable().Where(entityQueryExpression);

// Assert
entityQuery.ToList().Count().ShouldBe(1);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ public void Works_for_inherited_properties()
Assert.True(items.Count == 1);
}

[Fact]
public void Works_for_inherited_properties_on_base_types()
{
//Arrange
Expression<Func<RootModel, bool>> selection = s => ((DerivedModel)s).Nested.NestedTitle2 == "nested test";

//Act
Expression<Func<DataModel, bool>> selectionMapped = mapper.MapExpression<Expression<Func<DataModel, bool>>>(selection);
List<DataModel> items = DataObjects.Where(selectionMapped).ToList();

//Assert
Assert.True(items.Count == 1);
}

[Fact]
public void Works_for_top_level_string_member()
{
Expand Down Expand Up @@ -217,9 +231,13 @@ public class ForPathCustomerProfile : Profile
{
public ForPathCustomerProfile()
{
CreateMap<RootModel, DataModel>()
.Include<DerivedModel, DerivedDataModel>();

CreateMap<DerivedDataModel, DerivedModel>()
.ForPath(d => d.Nested.NestedTitle, opt => opt.MapFrom(src => src.Title))
.ForPath(d => d.Nested.NestedTitle2, opt => opt.MapFrom(src => src.Title2));
.ForPath(d => d.Nested.NestedTitle2, opt => opt.MapFrom(src => src.Title2))
.ReverseMap();

CreateMap<OrderDto, Order>()
.ForPath(o => o.CustomerHolder.Customer.Name, o => o.MapFrom(s => s.Customer.Name))
Expand Down

0 comments on commit bebad43

Please sign in to comment.