From 61295b3ce1e20d70976c624fbbec13658b61ae37 Mon Sep 17 00:00:00 2001 From: BlaiseD Date: Fri, 20 Nov 2020 11:01:05 -0500 Subject: [PATCH 1/2] Supporting IncludeMembers. --- Pack_Push.ps1 | 2 +- ...Mapper.Extensions.ExpressionMapping.csproj | 2 +- .../Extensions/VisitorExtensions.cs | 6 +- .../Structures/DeclaringMemberKey.cs | 41 ++ .../Structures/MemberAssignmentInfo.cs | 26 ++ .../Structures/MemberBindingGroup.cs | 42 ++ .../XpressionMapperVisitor.cs | 169 +++++++- ...MappingWithIncludeMembersConfigurations.cs | 404 ++++++++++++++++++ 8 files changed, 666 insertions(+), 26 deletions(-) create mode 100644 src/AutoMapper.Extensions.ExpressionMapping/Structures/DeclaringMemberKey.cs create mode 100644 src/AutoMapper.Extensions.ExpressionMapping/Structures/MemberAssignmentInfo.cs create mode 100644 src/AutoMapper.Extensions.ExpressionMapping/Structures/MemberBindingGroup.cs create mode 100644 tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingWithIncludeMembersConfigurations.cs diff --git a/Pack_Push.ps1 b/Pack_Push.ps1 index ec7e438..94e20e0 100644 --- a/Pack_Push.ps1 +++ b/Pack_Push.ps1 @@ -9,7 +9,7 @@ $NUGET_PACKAGE_PATH = ".\artifacts\$($Env:PROJECT_NAME).*.nupkg" Write-Host "Project Path ${PROJECT_PATH}" Write-Host "Package Path ${NUGET_PACKAGE_PATH}" -if ($Env:REPO_OWNER -ne "AutoMapper") { +if ([string]::IsNullOrEmpty($Env:DEPLOY_PACKAGE_API_KEY)) { Write-Host "${scriptName}: Only creates packages on AutoMapper repositories." } else { dotnet pack $PROJECT_PATH -c Release -o .\artifacts --no-build diff --git a/src/AutoMapper.Extensions.ExpressionMapping/AutoMapper.Extensions.ExpressionMapping.csproj b/src/AutoMapper.Extensions.ExpressionMapping/AutoMapper.Extensions.ExpressionMapping.csproj index 4daeca4..904155a 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/AutoMapper.Extensions.ExpressionMapping.csproj +++ b/src/AutoMapper.Extensions.ExpressionMapping/AutoMapper.Extensions.ExpressionMapping.csproj @@ -18,7 +18,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Extensions/VisitorExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/Extensions/VisitorExtensions.cs index 667d366..8989718 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/Extensions/VisitorExtensions.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/Extensions/VisitorExtensions.cs @@ -95,6 +95,9 @@ public static Expression ConvertTypeIfNecessary(this Expression expression, Type public static MemberExpression GetMemberExpression(this LambdaExpression expr) => expr.Body.GetUnconvertedMemberExpression() as MemberExpression; + public static MemberExpression GetMemberExpression(this Expression expr) + => expr.GetUnconvertedMemberExpression() as MemberExpression; + /// /// Returns the ParameterExpression for the LINQ parameter. /// @@ -177,7 +180,8 @@ public static string GetMemberFullName(this LambdaExpression expr) { case ExpressionType.Convert: case ExpressionType.ConvertChecked: - me = (expr.Body as UnaryExpression)?.Operand as MemberExpression; + case ExpressionType.TypeAs: + me = expr.Body.GetUnconvertedMemberExpression() as MemberExpression; break; default: me = expr.Body as MemberExpression; diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Structures/DeclaringMemberKey.cs b/src/AutoMapper.Extensions.ExpressionMapping/Structures/DeclaringMemberKey.cs new file mode 100644 index 0000000..7603a9f --- /dev/null +++ b/src/AutoMapper.Extensions.ExpressionMapping/Structures/DeclaringMemberKey.cs @@ -0,0 +1,41 @@ +using System; +using System.Reflection; + +namespace AutoMapper.Extensions.ExpressionMapping.Structures +{ + internal class DeclaringMemberKey : IEquatable + { + public DeclaringMemberKey(MemberInfo declaringMemberInfo, string declaringMemberFullName) + { + DeclaringMemberInfo = declaringMemberInfo; + DeclaringMemberFullName = declaringMemberFullName; + } + + public MemberInfo DeclaringMemberInfo { get; set; } + public string DeclaringMemberFullName { get; set; } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + + DeclaringMemberKey key = obj as DeclaringMemberKey; + if (key == null) return false; + + return Equals(key); + } + + public bool Equals(DeclaringMemberKey other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return this.DeclaringMemberInfo.Equals(other.DeclaringMemberInfo) + && this.DeclaringMemberFullName == other.DeclaringMemberFullName; + } + + public override int GetHashCode() => this.DeclaringMemberInfo.GetHashCode(); + + public override string ToString() => this.DeclaringMemberFullName; + } +} diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Structures/MemberAssignmentInfo.cs b/src/AutoMapper.Extensions.ExpressionMapping/Structures/MemberAssignmentInfo.cs new file mode 100644 index 0000000..d6a1636 --- /dev/null +++ b/src/AutoMapper.Extensions.ExpressionMapping/Structures/MemberAssignmentInfo.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Text; + +namespace AutoMapper.Extensions.ExpressionMapping.Structures +{ + internal class MemberAssignmentInfo + { + public MemberAssignmentInfo(PropertyMap propertyMap, MemberAssignment memberAssignment) + { + PropertyMap = propertyMap; + MemberAssignment = memberAssignment; + } + + /// + /// Used to get the source member to be bound with the mapped binding expression. + /// + public PropertyMap PropertyMap { get; set; } + + /// + /// Initial member assignment who's binding expression will be mapped and assigned to the source menber of the new type + /// + public MemberAssignment MemberAssignment { get; set; } + } +} diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Structures/MemberBindingGroup.cs b/src/AutoMapper.Extensions.ExpressionMapping/Structures/MemberBindingGroup.cs new file mode 100644 index 0000000..d847a81 --- /dev/null +++ b/src/AutoMapper.Extensions.ExpressionMapping/Structures/MemberBindingGroup.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; + +namespace AutoMapper.Extensions.ExpressionMapping.Structures +{ + /// + /// Defines the type to be initialized and a list of source bindings. + /// The new bound members will be matched using MemberAssignmentInfos.PropertyMap and + /// assigned to the mapped expression (mapped from MemberAssignmentInfos.MemberAssignment.Expression). + /// + internal class MemberBindingGroup + { + public MemberBindingGroup(DeclaringMemberKey declaringMemberKey, bool isRootMemberAssignment, Type newType, List memberAssignmentInfos) + { + DeclaringMemberKey = declaringMemberKey; + IsRootMemberAssignment = isRootMemberAssignment; + NewType = newType; + MemberAssignmentInfos = memberAssignmentInfos; + } + + /// + /// DeclaringMemberKey will be null when the member assignment is a member binding of OldType on the initial (root) TypeMap (OldType -> NewType) + /// + public DeclaringMemberKey DeclaringMemberKey { get; set; } + + /// + /// MemberAssignment is true if it is a member binding of OldType on the initial (root) TypeMap (OldType -> NewType) + /// + public bool IsRootMemberAssignment { get; set; } + + /// + /// Destination type of the member assignment. If IsRootMemberAssignment == true then this is the destination type of initial (root) TypeMap (OldType -> NewType) + /// Otherwise it is the PropertyType/FieldType of DeclaringMemberInfo + /// + public Type NewType { get; set; } + + /// + /// List of members to be mapped and bound to the new type + /// + public List MemberAssignmentInfos { get; set; } + } +} diff --git a/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs b/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs index 6eccfdc..e169f1c 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs @@ -1,15 +1,14 @@ -using System; +using AutoMapper.Extensions.ExpressionMapping.Extensions; +using AutoMapper.Extensions.ExpressionMapping.Structures; +using AutoMapper.Internal; +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 { @@ -175,34 +174,119 @@ protected override Expression VisitMemberInit(MemberInitExpression node) //The destination becomes the source because to map a source expression to a destination expression, //we need the expressions used to create the source from the destination - IEnumerable bindings = node.Bindings.Aggregate(new List(), (list, binding) => - { - var propertyMap = typeMap.PropertyMaps.SingleOrDefault(item => item.DestinationName == binding.Member.Name); - if (propertyMap == null) - return list; + return GetMemberInit + ( + new MemberBindingGroup + ( + declaringMemberKey: null, + isRootMemberAssignment: true, + newType: newType, + memberAssignmentInfos: node.Bindings.OfType().Aggregate(new List(), (list, binding) => + { + var propertyMap = typeMap.PropertyMaps.SingleOrDefault(item => item.DestinationName == binding.Member.Name); + if (propertyMap == null) + return list; + + list.Add(new MemberAssignmentInfo(propertyMap, binding)); + return list; + }) + ) + ); + } + + return base.VisitMemberInit(node); + + } - var sourceMember = GetSourceMember(propertyMap); - if (sourceMember == null) - return list; + private MemberInitExpression GetMemberInit(MemberBindingGroup memberBindingGroup) + { + Dictionary> includedMembers = new Dictionary>(); + + List bindings = memberBindingGroup.MemberAssignmentInfos.Aggregate(new List(), (list, next) => + { + var propertyMap = next.PropertyMap; + var binding = next.MemberAssignment; + + var sourceMember = GetSourceMember(propertyMap);//does the corresponding member mapping exist + if (sourceMember == null) + return list; + + DeclaringMemberKey declaringMemberKey = new DeclaringMemberKey + ( + GetParentMember(propertyMap), + BuildParentFullName(propertyMap) + ); - Expression bindingExpression = ((MemberAssignment)binding).Expression; - list.Add + if (ShouldBindPropertyMap(next)) + { + list.Add//adding bindings for property maps ( DoBind ( sourceMember, - bindingExpression, - this.Visit(bindingExpression) + binding.Expression, + this.Visit(binding.Expression) ) ); + } + else + { + if (declaringMemberKey.DeclaringMemberInfo == null) + throw new ArgumentNullException(nameof(declaringMemberKey.DeclaringMemberInfo)); - return list; - }); + if (!includedMembers.TryGetValue(declaringMemberKey, out List assignments)) + { + includedMembers.Add + ( + declaringMemberKey, + new List + { + new MemberAssignmentInfo + ( + propertyMap, + binding + ) + } + ); + } + else + { + assignments.Add(new MemberAssignmentInfo(propertyMap, binding)); + } + } - return Expression.MemberInit(Expression.New(newType), bindings); - } + return list; - return base.VisitMemberInit(node); + bool ShouldBindPropertyMap(MemberAssignmentInfo memberAssignmentInfo) + => (memberBindingGroup.IsRootMemberAssignment && sourceMember.ReflectedType == memberBindingGroup.NewType) + || (!memberBindingGroup.IsRootMemberAssignment && declaringMemberKey.Equals(memberBindingGroup.DeclaringMemberKey)); + }); + + includedMembers.Select + ( + kvp => new MemberBindingGroup + ( + declaringMemberKey: kvp.Key, + isRootMemberAssignment: false, + newType: kvp.Key.DeclaringMemberInfo.GetMemberType(), + memberAssignmentInfos: includedMembers.Values.SelectMany(m => m).ToList() + ) + ) + .ToList() + .ForEach(group => + { + if (ShouldBindChildReference(group)) + bindings.Add(Expression.Bind(group.DeclaringMemberKey.DeclaringMemberInfo, GetMemberInit(group))); + }); + + bool ShouldBindChildReference(MemberBindingGroup group) + => (memberBindingGroup.IsRootMemberAssignment + && group.DeclaringMemberKey.DeclaringMemberInfo.ReflectedType == memberBindingGroup.NewType) + || (!memberBindingGroup.IsRootMemberAssignment + && group.DeclaringMemberKey.DeclaringMemberInfo.ReflectedType == memberBindingGroup.NewType + && group.DeclaringMemberKey.DeclaringMemberFullName.StartsWith(memberBindingGroup.DeclaringMemberKey.DeclaringMemberFullName)); + + return Expression.MemberInit(Expression.New(memberBindingGroup.NewType), bindings); } private MemberBinding DoBind(MemberInfo sourceMember, Expression initial, Expression mapped) @@ -217,6 +301,39 @@ private MemberInfo GetSourceMember(PropertyMap propertyMap) ? propertyMap.CustomMapExpression.GetMemberExpression()?.Member : propertyMap.SourceMember; + private MemberInfo GetParentMember(PropertyMap propertyMap) + => propertyMap.IncludedMember != null + ? propertyMap.ProjectToCustomSource.GetMemberExpression().Member + : GetSourceParentMember(propertyMap); + + private MemberInfo GetSourceParentMember(PropertyMap propertyMap) + { + if (propertyMap.CustomMapExpression != null) + return propertyMap.CustomMapExpression.GetMemberExpression()?.Expression.GetMemberExpression()?.Member; + + if (propertyMap.SourceMembers.Count > 1) + return new List(propertyMap.SourceMembers)[propertyMap.SourceMembers.Count - 2]; + + return null; + } + + private string BuildParentFullName(PropertyMap propertyMap) + { + List propertyMapInfos = new List(); + if (propertyMap.IncludedMember != null) + propertyMapInfos.Add(new PropertyMapInfo(propertyMap.ProjectToCustomSource, new List())); + + propertyMapInfos.Add(new PropertyMapInfo(propertyMap.CustomMapExpression, propertyMap.SourceMembers.ToList())); + + List fullNameArray = BuildFullName(propertyMapInfos) + .Split(new char[] { '.' }) + .ToList(); + + fullNameArray.Remove(fullNameArray.Last()); + + return string.Join(".", fullNameArray); + } + protected override Expression VisitBinary(BinaryExpression node) { return DoVisitBinary(this.Visit(node.Left), this.Visit(node.Right), this.Visit(node.Conversion)); @@ -509,6 +626,9 @@ void CompareSourceAndDestLiterals(Type mappedPropertyType, string mappedProperty throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.expressionMapValueTypeMustMatchFormat, mappedPropertyType.Name, mappedPropertyDescription, sourceMemberType.Name, propertyMap.DestinationMember.Name)); } + if (propertyMap.ProjectToCustomSource != null) + propertyMapInfoList.Add(new PropertyMapInfo(propertyMap.ProjectToCustomSource, new List())); + propertyMapInfoList.Add(new PropertyMapInfo(propertyMap.CustomMapExpression, propertyMap.SourceMembers.ToList())); } else @@ -520,6 +640,9 @@ void CompareSourceAndDestLiterals(Type mappedPropertyType, string mappedProperty if (propertyMap.CustomMapExpression == null && !propertyMap.SourceMembers.Any())//If sourceFullName has a period then the SourceMember cannot be null. The SourceMember is required to find the ProertyMap of its child object. throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.srcMemberCannotBeNullFormat, typeSource.Name, typeDestination.Name, propertyName)); + if (propertyMap.ProjectToCustomSource != null) + propertyMapInfoList.Add(new PropertyMapInfo(propertyMap.ProjectToCustomSource, new List())); + propertyMapInfoList.Add(new PropertyMapInfo(propertyMap.CustomMapExpression, propertyMap.SourceMembers.ToList())); var childFullName = sourceFullName.Substring(sourceFullName.IndexOf(period, StringComparison.OrdinalIgnoreCase) + 1); diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingWithIncludeMembersConfigurations.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingWithIncludeMembersConfigurations.cs new file mode 100644 index 0000000..7c21a41 --- /dev/null +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingWithIncludeMembersConfigurations.cs @@ -0,0 +1,404 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Xunit; + +namespace AutoMapper.Extensions.ExpressionMapping.UnitTests +{ + public class MappingWithIncludeMembersConfigurations + { + [Fact] + public void ShouldMapWhenParenIsConfiguredUsingIncludeMembers() + { + var config = new MapperConfiguration(cfg => + { + cfg.CreateMap() + .IncludeMembers(p => p.StatsA/*, p => p.StatsB*/); + + cfg.CreateMap() + .ForMember(d => d.Name, opt => opt.Ignore()) + .ForMember(d => d.StatsARating, opt => opt.MapFrom(s => s.Rating)) + .ForMember(d => d.StatsBRating, opt => opt.MapFrom(s => s.Rating)) + .ForMember(d => d.StatsASpeed, opt => opt.MapFrom(s => s.SpeedValue)) + .ForMember(d => d.StatsBSpeed, opt => opt.MapFrom(s => s.SpeedValue)) + .ForMember(d => d.StatsAPower, opt => opt.MapFrom(s => s.Power)) + .ForMember(d => d.StatsBPower, opt => opt.MapFrom(s => s.Power)) + .IncludeMembers(p => p.StatsBuilder); + + cfg.CreateMap() + .ForMember(d => d.StatsABuilderCity, opt => opt.MapFrom(s => s.City)) + .ForMember(d => d.StatsABuilderId, opt => opt.MapFrom(s => s.Id)) + .ForMember(d => d.StatsBBuilderCity, opt => opt.MapFrom(s => s.City)) + .ForMember(d => d.StatsBBuilderId, opt => opt.MapFrom(s => s.Id)) + .ForMember(d => d.Name, opt => opt.Ignore()) + .ForMember(d => d.StatsASpeed, opt => opt.Ignore()) + .ForMember(d => d.StatsAPower, opt => opt.Ignore()) + .ForMember(d => d.StatsARating, opt => opt.Ignore()) + .ForMember(d => d.StatsBSpeed, opt => opt.Ignore()) + .ForMember(d => d.StatsBPower, opt => opt.Ignore()) + .ForMember(d => d.StatsBRating, opt => opt.Ignore()); + }); + + config.AssertConfigurationIsValid(); + var mapper = config.CreateMapper(); + + List models = mapper.ProjectTo(Players).ToList(); + Assert.Equal("Jack", models[0].Name); + + Assert.Equal(1, models[0].StatsAPower); + Assert.Equal(2, models[0].StatsASpeed); + Assert.Equal(5, models[0].StatsARating); + Assert.Equal(1, models[0].StatsABuilderId); + Assert.Equal("Atlanta", models[0].StatsABuilderCity); + + //Assert.Equal(2, models[0].StatsBPower); + //Assert.Equal(3, models[0].StatsBSpeed); + //Assert.Equal(7, models[0].StatsBRating); + //Assert.Equal(3, models[0].StatsBBuilderId); + //Assert.Equal("Columbia", models[0].StatsBBuilderCity); + } + + [Fact] + public void Map_member_init_with_include_members() + { + var config = new MapperConfiguration(cfg => + { + cfg.AddExpressionMapping(); + + cfg.CreateMap() + .ForMember(d => d.StatsABuilderCity, opt => opt.Ignore()) + .ForMember(d => d.StatsABuilderId, opt => opt.Ignore()) + .ForMember(d => d.StatsBBuilderCity, opt => opt.Ignore()) + .ForMember(d => d.StatsBBuilderId, opt => opt.Ignore()) + .IncludeMembers(p => p.StatsA); + + cfg.CreateMap() + .ForMember(d => d.Name, opt => opt.Ignore()) + .ForMember(d => d.StatsABuilderCity, opt => opt.Ignore()) + .ForMember(d => d.StatsARating, opt => opt.MapFrom(s => s.Rating)) + .ForMember(d => d.StatsABuilderId, opt => opt.Ignore()) + .ForMember(d => d.StatsBBuilderCity, opt => opt.Ignore()) + .ForMember(d => d.StatsBRating, opt => opt.Ignore()) + .ForMember(d => d.StatsBBuilderId, opt => opt.Ignore()) + .ForMember(d => d.StatsASpeed, opt => opt.MapFrom(s => s.SpeedValue)) + .ForMember(d => d.StatsBSpeed, opt => opt.MapFrom(s => s.SpeedValue)) + .ForMember(d => d.StatsAPower, opt => opt.MapFrom(s => s.Power)) + .ForMember(d => d.StatsBPower, opt => opt.MapFrom(s => s.Power)); + }); + + config.AssertConfigurationIsValid(); + var mapper = config.CreateMapper(); + + //Arrange + Expression> selection = s => new PlayerModel { Name = s.Name, StatsASpeed = s.StatsASpeed, StatsAPower = s.StatsAPower, StatsARating = s.StatsARating }; + + //Act + Expression> selectionMapped = mapper.MapExpression>>(selection); + List result = Players.Select(selectionMapped).ToList(); + + //Assert + Assert.Equal("Jack", result[0].Name); + Assert.Equal(1, result[0].StatsA.Power); + Assert.Equal(2, result[0].StatsA.SpeedValue); + Assert.Equal(5, result[0].StatsA.Rating); + } + + [Fact] + public void Map_member_init_with_include_members_and_nested_include_members() + { + var config = new MapperConfiguration(cfg => + { + cfg.AddExpressionMapping(); + + cfg.CreateMap() + .IncludeMembers(p => p.StatsA/*, p => p.StatsB*/); + + cfg.CreateMap() + .ForMember(d => d.Name, opt => opt.Ignore()) + .ForMember(d => d.StatsARating, opt => opt.MapFrom(s => s.Rating)) + .ForMember(d => d.StatsBRating, opt => opt.MapFrom(s => s.Rating)) + .ForMember(d => d.StatsASpeed, opt => opt.MapFrom(s => s.SpeedValue)) + .ForMember(d => d.StatsBSpeed, opt => opt.MapFrom(s => s.SpeedValue)) + .ForMember(d => d.StatsAPower, opt => opt.MapFrom(s => s.Power)) + .ForMember(d => d.StatsBPower, opt => opt.MapFrom(s => s.Power)) + .IncludeMembers(p => p.StatsBuilder); + + cfg.CreateMap() + .ForMember(d => d.StatsABuilderCity, opt => opt.MapFrom(s => s.City)) + .ForMember(d => d.StatsABuilderId, opt => opt.MapFrom(s => s.Id)) + .ForMember(d => d.StatsBBuilderCity, opt => opt.MapFrom(s => s.City)) + .ForMember(d => d.StatsBBuilderId, opt => opt.MapFrom(s => s.Id)) + .ForMember(d => d.Name, opt => opt.Ignore()) + .ForMember(d => d.StatsASpeed, opt => opt.Ignore()) + .ForMember(d => d.StatsAPower, opt => opt.Ignore()) + .ForMember(d => d.StatsARating, opt => opt.Ignore()) + .ForMember(d => d.StatsBSpeed, opt => opt.Ignore()) + .ForMember(d => d.StatsBPower, opt => opt.Ignore()) + .ForMember(d => d.StatsBRating, opt => opt.Ignore()); + }); + + config.AssertConfigurationIsValid(); + var mapper = config.CreateMapper(); + + //Arrange + Expression> selection = s => new PlayerModel + { + Name = s.Name, + StatsASpeed = s.StatsASpeed, + StatsAPower = s.StatsAPower, + StatsARating = s.StatsARating, + StatsABuilderId = s.StatsABuilderId, + StatsABuilderCity = s.StatsABuilderCity, + StatsBSpeed = s.StatsBSpeed, + StatsBPower = s.StatsBPower, + StatsBRating = s.StatsBRating, + StatsBBuilderId = s.StatsBBuilderId, + StatsBBuilderCity = s.StatsBBuilderCity + }; + + //Act + Expression> selectionMapped = mapper.MapExpression>>(selection); + List result = Players.Select(selectionMapped).ToList(); + + //Assert + Assert.Equal("Jack", result[0].Name); + Assert.Equal(1, result[0].StatsA.Power); + Assert.Equal(2, result[0].StatsA.SpeedValue); + Assert.Equal(5, result[0].StatsA.Rating); + Assert.Equal(1, result[0].StatsA.StatsBuilder.Id); + Assert.Equal("Atlanta", result[0].StatsA.StatsBuilder.City); + + //Assert.Equal(2, result[0].StatsB.Power); + //Assert.Equal(3, result[0].StatsB.SpeedValue); + //Assert.Equal(7, result[0].StatsB.Rating); + //Assert.Equal(3, result[0].StatsB.StatsBuilder.Id); + //Assert.Equal("Columbia", result[0].StatsB.StatsBuilder.City); + } + + [Fact] + public void Map_member_expression_with_include_members_and_nested_include_members() + { + var config = new MapperConfiguration(cfg => + { + cfg.AddExpressionMapping(); + + cfg.CreateMap() + .IncludeMembers(p => p.StatsA); + + cfg.CreateMap() + .ForMember(d => d.Name, opt => opt.Ignore()) + .ForMember(d => d.StatsARating, opt => opt.MapFrom(s => s.Rating)) + .ForMember(d => d.StatsBRating, opt => opt.MapFrom(s => s.Rating)) + .ForMember(d => d.StatsASpeed, opt => opt.MapFrom(s => s.SpeedValue)) + .ForMember(d => d.StatsBSpeed, opt => opt.MapFrom(s => s.SpeedValue)) + .ForMember(d => d.StatsAPower, opt => opt.MapFrom(s => s.Power)) + .ForMember(d => d.StatsBPower, opt => opt.MapFrom(s => s.Power)) + .IncludeMembers(p => p.StatsBuilder); + + cfg.CreateMap() + .ForMember(d => d.StatsABuilderCity, opt => opt.MapFrom(s => s.City)) + .ForMember(d => d.StatsABuilderId, opt => opt.MapFrom(s => s.Id)) + .ForMember(d => d.StatsBBuilderCity, opt => opt.MapFrom(s => s.City)) + .ForMember(d => d.StatsBBuilderId, opt => opt.MapFrom(s => s.Id)) + .ForMember(d => d.Name, opt => opt.Ignore()) + .ForMember(d => d.StatsASpeed, opt => opt.Ignore()) + .ForMember(d => d.StatsAPower, opt => opt.Ignore()) + .ForMember(d => d.StatsARating, opt => opt.Ignore()) + .ForMember(d => d.StatsBSpeed, opt => opt.Ignore()) + .ForMember(d => d.StatsBPower, opt => opt.Ignore()) + .ForMember(d => d.StatsBRating, opt => opt.Ignore()); + }); + + config.AssertConfigurationIsValid(); + var mapper = config.CreateMapper(); + + //Arrange + Expression> selection = p => p.StatsABuilderCity; + + //Act + Expression> selectionMapped = mapper.MapExpression>>(selection); + List result = Players.Select(selectionMapped).ToList(); + + //Assert + Assert.Equal("Atlanta", result[0]); + Assert.Equal("p => p.StatsA.StatsBuilder.City", selectionMapped.ToString()); + } + + [Fact] + public void Map_member_init_without_include_members() + { + var config = new MapperConfiguration(cfg => + { + cfg.AddExpressionMapping(); + + cfg.CreateMap() + .ForMember(d => d.StatsABuilderId, opt => opt.Ignore()) + .ForMember(d => d.StatsABuilderCity, opt => opt.MapFrom(s => s.StatsA.StatsBuilder.City)) + .ForMember(d => d.StatsBBuilderCity, opt => opt.Ignore()) + .ForMember(d => d.StatsBBuilderId, opt => opt.Ignore()) + .ForMember(d => d.StatsAPower, opt => opt.MapFrom(s => s.StatsA.Power)) + .ForMember(d => d.StatsASpeed, opt => opt.MapFrom(s => s.StatsA.SpeedValue)) + .ForMember(d => d.StatsBPower, opt => opt.MapFrom(s => s.StatsB.Power)) + .ForMember(d => d.StatsBSpeed, opt => opt.MapFrom(s => s.StatsB.SpeedValue)); + }); + + config.AssertConfigurationIsValid(); + var mapper = config.CreateMapper(); + + //Arrange + Expression> selection = s => new PlayerModel { Name = s.Name, StatsASpeed = s.StatsASpeed, StatsAPower = s.StatsAPower, StatsARating = s.StatsARating }; + + //Act + Expression> selectionMapped = mapper.MapExpression>>(selection); + List result = Players.Select(selectionMapped).ToList(); + + //Assert + Assert.Equal("Jack", result[0].Name); + Assert.Equal(1, result[0].StatsA.Power); + Assert.Equal(2, result[0].StatsA.SpeedValue); + Assert.Equal(5, result[0].StatsA.Rating); + } + + [Fact] + public void Map_member_init_without_include_members_and_including_nested_members() + { + var config = new MapperConfiguration(cfg => + { + cfg.AddExpressionMapping(); + + cfg.CreateMap() + .ForMember(d => d.StatsABuilderId, opt => opt.MapFrom(s => s.StatsA.StatsBuilder.Id)) + .ForMember(d => d.StatsABuilderCity, opt => opt.MapFrom(s => s.StatsA.StatsBuilder.City)) + .ForMember(d => d.StatsAPower, opt => opt.MapFrom(s => s.StatsA.Power)) + .ForMember(d => d.StatsASpeed, opt => opt.MapFrom(s => s.StatsA.SpeedValue)) + .ForMember(d => d.StatsBBuilderId, opt => opt.MapFrom(s => s.StatsB.StatsBuilder.Id)) + .ForMember(d => d.StatsBBuilderCity, opt => opt.MapFrom(s => s.StatsB.StatsBuilder.City)) + .ForMember(d => d.StatsBPower, opt => opt.MapFrom(s => s.StatsB.Power)) + .ForMember(d => d.StatsBSpeed, opt => opt.MapFrom(s => s.StatsB.SpeedValue)); + }); + + config.AssertConfigurationIsValid(); + var mapper = config.CreateMapper(); + + //Arrange + Expression> selection = s => new PlayerModel + { + Name = s.Name, + StatsASpeed = s.StatsASpeed, + StatsAPower = s.StatsAPower, + StatsARating = s.StatsARating, + StatsABuilderId = s.StatsABuilderId, + StatsABuilderCity = s.StatsABuilderCity, + StatsBSpeed = s.StatsBSpeed, + StatsBPower = s.StatsBPower, + StatsBRating = s.StatsBRating, + StatsBBuilderId = s.StatsBBuilderId, + StatsBBuilderCity = s.StatsBBuilderCity + }; + + //Act + Expression> selectionMapped = mapper.MapExpression>>(selection); + List result = Players.Select(selectionMapped).ToList(); + + //Assert + Assert.Equal("Jack", result[0].Name); + Assert.Equal(1, result[0].StatsA.Power); + Assert.Equal(2, result[0].StatsA.SpeedValue); + Assert.Equal(5, result[0].StatsA.Rating); + Assert.Equal(1, result[0].StatsA.StatsBuilder.Id); + Assert.Equal("Atlanta", result[0].StatsA.StatsBuilder.City); + + Assert.Equal(2, result[0].StatsB.Power); + Assert.Equal(3, result[0].StatsB.SpeedValue); + Assert.Equal(7, result[0].StatsB.Rating); + Assert.Equal(3, result[0].StatsB.StatsBuilder.Id); + Assert.Equal("Columbia", result[0].StatsB.StatsBuilder.City); + } + + readonly IQueryable Players = new List + { + new Player + { + Name = "Jack", + StatsA = new Stats + { + Power = 1, + SpeedValue = 2, + Rating = 5, + StatsBuilder = new Builder + { + Id = 1, + City = "Atlanta" + } + }, + StatsB = new Stats + { + Power = 2, + SpeedValue = 3, + Rating = 7, + StatsBuilder = new Builder + { + Id = 3, + City = "Columbia" + } + } + }, + new Player + { + Name = "Jane", + StatsA = new Stats + { + Power = 1, + SpeedValue = 3, + Rating = 6, + StatsBuilder = new Builder + { + Id = 2, + City = "Charlotte" + } + }, + StatsB = new Stats + { + StatsBuilder = new Builder() + } + } + }.AsQueryable(); + + public class Player + { + public string Name { get; set; } + public Stats StatsA { get; set; } + public Stats StatsB { get; set; } + } + + public class Stats + { + public int SpeedValue { get; set; } + public int Power { get; set; } + public int Rating { get; set; } + public Builder StatsBuilder { get; set; } + } + + public class Builder + { + public int Id { get; set; } + public string City { get; set; } + } + + public class PlayerModel + { + public string Name { get; set; } + public int StatsASpeed { get; set; } + public int StatsAPower { get; set; } + public int StatsARating { get; set; } + public int StatsABuilderId { get; set; } + public string StatsABuilderCity { get; set; } + public int StatsBSpeed { get; set; } + public int StatsBPower { get; set; } + public int StatsBRating { get; set; } + public int StatsBBuilderId { get; set; } + public string StatsBBuilderCity { get; set; } + } + } +} From 442eeb51da6dd20194ffbf1a04826eb170a1e790 Mon Sep 17 00:00:00 2001 From: BlaiseD Date: Fri, 20 Nov 2020 11:54:51 -0500 Subject: [PATCH 2/2] Consolidate configs. --- ...MappingWithIncludeMembersConfigurations.cs | 220 +++++------------- 1 file changed, 63 insertions(+), 157 deletions(-) diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingWithIncludeMembersConfigurations.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingWithIncludeMembersConfigurations.cs index 7c21a41..79f825e 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingWithIncludeMembersConfigurations.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingWithIncludeMembersConfigurations.cs @@ -11,41 +11,16 @@ public class MappingWithIncludeMembersConfigurations [Fact] public void ShouldMapWhenParenIsConfiguredUsingIncludeMembers() { - var config = new MapperConfiguration(cfg => - { - cfg.CreateMap() - .IncludeMembers(p => p.StatsA/*, p => p.StatsB*/); - - cfg.CreateMap() - .ForMember(d => d.Name, opt => opt.Ignore()) - .ForMember(d => d.StatsARating, opt => opt.MapFrom(s => s.Rating)) - .ForMember(d => d.StatsBRating, opt => opt.MapFrom(s => s.Rating)) - .ForMember(d => d.StatsASpeed, opt => opt.MapFrom(s => s.SpeedValue)) - .ForMember(d => d.StatsBSpeed, opt => opt.MapFrom(s => s.SpeedValue)) - .ForMember(d => d.StatsAPower, opt => opt.MapFrom(s => s.Power)) - .ForMember(d => d.StatsBPower, opt => opt.MapFrom(s => s.Power)) - .IncludeMembers(p => p.StatsBuilder); - - cfg.CreateMap() - .ForMember(d => d.StatsABuilderCity, opt => opt.MapFrom(s => s.City)) - .ForMember(d => d.StatsABuilderId, opt => opt.MapFrom(s => s.Id)) - .ForMember(d => d.StatsBBuilderCity, opt => opt.MapFrom(s => s.City)) - .ForMember(d => d.StatsBBuilderId, opt => opt.MapFrom(s => s.Id)) - .ForMember(d => d.Name, opt => opt.Ignore()) - .ForMember(d => d.StatsASpeed, opt => opt.Ignore()) - .ForMember(d => d.StatsAPower, opt => opt.Ignore()) - .ForMember(d => d.StatsARating, opt => opt.Ignore()) - .ForMember(d => d.StatsBSpeed, opt => opt.Ignore()) - .ForMember(d => d.StatsBPower, opt => opt.Ignore()) - .ForMember(d => d.StatsBRating, opt => opt.Ignore()); - }); - + //Arrange + var config = GetConfigurationWiithIncludeMembers(); config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); + //Act List models = mapper.ProjectTo(Players).ToList(); - Assert.Equal("Jack", models[0].Name); + //Assert + Assert.Equal("Jack", models[0].Name); Assert.Equal(1, models[0].StatsAPower); Assert.Equal(2, models[0].StatsASpeed); Assert.Equal(5, models[0].StatsARating); @@ -62,35 +37,10 @@ public void ShouldMapWhenParenIsConfiguredUsingIncludeMembers() [Fact] public void Map_member_init_with_include_members() { - var config = new MapperConfiguration(cfg => - { - cfg.AddExpressionMapping(); - - cfg.CreateMap() - .ForMember(d => d.StatsABuilderCity, opt => opt.Ignore()) - .ForMember(d => d.StatsABuilderId, opt => opt.Ignore()) - .ForMember(d => d.StatsBBuilderCity, opt => opt.Ignore()) - .ForMember(d => d.StatsBBuilderId, opt => opt.Ignore()) - .IncludeMembers(p => p.StatsA); - - cfg.CreateMap() - .ForMember(d => d.Name, opt => opt.Ignore()) - .ForMember(d => d.StatsABuilderCity, opt => opt.Ignore()) - .ForMember(d => d.StatsARating, opt => opt.MapFrom(s => s.Rating)) - .ForMember(d => d.StatsABuilderId, opt => opt.Ignore()) - .ForMember(d => d.StatsBBuilderCity, opt => opt.Ignore()) - .ForMember(d => d.StatsBRating, opt => opt.Ignore()) - .ForMember(d => d.StatsBBuilderId, opt => opt.Ignore()) - .ForMember(d => d.StatsASpeed, opt => opt.MapFrom(s => s.SpeedValue)) - .ForMember(d => d.StatsBSpeed, opt => opt.MapFrom(s => s.SpeedValue)) - .ForMember(d => d.StatsAPower, opt => opt.MapFrom(s => s.Power)) - .ForMember(d => d.StatsBPower, opt => opt.MapFrom(s => s.Power)); - }); - + //Arrange + var config = GetConfigurationWiithIncludeMembers(); config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); - - //Arrange Expression> selection = s => new PlayerModel { Name = s.Name, StatsASpeed = s.StatsASpeed, StatsAPower = s.StatsAPower, StatsARating = s.StatsARating }; //Act @@ -107,41 +57,10 @@ public void Map_member_init_with_include_members() [Fact] public void Map_member_init_with_include_members_and_nested_include_members() { - var config = new MapperConfiguration(cfg => - { - cfg.AddExpressionMapping(); - - cfg.CreateMap() - .IncludeMembers(p => p.StatsA/*, p => p.StatsB*/); - - cfg.CreateMap() - .ForMember(d => d.Name, opt => opt.Ignore()) - .ForMember(d => d.StatsARating, opt => opt.MapFrom(s => s.Rating)) - .ForMember(d => d.StatsBRating, opt => opt.MapFrom(s => s.Rating)) - .ForMember(d => d.StatsASpeed, opt => opt.MapFrom(s => s.SpeedValue)) - .ForMember(d => d.StatsBSpeed, opt => opt.MapFrom(s => s.SpeedValue)) - .ForMember(d => d.StatsAPower, opt => opt.MapFrom(s => s.Power)) - .ForMember(d => d.StatsBPower, opt => opt.MapFrom(s => s.Power)) - .IncludeMembers(p => p.StatsBuilder); - - cfg.CreateMap() - .ForMember(d => d.StatsABuilderCity, opt => opt.MapFrom(s => s.City)) - .ForMember(d => d.StatsABuilderId, opt => opt.MapFrom(s => s.Id)) - .ForMember(d => d.StatsBBuilderCity, opt => opt.MapFrom(s => s.City)) - .ForMember(d => d.StatsBBuilderId, opt => opt.MapFrom(s => s.Id)) - .ForMember(d => d.Name, opt => opt.Ignore()) - .ForMember(d => d.StatsASpeed, opt => opt.Ignore()) - .ForMember(d => d.StatsAPower, opt => opt.Ignore()) - .ForMember(d => d.StatsARating, opt => opt.Ignore()) - .ForMember(d => d.StatsBSpeed, opt => opt.Ignore()) - .ForMember(d => d.StatsBPower, opt => opt.Ignore()) - .ForMember(d => d.StatsBRating, opt => opt.Ignore()); - }); - + //Arrange + var config = GetConfigurationWiithIncludeMembers(); config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); - - //Arrange Expression> selection = s => new PlayerModel { Name = s.Name, @@ -179,41 +98,10 @@ public void Map_member_init_with_include_members_and_nested_include_members() [Fact] public void Map_member_expression_with_include_members_and_nested_include_members() { - var config = new MapperConfiguration(cfg => - { - cfg.AddExpressionMapping(); - - cfg.CreateMap() - .IncludeMembers(p => p.StatsA); - - cfg.CreateMap() - .ForMember(d => d.Name, opt => opt.Ignore()) - .ForMember(d => d.StatsARating, opt => opt.MapFrom(s => s.Rating)) - .ForMember(d => d.StatsBRating, opt => opt.MapFrom(s => s.Rating)) - .ForMember(d => d.StatsASpeed, opt => opt.MapFrom(s => s.SpeedValue)) - .ForMember(d => d.StatsBSpeed, opt => opt.MapFrom(s => s.SpeedValue)) - .ForMember(d => d.StatsAPower, opt => opt.MapFrom(s => s.Power)) - .ForMember(d => d.StatsBPower, opt => opt.MapFrom(s => s.Power)) - .IncludeMembers(p => p.StatsBuilder); - - cfg.CreateMap() - .ForMember(d => d.StatsABuilderCity, opt => opt.MapFrom(s => s.City)) - .ForMember(d => d.StatsABuilderId, opt => opt.MapFrom(s => s.Id)) - .ForMember(d => d.StatsBBuilderCity, opt => opt.MapFrom(s => s.City)) - .ForMember(d => d.StatsBBuilderId, opt => opt.MapFrom(s => s.Id)) - .ForMember(d => d.Name, opt => opt.Ignore()) - .ForMember(d => d.StatsASpeed, opt => opt.Ignore()) - .ForMember(d => d.StatsAPower, opt => opt.Ignore()) - .ForMember(d => d.StatsARating, opt => opt.Ignore()) - .ForMember(d => d.StatsBSpeed, opt => opt.Ignore()) - .ForMember(d => d.StatsBPower, opt => opt.Ignore()) - .ForMember(d => d.StatsBRating, opt => opt.Ignore()); - }); - + //Arrange + var config = GetConfigurationWiithIncludeMembers(); config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); - - //Arrange Expression> selection = p => p.StatsABuilderCity; //Act @@ -228,25 +116,10 @@ public void Map_member_expression_with_include_members_and_nested_include_member [Fact] public void Map_member_init_without_include_members() { - var config = new MapperConfiguration(cfg => - { - cfg.AddExpressionMapping(); - - cfg.CreateMap() - .ForMember(d => d.StatsABuilderId, opt => opt.Ignore()) - .ForMember(d => d.StatsABuilderCity, opt => opt.MapFrom(s => s.StatsA.StatsBuilder.City)) - .ForMember(d => d.StatsBBuilderCity, opt => opt.Ignore()) - .ForMember(d => d.StatsBBuilderId, opt => opt.Ignore()) - .ForMember(d => d.StatsAPower, opt => opt.MapFrom(s => s.StatsA.Power)) - .ForMember(d => d.StatsASpeed, opt => opt.MapFrom(s => s.StatsA.SpeedValue)) - .ForMember(d => d.StatsBPower, opt => opt.MapFrom(s => s.StatsB.Power)) - .ForMember(d => d.StatsBSpeed, opt => opt.MapFrom(s => s.StatsB.SpeedValue)); - }); - + //Arrange + var config = GetConfigurationWiithoutIncludeMembers(); config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); - - //Arrange Expression> selection = s => new PlayerModel { Name = s.Name, StatsASpeed = s.StatsASpeed, StatsAPower = s.StatsAPower, StatsARating = s.StatsARating }; //Act @@ -263,25 +136,10 @@ public void Map_member_init_without_include_members() [Fact] public void Map_member_init_without_include_members_and_including_nested_members() { - var config = new MapperConfiguration(cfg => - { - cfg.AddExpressionMapping(); - - cfg.CreateMap() - .ForMember(d => d.StatsABuilderId, opt => opt.MapFrom(s => s.StatsA.StatsBuilder.Id)) - .ForMember(d => d.StatsABuilderCity, opt => opt.MapFrom(s => s.StatsA.StatsBuilder.City)) - .ForMember(d => d.StatsAPower, opt => opt.MapFrom(s => s.StatsA.Power)) - .ForMember(d => d.StatsASpeed, opt => opt.MapFrom(s => s.StatsA.SpeedValue)) - .ForMember(d => d.StatsBBuilderId, opt => opt.MapFrom(s => s.StatsB.StatsBuilder.Id)) - .ForMember(d => d.StatsBBuilderCity, opt => opt.MapFrom(s => s.StatsB.StatsBuilder.City)) - .ForMember(d => d.StatsBPower, opt => opt.MapFrom(s => s.StatsB.Power)) - .ForMember(d => d.StatsBSpeed, opt => opt.MapFrom(s => s.StatsB.SpeedValue)); - }); - + //Arrange + var config = GetConfigurationWiithoutIncludeMembers(); config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); - - //Arrange Expression> selection = s => new PlayerModel { Name = s.Name, @@ -316,6 +174,54 @@ public void Map_member_init_without_include_members_and_including_nested_members Assert.Equal("Columbia", result[0].StatsB.StatsBuilder.City); } + MapperConfiguration GetConfigurationWiithoutIncludeMembers() + => new MapperConfiguration(cfg => + { + cfg.AddExpressionMapping(); + + cfg.CreateMap() + .ForMember(d => d.StatsABuilderId, opt => opt.MapFrom(s => s.StatsA.StatsBuilder.Id)) + .ForMember(d => d.StatsABuilderCity, opt => opt.MapFrom(s => s.StatsA.StatsBuilder.City)) + .ForMember(d => d.StatsAPower, opt => opt.MapFrom(s => s.StatsA.Power)) + .ForMember(d => d.StatsASpeed, opt => opt.MapFrom(s => s.StatsA.SpeedValue)) + .ForMember(d => d.StatsBBuilderId, opt => opt.MapFrom(s => s.StatsB.StatsBuilder.Id)) + .ForMember(d => d.StatsBBuilderCity, opt => opt.MapFrom(s => s.StatsB.StatsBuilder.City)) + .ForMember(d => d.StatsBPower, opt => opt.MapFrom(s => s.StatsB.Power)) + .ForMember(d => d.StatsBSpeed, opt => opt.MapFrom(s => s.StatsB.SpeedValue)); + }); + + MapperConfiguration GetConfigurationWiithIncludeMembers() + => new MapperConfiguration(cfg => + { + cfg.AddExpressionMapping(); + + cfg.CreateMap() + .IncludeMembers(p => p.StatsA); + + cfg.CreateMap() + .ForMember(d => d.Name, opt => opt.Ignore()) + .ForMember(d => d.StatsARating, opt => opt.MapFrom(s => s.Rating)) + .ForMember(d => d.StatsBRating, opt => opt.MapFrom(s => s.Rating)) + .ForMember(d => d.StatsASpeed, opt => opt.MapFrom(s => s.SpeedValue)) + .ForMember(d => d.StatsBSpeed, opt => opt.MapFrom(s => s.SpeedValue)) + .ForMember(d => d.StatsAPower, opt => opt.MapFrom(s => s.Power)) + .ForMember(d => d.StatsBPower, opt => opt.MapFrom(s => s.Power)) + .IncludeMembers(p => p.StatsBuilder); + + cfg.CreateMap() + .ForMember(d => d.StatsABuilderCity, opt => opt.MapFrom(s => s.City)) + .ForMember(d => d.StatsABuilderId, opt => opt.MapFrom(s => s.Id)) + .ForMember(d => d.StatsBBuilderCity, opt => opt.MapFrom(s => s.City)) + .ForMember(d => d.StatsBBuilderId, opt => opt.MapFrom(s => s.Id)) + .ForMember(d => d.Name, opt => opt.Ignore()) + .ForMember(d => d.StatsASpeed, opt => opt.Ignore()) + .ForMember(d => d.StatsAPower, opt => opt.Ignore()) + .ForMember(d => d.StatsARating, opt => opt.Ignore()) + .ForMember(d => d.StatsBSpeed, opt => opt.Ignore()) + .ForMember(d => d.StatsBPower, opt => opt.Ignore()) + .ForMember(d => d.StatsBRating, opt => opt.Ignore()); + }); + readonly IQueryable Players = new List { new Player