Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: AutoMapper/AutoMapper.Extensions.ExpressionMapping
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: rodrigoreis/AutoMapper.Extensions.ExpressionMapping
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Can’t automatically merge. Don’t worry, you can still create the pull request.
  • 1 commit
  • 4 files changed
  • 1 contributor

Commits on Aug 14, 2019

  1. Copy the full SHA
    e96a580 View commit details
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
using System;
using AutoMapper.Extensions.ExpressionMapping.Extensions;
using AutoMapper.Extensions.ExpressionMapping.Structures;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using AutoMapper.Internal;
using AutoMapper.Extensions.ExpressionMapping.Extensions;
using AutoMapper.Extensions.ExpressionMapping.Structures;

namespace AutoMapper.Extensions.ExpressionMapping
{
public class MapIncludesVisitor : XpressionMapperVisitor
{
public MapIncludesVisitor(IMapper mapper, IConfigurationProvider configurationProvider, Dictionary<Type, Type> typeMappings)
: base(mapper, configurationProvider, typeMappings)
public MapIncludesVisitor(IMapper mapper, IConfigurationProvider configurationProvider, Dictionary<Type, Type> typeMappings, bool ignoreValidations)
: base(mapper, configurationProvider, typeMappings, ignoreValidations)
{
}

60 changes: 36 additions & 24 deletions src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System;
using AutoMapper.Internal;
using AutoMapper.Mappers.Internal;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using static System.Linq.Expressions.Expression;
using System.Reflection;
using AutoMapper.Mappers.Internal;
using AutoMapper.Internal;
using static System.Linq.Expressions.Expression;

namespace AutoMapper.Extensions.ExpressionMapping
{
@@ -17,21 +17,23 @@ public static class MapperExtensions
/// <typeparam name="TDestDelegate"></typeparam>
/// <param name="mapper"></param>
/// <param name="expression"></param>
/// <param name="ignoreValidations"></param>
/// <returns></returns>
public static TDestDelegate MapExpression<TDestDelegate>(this IMapper mapper, LambdaExpression expression)
public static TDestDelegate MapExpression<TDestDelegate>(this IMapper mapper, LambdaExpression expression, bool ignoreValidations = false)
where TDestDelegate : LambdaExpression
{
if (expression == null)
return default;

return mapper.MapExpression<TDestDelegate>
(
expression,
(config, mappings) => new XpressionMapperVisitor(mapper, config, mappings)
expression,
ignoreValidations,
(config, mappings, _ignoreValidations) => new XpressionMapperVisitor(mapper, config, mappings, _ignoreValidations)
);
}

private static TDestDelegate MapExpression<TDestDelegate>(this IMapper mapper, LambdaExpression expression, Func<IConfigurationProvider, Dictionary<Type, Type>, XpressionMapperVisitor> getVisitor)
private static TDestDelegate MapExpression<TDestDelegate>(this IMapper mapper, LambdaExpression expression, bool ignoreValidations, Func<IConfigurationProvider, Dictionary<Type, Type>, bool, XpressionMapperVisitor> getVisitor)
where TDestDelegate : LambdaExpression
{
return MapExpression<TDestDelegate>
@@ -41,6 +43,7 @@ private static TDestDelegate MapExpression<TDestDelegate>(this IMapper mapper, L
expression,
expression.GetType().GetGenericArguments()[0],
typeof(TDestDelegate).GetGenericArguments()[0],
ignoreValidations,
getVisitor
);
}
@@ -50,13 +53,14 @@ private static TDestDelegate MapExpression<TDestDelegate>(IMapper mapper,
LambdaExpression expression,
Type typeSourceFunc,
Type typeDestFunc,
Func<IConfigurationProvider, Dictionary<Type, Type>, XpressionMapperVisitor> getVisitor)
bool ignoreValidations,
Func<IConfigurationProvider, Dictionary<Type, Type>, bool, XpressionMapperVisitor> getVisitor)
where TDestDelegate : LambdaExpression
{
return CreateVisitor(new Dictionary<Type, Type>().AddTypeMappingsFromDelegates(configurationProvider, typeSourceFunc, typeDestFunc));

TDestDelegate CreateVisitor(Dictionary<Type, Type> typeMappings)
=> MapBody(typeMappings, getVisitor(configurationProvider, typeMappings));
=> MapBody(typeMappings, getVisitor(configurationProvider, typeMappings, ignoreValidations));

TDestDelegate MapBody(Dictionary<Type, Type> typeMappings, XpressionMapperVisitor visitor)
=> GetLambda(typeMappings, visitor, visitor.Visit(expression.Body));
@@ -85,20 +89,22 @@ private static bool IsFuncType(this Type type)
/// <typeparam name="TDestDelegate"></typeparam>
/// <param name="mapper"></param>
/// <param name="expression"></param>
/// <param name="ignoreValidations"></param>
/// <returns></returns>
public static TDestDelegate MapExpression<TSourceDelegate, TDestDelegate>(this IMapper mapper, TSourceDelegate expression)
public static TDestDelegate MapExpression<TSourceDelegate, TDestDelegate>(this IMapper mapper, TSourceDelegate expression, bool ignoreValidations = false)
where TSourceDelegate : LambdaExpression
where TDestDelegate : LambdaExpression
=> mapper.MapExpression<TDestDelegate>(expression);
=> mapper.MapExpression<TDestDelegate>(expression, ignoreValidations);

/// <summary>
/// Maps an expression to be used as an "Include" given a dictionary of types where the source type is the key and the destination type is the value.
/// </summary>
/// <typeparam name="TDestDelegate"></typeparam>
/// <param name="mapper"></param>
/// <param name="expression"></param>
/// <param name="ignoreValidations"></param>
/// <returns></returns>
public static TDestDelegate MapExpressionAsInclude<TDestDelegate>(this IMapper mapper, LambdaExpression expression)
public static TDestDelegate MapExpressionAsInclude<TDestDelegate>(this IMapper mapper, LambdaExpression expression, bool ignoreValidations = false)
where TDestDelegate : LambdaExpression
{
if (expression == null)
@@ -107,7 +113,8 @@ public static TDestDelegate MapExpressionAsInclude<TDestDelegate>(this IMapper m
return mapper.MapExpression<TDestDelegate>
(
expression,
(config, mappings) => new MapIncludesVisitor(mapper, config, mappings)
ignoreValidations,
(config, mappings, _ignoreValidations) => new MapIncludesVisitor(mapper, config, mappings, _ignoreValidations)
);
}

@@ -118,11 +125,12 @@ public static TDestDelegate MapExpressionAsInclude<TDestDelegate>(this IMapper m
/// <typeparam name="TDestDelegate"></typeparam>
/// <param name="mapper"></param>
/// <param name="expression"></param>
/// <param name="ignoreValidations"></param>
/// <returns></returns>
public static TDestDelegate MapExpressionAsInclude<TSourceDelegate, TDestDelegate>(this IMapper mapper, TSourceDelegate expression)
public static TDestDelegate MapExpressionAsInclude<TSourceDelegate, TDestDelegate>(this IMapper mapper, TSourceDelegate expression, bool ignoreValidations = false)
where TSourceDelegate : LambdaExpression
where TDestDelegate : LambdaExpression
=> mapper.MapExpressionAsInclude<TDestDelegate>(expression);
=> mapper.MapExpressionAsInclude<TDestDelegate>(expression, ignoreValidations);

/// <summary>
/// Maps a collection of expressions given a dictionary of types where the source type is the key and the destination type is the value.
@@ -131,22 +139,24 @@ public static TDestDelegate MapExpressionAsInclude<TSourceDelegate, TDestDelegat
/// <typeparam name="TDestDelegate"></typeparam>
/// <param name="mapper"></param>
/// <param name="collection"></param>
/// <param name="ignoreValidations"></param>
/// <returns></returns>
public static ICollection<TDestDelegate> MapExpressionList<TSourceDelegate, TDestDelegate>(this IMapper mapper, ICollection<TSourceDelegate> collection)
public static ICollection<TDestDelegate> MapExpressionList<TSourceDelegate, TDestDelegate>(this IMapper mapper, ICollection<TSourceDelegate> collection, bool ignoreValidations = false)
where TSourceDelegate : LambdaExpression
where TDestDelegate : LambdaExpression
=> collection?.Select(mapper.MapExpression<TSourceDelegate, TDestDelegate>).ToList();
=> collection?.Select(item => mapper.MapExpression<TSourceDelegate, TDestDelegate>(item, ignoreValidations)).ToList();

/// <summary>
/// Maps a collection of expressions given a dictionary of types where the source type is the key and the destination type is the value.
/// </summary>
/// <typeparam name="TDestDelegate"></typeparam>
/// <param name="mapper"></param>
/// <param name="collection"></param>
/// <param name="ignoreValidations"></param>
/// <returns></returns>
public static ICollection<TDestDelegate> MapExpressionList<TDestDelegate>(this IMapper mapper, IEnumerable<LambdaExpression> collection)
public static ICollection<TDestDelegate> MapExpressionList<TDestDelegate>(this IMapper mapper, IEnumerable<LambdaExpression> collection, bool ignoreValidations = false)
where TDestDelegate : LambdaExpression
=> collection?.Select(mapper.MapExpression<TDestDelegate>).ToList();
=> collection?.Select(item => mapper.MapExpression<TDestDelegate>(item, ignoreValidations)).ToList();

/// <summary>
/// Maps a collection of expressions to be used as a "Includes" given a dictionary of types where the source type is the key and the destination type is the value.
@@ -155,22 +165,24 @@ public static ICollection<TDestDelegate> MapExpressionList<TDestDelegate>(this I
/// <typeparam name="TDestDelegate"></typeparam>
/// <param name="mapper"></param>
/// <param name="collection"></param>
/// <param name="ignoreValidations"></param>
/// <returns></returns>
public static ICollection<TDestDelegate> MapIncludesList<TSourceDelegate, TDestDelegate>(this IMapper mapper, ICollection<TSourceDelegate> collection)
public static ICollection<TDestDelegate> MapIncludesList<TSourceDelegate, TDestDelegate>(this IMapper mapper, ICollection<TSourceDelegate> collection, bool ignoreValidations = false)
where TSourceDelegate : LambdaExpression
where TDestDelegate : LambdaExpression
=> collection?.Select(mapper.MapExpressionAsInclude<TSourceDelegate, TDestDelegate>).ToList();
=> collection?.Select(item => mapper.MapExpressionAsInclude<TSourceDelegate, TDestDelegate>(item, ignoreValidations)).ToList();

/// <summary>
/// Maps a collection of expressions to be used as a "Includes" given a dictionary of types where the source type is the key and the destination type is the value.
/// </summary>
/// <typeparam name="TDestDelegate"></typeparam>
/// <param name="mapper"></param>
/// <param name="collection"></param>
/// <param name="ignoreValidations"></param>
/// <returns></returns>
public static ICollection<TDestDelegate> MapIncludesList<TDestDelegate>(this IMapper mapper, IEnumerable<LambdaExpression> collection)
public static ICollection<TDestDelegate> MapIncludesList<TDestDelegate>(this IMapper mapper, IEnumerable<LambdaExpression> collection, bool ignoreValidations = false)
where TDestDelegate : LambdaExpression
=> collection?.Select(mapper.MapExpressionAsInclude<TDestDelegate>).ToList();
=> collection?.Select(item => mapper.MapExpressionAsInclude<TDestDelegate>(item, ignoreValidations)).ToList();

/// <summary>
/// Takes a list of parameters from the source lamda expression and returns a list of parameters for the destination lambda expression.
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
using System;
using AutoMapper.Extensions.ExpressionMapping.Extensions;
using AutoMapper.Extensions.ExpressionMapping.Structures;
using AutoMapper.QueryableExtensions.Impl;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using AutoMapper.Extensions.ExpressionMapping.ArgumentMappers;
using AutoMapper.Extensions.ExpressionMapping.Extensions;
using AutoMapper.Extensions.ExpressionMapping.Structures;
using AutoMapper.Internal;
using AutoMapper.QueryableExtensions.Impl;

namespace AutoMapper.Extensions.ExpressionMapping
{
public class XpressionMapperVisitor : ExpressionVisitor
{
public XpressionMapperVisitor(IMapper mapper, IConfigurationProvider configurationProvider, Dictionary<Type, Type> typeMappings)
private readonly bool _ignoreValidations;

public XpressionMapperVisitor(IMapper mapper, IConfigurationProvider configurationProvider, Dictionary<Type, Type> typeMappings, bool ignoreValidations)
{
Mapper = mapper;
TypeMappings = typeMappings;
InfoDictionary = new MapperInfoDictionary(new ParameterExpressionEqualityComparer());
ConfigurationProvider = configurationProvider;
_ignoreValidations = ignoreValidations;
}

public MapperInfoDictionary InfoDictionary { get; }
@@ -154,7 +155,7 @@ protected override Expression VisitUnary(UnaryExpression node)
case ExpressionType.Constant:
return ProcessConstant((ConstantExpression)node.Operand);
default:
return base.VisitUnary(node);
return _ignoreValidations ? node.Update(Visit(node.Operand)) : base.VisitUnary(node);
}
case ExpressionType.Lambda:
var lambdaExpression = (LambdaExpression)node.Operand;
@@ -164,7 +165,7 @@ protected override Expression VisitUnary(UnaryExpression node)
this.TypeMappings.AddTypeMapping(ConfigurationProvider, node.Type, mapped.Type);
return mapped;
default:
return base.VisitUnary(node);
return _ignoreValidations ? node.Update(Visit(node.Operand)) : base.VisitUnary(node);
}

Expression ProcessConstant(ConstantExpression operand)
@@ -277,13 +278,13 @@ private static void AddPropertyMapInfo(Type parentType, string name, List<Proper
}
}

private bool GenericTypeDefinitionsAreEquivalent(Type typeSource, Type typeDestination)
private bool GenericTypeDefinitionsAreEquivalent(Type typeSource, Type typeDestination)
=> typeSource.IsGenericType() && typeDestination.IsGenericType() && typeSource.GetGenericTypeDefinition() == typeDestination.GetGenericTypeDefinition();

protected void FindDestinationFullName(Type typeSource, Type typeDestination, string sourceFullName, List<PropertyMapInfo> propertyMapInfoList)
{
const string period = ".";

if (typeSource == typeDestination)
{
var sourceFullNameArray = sourceFullName.Split(new[] { period[0] }, StringSplitOptions.RemoveEmptyEntries);
@@ -326,7 +327,7 @@ protected void FindDestinationFullName(Type typeSource, Type typeDestination, st

var sourceType = typeSource.GetFieldOrProperty(propertyName).GetMemberType();
var destType = typeDestination.GetFieldOrProperty(propertyName).GetMemberType();

TypeMappings.AddTypeMapping(ConfigurationProvider, sourceType, destType);

var childFullName = sourceFullName.Substring(sourceFullName.IndexOf(period, StringComparison.OrdinalIgnoreCase) + 1);
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@

using Shouldly;
using System;
using System.Linq;
using System.Linq.Expressions;
using Shouldly;
using Xunit;

namespace AutoMapper.Extensions.ExpressionMapping.UnitTests
@@ -28,6 +28,28 @@ public class Dest
public int ChildValue { get; set; }
}

public enum SourceEnum
{
Foo,
Bar
}

public enum DestEnum
{
Foo,
Bar
}

public class SourceWithEnum : Source
{
public SourceEnum Enum { get; set; }
}

public class DestWithEnum : Dest
{
public DestEnum Enum { get; set; }
}

[Fact]
public void Can_map_single_properties()
{
@@ -133,5 +155,44 @@ public void Can_map_with_different_destination_types()

var items2 = items.AsQueryable().Select(mapped).ToList();
}

[Fact]
public void Throw_AutoMapperMappingException_because_it_would_change_the_meaning_of_the_operation()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddExpressionMapping();
cfg.CreateMap<SourceEnum, DestEnum>();
cfg.CreateMap<DestWithEnum, SourceWithEnum>();
});

Expression<Func<SourceWithEnum, bool>> expr = s => s.Enum == SourceEnum.Foo;

Assert.Throws<InvalidOperationException>(() => config.CreateMapper().MapExpression<Expression<Func<SourceWithEnum, bool>>, Expression<Func<DestWithEnum, bool>>>(expr));
}

[Fact]
public void Can_map_even_it_would_change_the_meaning_of_the_operation()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddExpressionMapping();
cfg.CreateMap<SourceEnum, DestEnum>();
cfg.CreateMap<DestWithEnum, SourceWithEnum>();
});

Expression<Func<SourceWithEnum, bool>> expr = s => s.Enum == SourceEnum.Bar;

var mapped = config.CreateMapper().MapExpression<Expression<Func<SourceWithEnum, bool>>, Expression<Func<DestWithEnum, bool>>>(expr, ignoreValidations: true);

var items = new[]
{
new DestWithEnum {Enum = DestEnum.Foo},
new DestWithEnum {Enum = DestEnum.Bar},
new DestWithEnum {Enum = DestEnum.Bar}
};

var items2 = items.AsQueryable().Select(mapped).ToList();
}
}
}