diff --git a/Directory.Build.props b/Directory.Build.props index 409fef6..9d6b55a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ Jimmy Bogard latest - 3.1.2 + 4.0.0-preview01 true $(NoWarn);1701;1702;1591 diff --git a/src/AutoMapper.Extensions.ExpressionMapping/AutoMapper.Extensions.ExpressionMapping.csproj b/src/AutoMapper.Extensions.ExpressionMapping/AutoMapper.Extensions.ExpressionMapping.csproj index dffca86..7c1cbee 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/AutoMapper.Extensions.ExpressionMapping.csproj +++ b/src/AutoMapper.Extensions.ExpressionMapping/AutoMapper.Extensions.ExpressionMapping.csproj @@ -17,7 +17,7 @@ - + diff --git a/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs b/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs index 1a373f2..e42487e 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs @@ -4,15 +4,16 @@ using System.Linq.Expressions; using System.Reflection; using AutoMapper.Extensions.ExpressionMapping; +using AutoMapper.Internal; using static System.Linq.Expressions.Expression; namespace AutoMapper.Mappers { public class ExpressionMapper : IObjectMapper { - private static TDestination Map(TSource expression, ResolutionContext context) + private static TDestination Map(TSource expression, ResolutionContext context, IConfigurationProvider configurationProvider) where TSource : LambdaExpression - where TDestination : LambdaExpression => context.Mapper.MapExpression(expression); + where TDestination : LambdaExpression => configurationProvider.CreateMapper().MapExpression(expression); private static readonly MethodInfo MapMethodInfo = typeof(ExpressionMapper).GetDeclaredMethod(nameof(Map)); @@ -25,7 +26,8 @@ public Expression MapExpression(IConfigurationProvider configurationProvider, Pr Call(null, MapMethodInfo.MakeGenericMethod(sourceExpression.Type, destExpression.Type), sourceExpression, - contextExpression); + contextExpression, + Constant(configurationProvider)); internal class MappingVisitor : ExpressionVisitor { diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Extensions/VisitorExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/Extensions/VisitorExtensions.cs index 9bb8c91..667d366 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/Extensions/VisitorExtensions.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/Extensions/VisitorExtensions.cs @@ -92,6 +92,9 @@ public static Expression ConvertTypeIfNecessary(this Expression expression, Type return expression; } + public static MemberExpression GetMemberExpression(this LambdaExpression expr) + => expr.Body.GetUnconvertedMemberExpression() as MemberExpression; + /// /// Returns the ParameterExpression for the LINQ parameter. /// @@ -140,10 +143,15 @@ public static ParameterExpression GetParameterExpression(this Expression express /// /// /// - public static Expression GetBaseOfMemberExpression(this MemberExpression expression) - => expression.Expression.NodeType == ExpressionType.MemberAccess - ? GetBaseOfMemberExpression((MemberExpression)expression.Expression) - : expression.Expression; + public static Expression GetBaseOfMemberExpression(this MemberExpression expression) + { + if (expression.Expression == null) + return null; + + return expression.Expression.NodeType == ExpressionType.MemberAccess + ? GetBaseOfMemberExpression((MemberExpression)expression.Expression) + : expression.Expression; + } /// /// Adds member expressions to an existing expression. diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs b/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs index 1060192..faf2293 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs @@ -1,3 +1,5 @@ +using AutoMapper.Internal; +using AutoMapper.Mappers; using System; using System.Collections; using System.Collections.Generic; @@ -5,15 +7,12 @@ using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; -using AutoMapper.Mappers; -using AutoMapper.Mappers.Internal; -using AutoMapper.QueryableExtensions; namespace AutoMapper.Extensions.ExpressionMapping.Impl { + using static Expression; using MemberPaths = IEnumerable>; using ParameterBag = IDictionary; - using static Expression; public class SourceInjectedQueryProvider : IQueryProvider { diff --git a/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs index 26a335c..61b14d8 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs @@ -1,11 +1,10 @@ -using System; +using AutoMapper.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 { @@ -36,10 +35,22 @@ private static TDestDelegate _MapExpression(this where TDestDelegate : LambdaExpression => mapper.MapExpression(expression); - private static MethodInfo GetMapExpressionMethod(this string methodName) => typeof(MapperExtensions).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static); + public static object MapObject(this IMapper mapper, object obj, Type sourceType, Type destType) + => "_MapObject".GetMapObjectMethod().MakeGenericMethod + ( + sourceType, + destType + ).Invoke(null, new object[] { mapper, obj }); + + private static TDest _MapObject(IMapper mapper, TSource source) + => mapper.Map(source); + + private static MethodInfo GetMapObjectMethod(this string methodName) + => typeof(MapperExtensions).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static); + /// /// Maps an expression given a dictionary of types where the source type is the key and the destination type is the value. /// diff --git a/src/AutoMapper.Extensions.ExpressionMapping/PrimitiveExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/PrimitiveExtensions.cs deleted file mode 100644 index cb8ad16..0000000 --- a/src/AutoMapper.Extensions.ExpressionMapping/PrimitiveExtensions.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using AutoMapper.Configuration.Internal; - -namespace AutoMapper.Extensions.ExpressionMapping -{ - internal static class PrimitiveExtensions - { - public static bool IsSetType(this Type type) - => type.ImplementsGenericInterface(typeof(ISet<>)); - - public static TValue GetOrDefault(this IDictionary dictionary, TKey key) - => PrimitiveHelper.GetOrDefault(dictionary, key); - - public static MethodInfo GetInheritedMethod(this Type type, string name) - => PrimitiveHelper.GetInheritedMethod(type, name); - - public static MemberInfo GetFieldOrProperty(this Type type, string name) - => PrimitiveHelper.GetFieldOrProperty(type, name); - - public static bool IsNullableType(this Type type) - => PrimitiveHelper.IsNullableType(type); - - public static Type GetTypeOfNullable(this Type type) - => PrimitiveHelper.GetTypeOfNullable(type); - - public static bool IsCollectionType(this Type type) - => PrimitiveHelper.IsCollectionType(type); - - public static bool IsEnumerableType(this Type type) - => PrimitiveHelper.IsEnumerableType(type); - - public static bool IsQueryableType(this Type type) - => PrimitiveHelper.IsQueryableType(type); - - public static bool IsListType(this Type type) - => PrimitiveHelper.IsListType(type); - - public static bool IsDictionaryType(this Type type) - => PrimitiveHelper.IsDictionaryType(type); - - public static bool ImplementsGenericInterface(this Type type, Type interfaceType) - => PrimitiveHelper.ImplementsGenericInterface(type, interfaceType); - - public static bool IsGenericType(this Type type, Type genericType) - => PrimitiveHelper.IsGenericType(type, genericType); - - public static Type GetIEnumerableType(this Type type) - => PrimitiveHelper.GetIEnumerableType(type); - - public static Type GetDictionaryType(this Type type) - => PrimitiveHelper.GetDictionaryType(type); - - public static Type GetGenericInterface(this Type type, Type genericInterface) - => PrimitiveHelper.GetGenericInterface(type, genericInterface); - - public static Type GetGenericElementType(this Type type) - => PrimitiveHelper.GetGenericElementType(type); - } -} \ No newline at end of file diff --git a/src/AutoMapper.Extensions.ExpressionMapping/ReflectionExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/ReflectionExtensions.cs index 26e2612..449bc60 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/ReflectionExtensions.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/ReflectionExtensions.cs @@ -17,12 +17,6 @@ public static object GetDefaultValue(this ParameterInfo parameter) public static object MapMember(this ResolutionContext context, MemberInfo member, object value, object destination = null) => ReflectionHelper.MapMember(context, member, value, destination); - public static bool IsDynamic(this object obj) - => ReflectionHelper.IsDynamic(obj); - - public static bool IsDynamic(this Type type) - => ReflectionHelper.IsDynamic(type); - public static void SetMemberValue(this MemberInfo propertyOrField, object target, object value) => ReflectionHelper.SetMemberValue(propertyOrField, target, value); @@ -38,27 +32,12 @@ public static MemberPaths GetMemberPaths(Type type, string[] membersToExpand) => public static MemberPaths GetMemberPaths(Expression>[] membersToExpand) => membersToExpand.Select(expr => MemberVisitor.GetMemberPath(expr)); - public static MemberInfo GetFieldOrProperty(this LambdaExpression expression) - => ReflectionHelper.GetFieldOrProperty(expression); - public static MemberInfo FindProperty(LambdaExpression lambdaExpression) => ReflectionHelper.FindProperty(lambdaExpression); public static Type GetMemberType(this MemberInfo memberInfo) => ReflectionHelper.GetMemberType(memberInfo); - /// - /// if targetType is oldType, method will return newType - /// if targetType is not oldType, method will return targetType - /// if targetType is generic type with oldType arguments, method will replace all oldType arguments on newType - /// - /// - /// - /// - /// - public static Type ReplaceItemType(this Type targetType, Type oldType, Type newType) - => ReflectionHelper.ReplaceItemType(targetType, oldType, newType); - public static IEnumerable GetDefinedTypes(this Assembly assembly) => assembly.DefinedTypes; diff --git a/src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs index f0c2f06..f6d90c9 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs @@ -1,4 +1,4 @@ -using AutoMapper.Configuration.Internal; +using AutoMapper.Internal; using System; using System.Collections.Generic; using System.Linq; @@ -124,7 +124,7 @@ public static bool IsNotPublic(this ConstructorInfo constructorInfo) => construc public static bool IsLiteralType(this Type type) { - if (PrimitiveHelper.IsNullableType(type)) + if (type.IsNullableType()) type = Nullable.GetUnderlyingType(type); return LiteralTypes.Contains(type); @@ -165,5 +165,11 @@ public static bool IsLiteralType(this Type type) public static MethodInfo GetSetMethod(this PropertyInfo propertyInfo) => propertyInfo.SetMethod; public static FieldInfo GetField(this Type type, string name) => type.GetRuntimeField(name); + + public static bool IsQueryableType(this Type type) + => typeof(IQueryable).IsAssignableFrom(type); + + public static Type GetGenericElementType(this Type type) + => type.HasElementType ? type.GetElementType() : type.GetTypeInfo().GenericTypeArguments[0]; } } diff --git a/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs b/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs index 4c5365e..821e7e5 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs @@ -38,11 +38,40 @@ protected override Expression VisitParameter(ParameterExpression node) return !pair.Equals(default(KeyValuePair)) ? pair.Value.NewParameter : base.VisitParameter(node); } + private object GetConstantValue(object constantObject, string fullName, Type parentType) + { + return fullName.Split('.').Aggregate(constantObject, (parent, memberName) => + { + MemberInfo memberInfo = parentType.GetFieldOrProperty(memberName); + parentType = memberInfo.GetMemberType(); + return memberInfo.GetMemberValue(parent); + }); + } + protected override Expression VisitMember(MemberExpression node) { var parameterExpression = node.GetParameterExpression(); if (parameterExpression == null) + { + var baseExpression = node.GetBaseOfMemberExpression(); + if (baseExpression?.NodeType == ExpressionType.Constant) + { + return this.Visit + ( + Expression.Constant + ( + GetConstantValue + ( + ((ConstantExpression)baseExpression).Value, + node.GetPropertyFullName(), + baseExpression.Type + ) + ) + ); + } + return base.VisitMember(node); + } InfoDictionary.Add(parameterExpression, TypeMappings); return GetMappedMemberExpression(node.GetBaseOfMemberExpression(), new List()); @@ -145,19 +174,39 @@ 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.Select - ( - binding => - { - Expression bindingExpression = ((MemberAssignment)binding).Expression; - return DoBind + //IEnumerable bindings = node.Bindings.Select + //( + // binding => + // { + // Expression bindingExpression = ((MemberAssignment)binding).Expression; + // return DoBind + // ( + // GetSourceMember(typeMap.GetPropertyMapByDestinationProperty(binding.Member.Name)), + // bindingExpression, + // this.Visit(bindingExpression) + // ); + // } + //); + + 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; + + Expression bindingExpression = ((MemberAssignment)binding).Expression; + list.Add + ( + DoBind ( - typeMap.GetPropertyMapByDestinationProperty(binding.Member.Name), + GetSourceMember(propertyMap), bindingExpression, this.Visit(bindingExpression) - ); - } - ); + ) + ); + + return list; + }); return Expression.MemberInit(Expression.New(newType), bindings); } @@ -165,13 +214,18 @@ protected override Expression VisitMemberInit(MemberInitExpression node) return base.VisitMemberInit(node); } - private MemberBinding DoBind(PropertyMap propertyMap, Expression initial, Expression mapped) + private MemberBinding DoBind(MemberInfo sourceMember, Expression initial, Expression mapped) { - mapped = mapped.ConvertTypeIfNecessary(propertyMap.SourceMember.GetMemberType()); + mapped = mapped.ConvertTypeIfNecessary(sourceMember.GetMemberType()); this.TypeMappings.AddTypeMapping(ConfigurationProvider, initial.Type, mapped.Type); - return Expression.Bind(propertyMap.SourceMember, mapped); + return Expression.Bind(sourceMember, mapped); } + private MemberInfo GetSourceMember(PropertyMap propertyMap) + => propertyMap.CustomMapExpression != null + ? propertyMap.CustomMapExpression.GetMemberExpression()?.Member + : propertyMap.SourceMember; + protected override Expression VisitBinary(BinaryExpression node) { return DoVisitBinary(this.Visit(node.Left), this.Visit(node.Right), this.Visit(node.Conversion)); @@ -261,7 +315,9 @@ Expression DoVisitUnary(Expression updated) protected override Expression VisitConstant(ConstantExpression node) { if (this.TypeMappings.TryGetValue(node.Type, out Type newType)) - return base.VisitConstant(Expression.Constant(Mapper.Map(node.Value, node.Type, newType), newType)); + return base.VisitConstant(Expression.Constant(Mapper.MapObject(node.Value, node.Type, newType), newType)); + //Issue 3455 (Non-Generic Mapper.Map failing for structs in v10) + //return base.VisitConstant(Expression.Constant(Mapper.Map(node.Value, node.Type, newType), newType)); return base.VisitConstant(node); } diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/Impl/SourceInjectedQuery.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/Impl/SourceInjectedQuery.cs index 0055e5e..604344f 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/Impl/SourceInjectedQuery.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/Impl/SourceInjectedQuery.cs @@ -138,7 +138,7 @@ public void Shoud_support_enumerable_return_type() .OrderBy(s => s.DestValue).SkipWhile(d => d.DestValue < 7).Take(1) .OrderByDescending(s => s.DestValue).Select(s => s.Strings); - result.First().Count().ShouldBe(0); + result.First().ShouldBe(null); } diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.Structs.Tests.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.Structs.Tests.cs index b95cf6c..fddcd56 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.Structs.Tests.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.Structs.Tests.cs @@ -34,6 +34,8 @@ public void Can_map_value_types_constants_with_instance_methods() //Act var output1 = source.AsQueryable().GetItems(mapper, q => q.Truck.Equals(default(TruckModel))); var output2 = source.AsQueryable().Query(mapper, q => q.First()); + + //Assert output1.First().Truck.ShouldBe(default); output2.Truck.ShouldBe(default); } @@ -63,6 +65,7 @@ public void Can_convert_return_type() var output1 = source.AsQueryable().GetItems(mapper, q => q.Truck.Year == 2000).Select(g => g.Truck); var output2 = source.AsQueryable().Query(mapper, q => q.Select(g => g.Truck).First()); + //Assert output1.First().Year.ShouldBe(2000); output2.Year.ShouldBe(2000); } @@ -70,6 +73,7 @@ public void Can_convert_return_type() [Fact] public void Replace_operator_when_operands_change() { + // Arrange var config = new MapperConfiguration(cfg => { cfg.AddExpressionMapping(); cfg.CreateMap().ReverseMap(); @@ -79,7 +83,87 @@ public void Replace_operator_when_operands_change() Expression> expression = x => x == default(Source); + //Act var mapped = mapper.MapExpression>>(expression); + + //Assert + Assert.NotNull(mapped); + } + + [Fact] + public void Can_map_local_variable_in_filter() + { + // Arrange + var config = new MapperConfiguration(c => + { + c.CreateMap() + .ReverseMap() + .ForMember(d => d.Color, opt => opt.MapFrom(s => s.Truck.Color)); + c.CreateMap() + .ReverseMap(); + }); + + config.AssertConfigurationIsValid(); + var mapper = config.CreateMapper(); + Truck truck = new Truck { Color = "Red", Year = 1999 }; + Expression> filter = m => m.Truck == truck; + + //Act + var mappedfilter = mapper.MapExpression>>(filter); + + //Assert + Assert.NotNull(mappedfilter); + } + + [Fact] + public void Can_map_child_property_of_local_variable_in_filter() + { + // Arrange + var config = new MapperConfiguration(c => + { + c.CreateMap() + .ReverseMap() + .ForMember(d => d.Color, opt => opt.MapFrom(s => s.Truck.Color)); + c.CreateMap() + .ReverseMap(); + }); + + config.AssertConfigurationIsValid(); + var mapper = config.CreateMapper(); + Garage garage = new Garage { Truck = new Truck { Color = "Red", Year = 1999 } }; + Expression> filter = m => m.Truck == garage.Truck; + + //Act + var mappedfilter = mapper.MapExpression>>(filter); + + //Assert + Assert.NotNull(mappedfilter); + } + + [Fact] + public void Can_map_listeral_child_property_of_local_variable_in_filter() + { + // Arrange + var config = new MapperConfiguration(c => + { + c.CreateMap() + .ReverseMap() + .ForMember(d => d.Color, opt => opt.MapFrom(s => s.Truck.Color)); + c.CreateMap() + .ReverseMap(); + }); + + config.AssertConfigurationIsValid(); + var mapper = config.CreateMapper(); + + GarageModel garage = new GarageModel { Color = "Blue", Truck = new TruckModel { Color = "Red", Year = 1999 } }; + Expression> filter = m => m.Color == garage.Color; + + //Act + var mappedfilter = mapper.MapExpression>>(filter); + + //Assert + Assert.NotNull(mappedfilter); } } @@ -238,12 +322,54 @@ public struct Truck { public string Color { get; set; } public int Year { get; set; } + + public static bool operator ==(Truck m1, Truck m2) + { + return m1.Year == m2.Year && m1.Color == m2.Color; + } + public static bool operator !=(Truck m1, Truck m2) + { + return !(m1 == m2); + } + public override bool Equals(object obj) + { + if (obj is Truck mb) + { + return this == mb; + } + return false; + } + public override int GetHashCode() + { + return Year.GetHashCode(); + } } public struct TruckModel { public string Color { get; set; } public int Year { get; set; } + + public static bool operator ==(TruckModel m1, TruckModel m2) + { + return m1.Year == m2.Year && m1.Color == m2.Color; + } + public static bool operator !=(TruckModel m1, TruckModel m2) + { + return !(m1 == m2); + } + public override bool Equals(object obj) + { + if (obj is TruckModel mb) + { + return this == mb; + } + return false; + } + public override int GetHashCode() + { + return Year.GetHashCode(); + } } static class Extensions diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapperTests.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapperTests.cs index 2201769..54bc36c 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapperTests.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapperTests.cs @@ -1,14 +1,13 @@ -using System; +using Shouldly; +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using Shouldly; using Xunit; -using AutoMapper; namespace AutoMapper.Extensions.ExpressionMapping.UnitTests { - using Configuration.Internal; + using AutoMapper.Internal; public class XpressionMapperTests { @@ -209,9 +208,9 @@ public void Map__object_including_child_and_grandchild_with_conditional_filter() { //Arrange ParameterExpression userParam = Expression.Parameter(typeof(UserModel), "s"); - MemberExpression accountModelProperty = Expression.MakeMemberAccess(userParam, PrimitiveHelper.GetFieldOrProperty(typeof(UserModel), "AccountModel")); - MemberExpression branchModelProperty = Expression.MakeMemberAccess(accountModelProperty, PrimitiveHelper.GetFieldOrProperty(typeof(AccountModel), "Branch")); - MemberExpression nameProperty = Expression.MakeMemberAccess(branchModelProperty, PrimitiveHelper.GetFieldOrProperty(typeof(BranchModel), "Name")); + MemberExpression accountModelProperty = Expression.MakeMemberAccess(userParam, TypeExtensions.GetFieldOrProperty(typeof(UserModel), "AccountModel")); + MemberExpression branchModelProperty = Expression.MakeMemberAccess(accountModelProperty, TypeExtensions.GetFieldOrProperty(typeof(AccountModel), "Branch")); + MemberExpression nameProperty = Expression.MakeMemberAccess(branchModelProperty, TypeExtensions.GetFieldOrProperty(typeof(BranchModel), "Name")); //{s => (IIF((IIF((s.AccountModel == null), null, s.AccountModel.Branch) == null), null, s.AccountModel.Branch.Name) == "Leeds")} Expression> selection = Expression.Lambda> @@ -255,7 +254,7 @@ public void Map_object_when_null_values_are_typed() //Arrange //Expression> selection = s => s != null && s.AccountModel != null && s.AccountModel.Bal > 555.20; ParameterExpression userParam = Expression.Parameter(typeof(UserModel), "s"); - MemberExpression accountModelProperty = Expression.MakeMemberAccess(userParam, PrimitiveHelper.GetFieldOrProperty(typeof(UserModel), "AccountModel")); + MemberExpression accountModelProperty = Expression.MakeMemberAccess(userParam, TypeExtensions.GetFieldOrProperty(typeof(UserModel), "AccountModel")); Expression> selection = Expression.Lambda> ( Expression.AndAlso @@ -267,7 +266,7 @@ public void Map_object_when_null_values_are_typed() ), Expression.GreaterThan ( - Expression.MakeMemberAccess(accountModelProperty, PrimitiveHelper.GetFieldOrProperty(typeof(AccountModel), "Bal")), + Expression.MakeMemberAccess(accountModelProperty, TypeExtensions.GetFieldOrProperty(typeof(AccountModel), "Bal")), Expression.Constant(555.20, typeof(double)) ) ),