From ba01164ef13086fc24da43107d7f3a97dff852af Mon Sep 17 00:00:00 2001 From: Blaise Taylor Date: Thu, 19 Feb 2026 15:08:15 -0500 Subject: [PATCH 1/5] Unused methods removed. --- .../ExpressionExtensions.cs | 24 --- .../Extensions/VisitorExtensions.cs | 75 ++------- .../FindMemberExpressionsVisitor.cs | 61 -------- .../MapIncludesVisitor.cs | 81 ---------- .../MapperExtensions.cs | 137 ++--------------- .../ReflectionExtensions.cs | 18 --- .../TypeExtensions.cs | 88 ----------- .../XpressionMapperVisitor.cs | 11 +- ...ensions.ExpressionMapping.UnitTests.csproj | 8 +- .../XpressionMapper.ForPath.Tests.cs | 47 ------ .../XpressionMapperTests.cs | 142 ------------------ 11 files changed, 28 insertions(+), 664 deletions(-) delete mode 100644 src/AutoMapper.Extensions.ExpressionMapping/FindMemberExpressionsVisitor.cs delete mode 100644 src/AutoMapper.Extensions.ExpressionMapping/MapIncludesVisitor.cs diff --git a/src/AutoMapper.Extensions.ExpressionMapping/ExpressionExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/ExpressionExtensions.cs index 3270328..daa4108 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/ExpressionExtensions.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/ExpressionExtensions.cs @@ -14,30 +14,6 @@ internal static class ExpressionExtensions { public static Expression MemberAccesses(this IEnumerable members, Expression obj) => members.Aggregate(obj, (expression, member) => MakeMemberAccess(expression, member)); - - public static IEnumerable GetMembers(this Expression expression) - { - var memberExpression = expression as MemberExpression; - if(memberExpression == null) - { - return new MemberExpression[0]; - } - return memberExpression.GetMembers(); - } - - public static IEnumerable GetMembers(this MemberExpression expression) - { - while(expression != null) - { - yield return expression; - expression = expression.Expression as MemberExpression; - } - } - - public static bool IsMemberPath(this LambdaExpression exp) - { - return exp.Body.GetMembers().LastOrDefault()?.Expression == exp.Parameters.First(); - } } internal static class ExpressionHelpers diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Extensions/VisitorExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/Extensions/VisitorExtensions.cs index f876a97..67f9ad9 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/Extensions/VisitorExtensions.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/Extensions/VisitorExtensions.cs @@ -11,37 +11,6 @@ namespace AutoMapper.Extensions.ExpressionMapping.Extensions { internal static class VisitorExtensions { - /// - /// Returns true if the expression is a direct or descendant member expression of the parameter. - /// - /// - /// - public static bool IsMemberExpression(this Expression expression) - { - if (expression.NodeType == ExpressionType.MemberAccess) - { - var memberExpression = (MemberExpression)expression; - return IsMemberOrParameterExpression(memberExpression.Expression); - } - - return false; - } - - private static bool IsMemberOrParameterExpression(Expression expression) - { - //the node represents parameter of the expression - switch (expression.NodeType) - { - case ExpressionType.Parameter: - return true; - case ExpressionType.MemberAccess: - var memberExpression = (MemberExpression)expression; - return IsMemberOrParameterExpression(memberExpression.Expression); - } - - return false; - } - /// /// Returns the fully qualified name of the member starting with the immediate child member of the parameter /// @@ -70,15 +39,11 @@ public static string GetPropertyFullName(this Expression expression) public static Expression GetUnconvertedExpression(this Expression expression) { - switch (expression.NodeType) + return expression.NodeType switch { - case ExpressionType.Convert: - case ExpressionType.ConvertChecked: - case ExpressionType.TypeAs: - return ((UnaryExpression)expression).Operand.GetUnconvertedExpression(); - default: - return expression; - } + ExpressionType.Convert or ExpressionType.ConvertChecked or ExpressionType.TypeAs => ((UnaryExpression)expression).Operand.GetUnconvertedExpression(), + _ => expression, + }; } public static Expression ConvertTypeIfNecessary(this Expression expression, Type memberType) @@ -136,9 +101,7 @@ public static ParameterExpression GetParameterExpression(this Expression express if (isExtension && parentExpression == null && methodExpression.Arguments.Count > 0) parentExpression = methodExpression.Arguments[0];//Method is an extension method based on the type of methodExpression.Arguments[0]. - return isExtension && parentExpression == null && methodExpression.Arguments.Count > 0 - ? GetParameterExpression(methodExpression.Arguments[0]) - : GetParameterExpression(parentExpression); + return GetParameterExpression(parentExpression); } return null; @@ -177,33 +140,19 @@ public static string GetMemberFullName(this LambdaExpression expr) { if (expr.Body.NodeType == ExpressionType.Parameter) return string.Empty; - - MemberExpression me; - switch (expr.Body.NodeType) + MemberExpression me = expr.Body.NodeType switch { - case ExpressionType.Convert: - case ExpressionType.ConvertChecked: - case ExpressionType.TypeAs: - me = expr.Body.GetUnconvertedExpression() as MemberExpression; - break; - default: - me = expr.Body as MemberExpression; - break; - } - + ExpressionType.Convert or ExpressionType.ConvertChecked or ExpressionType.TypeAs => expr.Body.GetUnconvertedExpression() as MemberExpression, + _ => expr.Body as MemberExpression, + }; return me.GetPropertyFullName(); } /// - /// Returns the underlying type typeof(T) when the type implements IEnumerable. + /// Determines whether the specified type is an enumeration type. /// - /// - /// - public static List GetUnderlyingGenericTypes(this Type type) => - type == null || !type.GetTypeInfo().IsGenericType - ? new List() - : type.GetGenericArguments().ToList(); - + /// The type to evaluate. This can be a nullable type, in which case the underlying type is checked. + /// true if the specified type is an enumeration; otherwise, false. public static bool IsEnumType(this Type type) { if (type.IsNullableType()) diff --git a/src/AutoMapper.Extensions.ExpressionMapping/FindMemberExpressionsVisitor.cs b/src/AutoMapper.Extensions.ExpressionMapping/FindMemberExpressionsVisitor.cs deleted file mode 100644 index fe1543e..0000000 --- a/src/AutoMapper.Extensions.ExpressionMapping/FindMemberExpressionsVisitor.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Linq.Expressions; -using AutoMapper.Internal; -using AutoMapper.Extensions.ExpressionMapping.Extensions; - -namespace AutoMapper.Extensions.ExpressionMapping -{ - internal class FindMemberExpressionsVisitor : ExpressionVisitor - { - internal FindMemberExpressionsVisitor(Expression newParentExpression) => _newParentExpression = newParentExpression; - - private readonly Expression _newParentExpression; - private readonly List _memberExpressions = new List(); - - public MemberExpression Result - { - get - { - const string period = "."; - var fullNamesGrouped = _memberExpressions.Select(m => m.GetPropertyFullName()) - .GroupBy(n => n) - .Select(grp => grp.Key) - .OrderBy(a => a.Length).ToList(); - - var member = fullNamesGrouped.Aggregate(string.Empty, (result, next) => - { - if (string.IsNullOrEmpty(result) || next.Contains(result)) - result = next; - else throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, - Properties.Resources.includeExpressionTooComplex, - string.Concat(_newParentExpression.Type.Name, period, result), - string.Concat(_newParentExpression.Type.Name, period, next))); - - return result; - }); - - return ExpressionHelpers.MemberAccesses(member, _newParentExpression); - } - } - - protected override Expression VisitMember(MemberExpression node) - { - var parameterExpression = node.GetParameterExpression(); - var sType = parameterExpression?.Type; - if (sType != null && _newParentExpression.Type == sType && node.IsMemberExpression()) - { - if (node.Expression.NodeType == ExpressionType.MemberAccess && node.Type.IsLiteralType()) - _memberExpressions.Add((MemberExpression)node.Expression); - else if (node.Expression.NodeType == ExpressionType.Parameter && node.Type.IsLiteralType()) - throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Properties.Resources.mappedMemberIsChildOfTheParameterFormat, node.GetPropertyFullName(), node.Type.FullName, sType.FullName)); - else - _memberExpressions.Add(node); - } - - return base.VisitMember(node); - } - } -} diff --git a/src/AutoMapper.Extensions.ExpressionMapping/MapIncludesVisitor.cs b/src/AutoMapper.Extensions.ExpressionMapping/MapIncludesVisitor.cs deleted file mode 100644 index 20d04fb..0000000 --- a/src/AutoMapper.Extensions.ExpressionMapping/MapIncludesVisitor.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using AutoMapper.Extensions.ExpressionMapping.Extensions; -using AutoMapper.Extensions.ExpressionMapping.Structures; - -namespace AutoMapper.Extensions.ExpressionMapping -{ - [Obsolete("This class is not intended for public use and may be removed in future versions.")] - public class MapIncludesVisitor : XpressionMapperVisitor - { - public MapIncludesVisitor(IMapper mapper, IConfigurationProvider configurationProvider, Dictionary typeMappings) - : base(mapper, configurationProvider, typeMappings) - { - } - - protected override Expression VisitLambda(Expression node) - { - if (!node.Body.Type.IsLiteralType()) - return base.VisitLambda(node); - - var ex = this.Visit(node.Body); - - var mapped = Expression.Lambda(ex, node.GetDestinationParameterExpressions(this.InfoDictionary, this.TypeMappings)); - this.TypeMappings.AddTypeMapping(ConfigurationProvider, node.Type, mapped.Type); - return mapped; - } - - protected override Expression VisitMember(MemberExpression node) - { - if (!node.Type.IsLiteralType()) - return base.VisitMember(node); - - var parameterExpression = node.GetParameterExpression(); - if (parameterExpression == null) - return base.VisitMember(node); - - InfoDictionary.Add(parameterExpression, TypeMappings); - return GetMappedMemberExpression(node.GetBaseOfMemberExpression(), new List()); - - Expression GetMappedMemberExpression(Expression parentExpression, List propertyMapInfoList) - { - Expression mappedParentExpression = this.Visit(parentExpression); - FindDestinationFullName(parentExpression.Type, mappedParentExpression.Type, node.GetPropertyFullName(), propertyMapInfoList); - - if (propertyMapInfoList.Any(x => x.CustomExpression != null))//CustomExpression takes precedence over DestinationPropertyInfo - { - return GetMemberExpression - ( - new FindMemberExpressionsVisitor(mappedParentExpression), - GetMemberExpressionFromCustomExpression - ( - propertyMapInfoList, - propertyMapInfoList.Last(x => x.CustomExpression != null), - mappedParentExpression - ) - ); - } - - return GetExpressionForInclude - ( - GetMemberExpressionFromMemberMaps - ( - BuildFullName(propertyMapInfoList), - mappedParentExpression - ) - ); - } - - Expression GetExpressionForInclude(MemberExpression memberExpression) - => memberExpression.Type.IsLiteralType() ? memberExpression.Expression : memberExpression; - - MemberExpression GetMemberExpression(FindMemberExpressionsVisitor visitor, Expression mappedExpression) - { - visitor.Visit(mappedExpression); - return visitor.Result; - } - } - } -} diff --git a/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs index b64296a..8f88d11 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs @@ -39,20 +39,6 @@ private static TDestDelegate _MapExpression(this private static MethodInfo GetMapExpressionMethod(this string methodName) => typeof(MapperExtensions).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static); - [Obsolete("This method is obsolete. Use IMapper.Map(object source, Type sourceType, Type destinationType) instead.")] - 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. /// @@ -60,49 +46,32 @@ private static MethodInfo GetMapObjectMethod(this string methodName) /// /// /// - public static TDestDelegate MapExpression(this IMapper mapper, LambdaExpression expression) + public static TDestDelegate MapExpression(this IMapper mapper, + LambdaExpression expression) where TDestDelegate : LambdaExpression { if (expression == null) return default; - return mapper.MapExpression - ( - expression, - (config, mappings) => new XpressionMapperVisitor(mapper, config, mappings), - (desType) => IsFuncType(desType) - ); - } - - private static TDestDelegate MapExpression(this IMapper mapper, - LambdaExpression expression, - Func, XpressionMapperVisitor> getVisitor, - Func shouldConvertMappedBodyToDestType) - where TDestDelegate : LambdaExpression - { return MapExpression ( - mapper.ConfigurationProvider, + mapper, expression, expression.GetType().GetGenericArguments()[0], - typeof(TDestDelegate).GetGenericArguments()[0], - getVisitor, - shouldConvertMappedBodyToDestType + typeof(TDestDelegate).GetGenericArguments()[0] ); } - private static TDestDelegate MapExpression(IConfigurationProvider configurationProvider, + private static TDestDelegate MapExpression(IMapper mapper, LambdaExpression expression, Type typeSourceFunc, - Type typeDestFunc, - Func, XpressionMapperVisitor> getVisitor, - Func shouldConvertMappedBodyToDestType) + Type typeDestFunc) where TDestDelegate : LambdaExpression { - return CreateVisitor(new Dictionary().AddTypeMappingsFromDelegates(configurationProvider, typeSourceFunc, typeDestFunc)); + return CreateVisitor(new Dictionary().AddTypeMappingsFromDelegates(mapper.ConfigurationProvider, typeSourceFunc, typeDestFunc)); TDestDelegate CreateVisitor(Dictionary typeMappings) - => MapBody(typeMappings, getVisitor(configurationProvider, typeMappings)); + => MapBody(typeMappings, new XpressionMapperVisitor(mapper, typeMappings)); TDestDelegate MapBody(Dictionary typeMappings, XpressionMapperVisitor visitor) => GetLambda(typeMappings, visitor, visitor.Visit(expression.Body)); @@ -115,24 +84,12 @@ TDestDelegate GetLambda(Dictionary typeMappings, XpressionMapperVisi return (TDestDelegate)Lambda ( typeDestFunc, - ConvertBody(), + mappedBody, expression.GetDestinationParameterExpressions(visitor.InfoDictionary, typeMappings) ); - - Expression ConvertBody() - { - if (!shouldConvertMappedBodyToDestType(typeDestFunc)) - return mappedBody; - - mappedBody = mappedBody.GetUnconvertedExpression(); - - return ElementTypeHelper.ToType(mappedBody, typeDestFunc.GetGenericArguments().Last()); - } } } - private static bool IsFuncType(this Type type) - => type.FullName.StartsWith("System.Func"); /// /// Maps an expression given a dictionary of types where the source type is the key and the destination type is the value. @@ -147,42 +104,6 @@ public static TDestDelegate MapExpression(this I where TDestDelegate : LambdaExpression => mapper.MapExpression(expression); - /// - /// 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. - /// - /// - /// - /// - /// - [Obsolete("Use ProjectTo with expansions instead of IMapper.MapExpressionAsInclude.")] - public static TDestDelegate MapExpressionAsInclude(this IMapper mapper, LambdaExpression expression) - where TDestDelegate : LambdaExpression - { - if (expression == null) - return default; - - return mapper.MapExpression - ( - expression, - (config, mappings) => new MapIncludesVisitor(mapper, config, mappings), - desType => false - ); - } - - /// - /// 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. - /// - /// - /// - /// - /// - /// - [Obsolete("Use ProjectTo with expansions instead of IMapper.MapExpressionAsInclude.")] - public static TDestDelegate MapExpressionAsInclude(this IMapper mapper, TSourceDelegate expression) - where TSourceDelegate : LambdaExpression - where TDestDelegate : LambdaExpression - => mapper.MapExpressionAsInclude(expression); - /// /// Maps a collection of expressions given a dictionary of types where the source type is the key and the destination type is the value. /// @@ -207,32 +128,6 @@ public static ICollection MapExpressionList(this I where TDestDelegate : LambdaExpression => collection?.Select(mapper.MapExpression).ToList(); - /// - /// 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. - /// - /// - /// - /// - /// - /// - [Obsolete("Use ProjectTo with expansions instead of IMapper.MapIncludesList.")] - public static ICollection MapIncludesList(this IMapper mapper, ICollection collection) - where TSourceDelegate : LambdaExpression - where TDestDelegate : LambdaExpression - => collection?.Select(mapper.MapExpressionAsInclude).ToList(); - - /// - /// 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. - /// - /// - /// - /// - /// - [Obsolete("Use ProjectTo with expansions instead of IMapper.MapIncludesList.")] - public static ICollection MapIncludesList(this IMapper mapper, IEnumerable collection) - where TDestDelegate : LambdaExpression - => collection?.Select(mapper.MapExpressionAsInclude).ToList(); - /// /// Takes a list of parameters from the source lamda expression and returns a list of parameters for the destination lambda expression. /// @@ -251,20 +146,6 @@ public static List GetDestinationParameterExpressions(this return expression.Parameters.Select(p => infoDictionary[p].NewParameter).ToList(); } - /// - /// Adds a new source and destination key-value pair to a dictionary of type mappings based on the generic arguments. - /// - /// - /// - /// - /// - /// - [Obsolete("This method is not being used and will be removed.")] - public static Dictionary AddTypeMapping(this Dictionary typeMappings, IConfigurationProvider configurationProvider) - => typeMappings == null - ? throw new ArgumentException(Properties.Resources.typeMappingsDictionaryIsNull) - : typeMappings.AddTypeMapping(configurationProvider, typeof(TSource), typeof(TDest)); - private static bool HasUnderlyingType(this Type type) { return (type.IsGenericType() && type.GetGenericArguments().Length > 0) || type.IsArray; diff --git a/src/AutoMapper.Extensions.ExpressionMapping/ReflectionExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/ReflectionExtensions.cs index 22a3c02..e50059c 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/ReflectionExtensions.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/ReflectionExtensions.cs @@ -11,36 +11,18 @@ namespace AutoMapper.Extensions.ExpressionMapping internal static class ReflectionExtensions { - public static object MapMember(this ResolutionContext context, MemberInfo member, object value, object destination = null) - => ReflectionHelper.MapMember(context, member, value, destination); - - public static void SetMemberValue(this MemberInfo propertyOrField, object target, object value) - => ReflectionHelper.SetMemberValue(propertyOrField, target, value); - public static object GetMemberValue(this MemberInfo propertyOrField, object target) => ReflectionHelper.GetMemberValue(propertyOrField, target); - public static IEnumerable GetMemberPath(Type type, string fullMemberName) - => ReflectionHelper.GetMemberPath(type, fullMemberName); - public static MemberPaths GetMemberPaths(Type type, string[] membersToExpand) => membersToExpand.Select(m => ReflectionHelper.GetMemberPath(type, m)); public static MemberPaths GetMemberPaths(Expression>[] membersToExpand) => membersToExpand.Select(expr => MemberVisitor.GetMemberPath(expr)); - public static MemberInfo FindProperty(LambdaExpression lambdaExpression) - => ReflectionHelper.FindProperty(lambdaExpression); - public static Type GetMemberType(this MemberInfo memberInfo) => ReflectionHelper.GetMemberType(memberInfo); - public static IEnumerable GetDefinedTypes(this Assembly assembly) => - assembly.DefinedTypes; - - public static bool GetHasDefaultValue(this ParameterInfo info) => - info.HasDefaultValue; - public static bool GetIsConstructedGenericType(this Type type) => type.IsConstructedGenericType; } diff --git a/src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs index 34a6f95..e0cee12 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs @@ -11,41 +11,10 @@ internal static class TypeExtensions { public static bool Has(this Type type) where TAttribute : Attribute => type.GetTypeInfo().IsDefined(typeof(TAttribute), inherit: false); - public static Type GetGenericTypeDefinitionIfGeneric(this Type type) => type.IsGenericType() ? type.GetGenericTypeDefinition() : type; - - public static Type[] GetGenericArguments(this Type type) => type.GetTypeInfo().GenericTypeArguments; - - public static Type[] GetGenericParameters(this Type type) => type.GetGenericTypeDefinition().GetTypeInfo().GenericTypeParameters; - public static IEnumerable GetDeclaredConstructors(this Type type) => type.GetTypeInfo().DeclaredConstructors; -#if !NET45 - public static MethodInfo GetAddMethod(this EventInfo eventInfo) => eventInfo.AddMethod; - - public static MethodInfo GetRemoveMethod(this EventInfo eventInfo) => eventInfo.RemoveMethod; -#endif - - public static IEnumerable GetDeclaredMembers(this Type type) => type.GetTypeInfo().DeclaredMembers; - - public static IEnumerable GetTypeInheritance(this Type type) - { - yield return type; - - var baseType = type.BaseType(); - while(baseType != null) - { - yield return baseType; - baseType = baseType.BaseType(); - } - } - - public static IEnumerable GetDeclaredMethods(this Type type) => type.GetTypeInfo().DeclaredMethods; - public static MethodInfo GetDeclaredMethod(this Type type, string name) => type.GetAllMethods().FirstOrDefault(mi => mi.Name == name); - public static MethodInfo GetDeclaredMethod(this Type type, string name, Type[] parameters) => - type.GetAllMethods().Where(mi => mi.Name == name).MatchParameters(parameters); - public static ConstructorInfo GetDeclaredConstructor(this Type type, Type[] parameters) => type.GetDeclaredConstructors().MatchParameters(parameters); @@ -56,12 +25,6 @@ private static TMethod MatchParameters(this IEnumerable method public static IEnumerable GetDeclaredProperties(this Type type) => type.GetTypeInfo().DeclaredProperties; - public static PropertyInfo GetDeclaredProperty(this Type type, string name) - => type.GetTypeInfo().GetDeclaredProperty(name); - - public static object[] GetCustomAttributes(this Type type, Type attributeType, bool inherit) - => type.GetTypeInfo().GetCustomAttributes(attributeType, inherit).Cast().ToArray(); - public static bool IsStatic(this FieldInfo fieldInfo) => fieldInfo?.IsStatic ?? false; public static bool IsStatic(this PropertyInfo propertyInfo) => propertyInfo?.GetGetMethod(true)?.IsStatic @@ -73,49 +36,12 @@ public static bool IsStatic(this MemberInfo memberInfo) => (memberInfo as FieldI || ((memberInfo as MethodInfo)?.IsStatic ?? false); - public static bool IsPublic(this PropertyInfo propertyInfo) => (propertyInfo?.GetGetMethod(true)?.IsPublic ?? false) - || (propertyInfo?.GetSetMethod(true)?.IsPublic ?? false); - - public static IEnumerable PropertiesWithAnInaccessibleSetter(this Type type) - { - return type.GetDeclaredProperties().Where(pm => pm.HasAnInaccessibleSetter()); - } - - public static bool HasAnInaccessibleSetter(this PropertyInfo property) - { - var setMethod = property.GetSetMethod(true); - return setMethod == null || setMethod.IsPrivate || setMethod.IsFamily; - } - - public static bool IsPublic(this MemberInfo memberInfo) => (memberInfo as FieldInfo)?.IsPublic ?? (memberInfo as PropertyInfo).IsPublic(); - - public static bool IsNotPublic(this ConstructorInfo constructorInfo) => constructorInfo.IsPrivate - || constructorInfo.IsFamilyAndAssembly - || constructorInfo.IsFamilyOrAssembly - || constructorInfo.IsFamily; - - public static Assembly Assembly(this Type type) => type.GetTypeInfo().Assembly; - - public static Type BaseType(this Type type) => type.GetTypeInfo().BaseType; - - public static bool IsAssignableFrom(this Type type, Type other) => type.GetTypeInfo().IsAssignableFrom(other.GetTypeInfo()); - - public static bool IsAbstract(this Type type) => type.GetTypeInfo().IsAbstract; - - public static bool IsClass(this Type type) => type.GetTypeInfo().IsClass; - public static bool IsEnum(this Type type) => type.GetTypeInfo().IsEnum; public static bool IsGenericType(this Type type) => type.GetTypeInfo().IsGenericType; - public static bool IsGenericTypeDefinition(this Type type) => type.GetTypeInfo().IsGenericTypeDefinition; - - public static bool IsInterface(this Type type) => type.GetTypeInfo().IsInterface; - public static bool IsPrimitive(this Type type) => type.GetTypeInfo().IsPrimitive; - public static bool IsSealed(this Type type) => type.GetTypeInfo().IsSealed; - public static bool IsValueType(this Type type) => type.GetTypeInfo().IsValueType; public static bool IsLiteralType(this Type type) @@ -157,20 +83,6 @@ public static bool IsLiteralType(this Type type) typeof(string) }; - public static bool IsInstanceOfType(this Type type, object o) => o != null && type.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo()); - - public static PropertyInfo[] GetProperties(this Type type) => type.GetRuntimeProperties().ToArray(); - - public static MethodInfo GetGetMethod(this PropertyInfo propertyInfo, bool ignored) => propertyInfo.GetMethod; - - public static MethodInfo GetSetMethod(this PropertyInfo propertyInfo, bool ignored) => propertyInfo.SetMethod; - - public static MethodInfo GetGetMethod(this PropertyInfo propertyInfo) => propertyInfo.GetMethod; - - 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); diff --git a/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs b/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs index 0a96708..73b8506 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs @@ -13,15 +13,6 @@ namespace AutoMapper.Extensions.ExpressionMapping { public class XpressionMapperVisitor : ExpressionVisitor { - [Obsolete("This constructor is obsolete. Use XpressionMapperVisitor(IMapper mapper, Dictionary typeMappings).")] - public XpressionMapperVisitor(IMapper mapper, IConfigurationProvider configurationProvider, Dictionary typeMappings) - { - Mapper = mapper; - TypeMappings = typeMappings; - InfoDictionary = new MapperInfoDictionary(new ParameterExpressionEqualityComparer()); - ConfigurationProvider = configurationProvider; - } - public XpressionMapperVisitor(IMapper mapper, Dictionary typeMappings) { Mapper = mapper; @@ -544,7 +535,7 @@ protected override Expression VisitConstant(ConstantExpression node) return base.VisitConstant(Expression.Constant(null, newType)); if (ConfigurationProvider.CanMapConstant(node.Type, newType)) - return base.VisitConstant(Expression.Constant(Mapper.MapObject(node.Value, node.Type, newType), newType)); + return base.VisitConstant(Expression.Constant(Mapper.Map(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)); diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/AutoMapper.Extensions.ExpressionMapping.UnitTests.csproj b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/AutoMapper.Extensions.ExpressionMapping.UnitTests.csproj index ab6b9f5..3dea78a 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/AutoMapper.Extensions.ExpressionMapping.UnitTests.csproj +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/AutoMapper.Extensions.ExpressionMapping.UnitTests.csproj @@ -1,13 +1,17 @@  - net8.0;net10.0 - net481;net8.0;net9.0;net10.0 + net10.0 + net481;net10.0 Exe false + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.ForPath.Tests.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.ForPath.Tests.cs index c0050e0..da9c071 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.ForPath.Tests.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.ForPath.Tests.cs @@ -71,53 +71,6 @@ public void Works_for_top_level_value_type() Assert.True(items.Count == 1); } - [Fact] - public void Maps_top_level_string_member_as_include() - { - //Arrange - Expression> selection = s => s.CustomerHolder.Customer.Name; - - //Act - Expression> selectionMapped = mapper.MapExpressionAsInclude>>(selection); - List orders = Orders.Select(selectionMapped).ToList(); - - //Assert - Assert.True(orders.Count == 2); - } - - [Fact] - public void Maps_top_level_value_type_as_include() - { - //Arrange - Expression> selection = s => s.CustomerHolder.Customer.Total; - - //Act - Expression> selectionMapped = mapper.MapExpressionAsInclude>>(selection); - List orders = Orders.Select(selectionMapped).ToList(); - - //Assert - Assert.True(orders.Count == 2); - } - - [Fact] - public void Throws_exception_when_mapped_value_type_is_a_child_of_the_parameter() - { - //Arrange - Expression> selection = s => s.CustomerHolder.Customer.Age; - - //Assert - Assert.Throws(() => mapper.MapExpressionAsInclude>>(selection)); - } - - [Fact] - public void Throws_exception_when_mapped_string_is_a_child_of_the_parameter() - { - //Arrange - Expression> selection = s => s.CustomerHolder.Customer.Address; - - //Assert - Assert.Throws(() => mapper.MapExpressionAsInclude>>(selection)); - } #endregion Tests private void SetupQueryableCollection() diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapperTests.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapperTests.cs index d9216e9..6a39db1 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapperTests.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapperTests.cs @@ -18,148 +18,6 @@ public XpressionMapperTests() } #region Tests - [Fact] - public void Map_includes_list() - { - //Arrange - ICollection>> selections = new List>>() { s => s.AccountModel.Bal, s => s.AccountName }; - - //Act - IList>> selectionsMapped = mapper.MapIncludesList>>(selections).ToList(); - List accounts = Users.Select(selectionsMapped[0]).ToList(); - List branches = Users.Select(selectionsMapped[1]).ToList(); - - //Assert - Assert.True(accounts.Count == 2 && branches.Count == 2); - } - - [Fact] - public void Map_includes_list_with_select() - { - //Arrange - ICollection>> selections = new List>>() { s => s.AccountModel.Bal, s => s.AccountName, s => s.AccountModel.ThingModels.Select(x => x.Color) }; - - //Act - IList>> selectionsMapped = mapper.MapIncludesList>>(selections).ToList(); - List accounts = Users.Select(selectionsMapped[0]).ToList(); - List branches = Users.Select(selectionsMapped[1]).ToList(); - List cars = Users.Select(selectionsMapped[2]).SelectMany(o => (o as IEnumerable)).ToList(); - - //Assert - Assert.True(cars.Count == 4 && accounts.Count == 2 && branches.Count == 2); - } - - [Fact] - public void Map_includes_with_value_types() - { - //Arrange - Expression> selection = s => s.AccountModel.Bal; - - //Act - Expression> selectionMapped = mapper.MapExpressionAsInclude>>(selection); - List accounts = Users.Select(selectionMapped).ToList(); - - //Assert - Assert.True(accounts.Count == 2); - } - - [Fact] - public void Map_includes_with_string() - { - //Arrange - Expression> selection = s => s.AccountName; - - //Act - Expression> selectionMapped = mapper.MapExpressionAsInclude>>(selection); - List accounts = Users.Select(selectionMapped).ToList(); - - //Assert - Assert.True(accounts.Count == 2); - } - - [Fact] - public void Map_collection_includes_with_flattened_string() - { - //Arrange - Expression>> selection = s => s.AccountModel.ThingModels.Select(x => x.Color); - - //Act - Expression> selectionMapped = mapper.MapExpressionAsInclude>>(selection); - List listOfCarLists = Users.Select(selectionMapped).ToList(); - - //Assert - Assert.True(listOfCarLists.Count == 2); - } - - [Fact] - public void Map_collection_includes_with_flattened_collection() - { - //Arrange - Expression> selection = s => s.AccountModel.ThingModels; - - //Act - Expression> selectionMapped = mapper.MapExpressionAsInclude>>(selection); - List listOfCarLists = Users.Select(selectionMapped).ToList(); - - //Assert - Assert.True(listOfCarLists.Count == 2); - } - - [Fact] - public void Map_collection_includes_with_flattened_collection_return_type_not_converted() - { - //Arrange - Expression> selection = s => s.AccountModel.ThingModels; - - //Act - Expression> selectionMapped = mapper.MapExpressionAsInclude>>(selection); - List listOfCarLists = Users.Select(selectionMapped).ToList(); - - //Assert - Assert.True(selectionMapped.ToString() == "s => s.Account.Things"); - } - - [Fact] - public void Map_includes_trim_string_nested_in_select_using_object() - { - //Arrange - Expression>> selection = s => s.AccountModel.ThingModels.Select(x => x.Color); - - //Act - Expression>> selectionMapped = mapper.MapExpressionAsInclude>>>(selection); - List cars = Users.SelectMany(selectionMapped).ToList(); - - //Assert - Assert.True(cars.Count == 4); - } - - [Fact] - public void Map_includes_trim_string_nested_in_select_using_explicit_types() - {//Probebly want to be careful about mapping strings or value types to reference types. What it there are multiple strings in the expression? - //Arrange - Expression>> selection = s => s.AccountModel.ThingModels.Select(x => x.Color); - - //Act - Expression>> selectionMapped = mapper.MapExpressionAsInclude>>>(selection); - List cars = Users.SelectMany(selectionMapped).ToList(); - - //Assert - Assert.True(cars.Count == 4); - } - - private static bool IncludeTest(IQueryable model, Expression> navigationPropertyPath) - { - return !model.Equals(navigationPropertyPath); - } - - [Fact] - public void Map_includes_with_include_method_call() - { - Expression, bool>> selection = s => IncludeTest(s, s => s.ValidLines); - var expression = mapper.MapExpressionAsInclude, bool>>>(selection); - - Assert.True(expression.Compile()(new List() { new PurchaseOrder() }.AsQueryable())); - } [Fact] public void Map_object_type_change() From 96158f449f3d177ef4db278b7bf3d7c124e49f97 Mon Sep 17 00:00:00 2001 From: Blaise Taylor Date: Fri, 20 Feb 2026 08:38:32 -0500 Subject: [PATCH 2/5] Moving management of type mappings from MapperExtensions to a dediated class. --- .../ITypeMappingsManager.cs | 16 + .../MapperExtensions.cs | 326 ++---------------- .../Properties/Resources.Designer.cs | 11 +- .../Properties/Resources.resx | 4 + .../TypeExtensions.cs | 22 +- .../TypeMappingsManager.cs | 216 ++++++++++++ .../XpressionMapperVisitor.cs | 39 ++- 7 files changed, 313 insertions(+), 321 deletions(-) create mode 100644 src/AutoMapper.Extensions.ExpressionMapping/ITypeMappingsManager.cs create mode 100644 src/AutoMapper.Extensions.ExpressionMapping/TypeMappingsManager.cs diff --git a/src/AutoMapper.Extensions.ExpressionMapping/ITypeMappingsManager.cs b/src/AutoMapper.Extensions.ExpressionMapping/ITypeMappingsManager.cs new file mode 100644 index 0000000..c79ad1e --- /dev/null +++ b/src/AutoMapper.Extensions.ExpressionMapping/ITypeMappingsManager.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace AutoMapper.Extensions.ExpressionMapping +{ + public interface ITypeMappingsManager + { + MapperInfoDictionary InfoDictionary { get; } + Dictionary TypeMappings { get; } + + void AddTypeMapping(Type sourceType, Type destType); + List GetDestinationParameterExpressions(LambdaExpression expression); + Type ReplaceType(Type sourceType); + } +} diff --git a/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs index 8f88d11..20a5b4f 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs @@ -1,10 +1,7 @@ -using AutoMapper.Extensions.ExpressionMapping.Extensions; -using AutoMapper.Internal; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Reflection; using static System.Linq.Expressions.Expression; namespace AutoMapper.Extensions.ExpressionMapping @@ -24,330 +21,81 @@ public static LambdaExpression MapExpression(this IMapper mapper, LambdaExpressi if (expression == null) return default; - return (LambdaExpression)"_MapExpression".GetMapExpressionMethod().MakeGenericMethod - ( - sourceExpressionType, - destExpressionType - ).Invoke(null, new object[] { mapper, expression }); - } + Type sourceDeledateType = sourceExpressionType.GetGenericArguments()[0]; + Type destDelegateType = destExpressionType.GetGenericArguments()[0]; + return GetLambdaExpression(new TypeMappingsManager(mapper.ConfigurationProvider, sourceDeledateType, destDelegateType)); - private static TDestDelegate _MapExpression(this IMapper mapper, TSourceDelegate expression) - where TSourceDelegate : LambdaExpression - where TDestDelegate : LambdaExpression - => mapper.MapExpression(expression); + LambdaExpression GetLambdaExpression(ITypeMappingsManager typeMappingsManager) + { + Expression mappedBody = new XpressionMapperVisitor(mapper, typeMappingsManager).Visit(expression.Body) ?? throw new InvalidOperationException(Properties.Resources.cantRemapExpression); - private static MethodInfo GetMapExpressionMethod(this string methodName) - => typeof(MapperExtensions).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static); + return Lambda + ( + destDelegateType, + mappedBody, + typeMappingsManager.GetDestinationParameterExpressions(expression) + ); + } + } /// /// Maps an expression given a dictionary of types where the source type is the key and the destination type is the value. /// - /// + /// /// /// /// - public static TDestDelegate MapExpression(this IMapper mapper, + public static TDestExpression MapExpression(this IMapper mapper, LambdaExpression expression) - where TDestDelegate : LambdaExpression + where TDestExpression : LambdaExpression { if (expression == null) return default; - return MapExpression + return (TDestExpression)MapExpression ( mapper, expression, - expression.GetType().GetGenericArguments()[0], - typeof(TDestDelegate).GetGenericArguments()[0] + expression.GetType(), + typeof(TDestExpression) ); } - private static TDestDelegate MapExpression(IMapper mapper, - LambdaExpression expression, - Type typeSourceFunc, - Type typeDestFunc) - where TDestDelegate : LambdaExpression - { - return CreateVisitor(new Dictionary().AddTypeMappingsFromDelegates(mapper.ConfigurationProvider, typeSourceFunc, typeDestFunc)); - - TDestDelegate CreateVisitor(Dictionary typeMappings) - => MapBody(typeMappings, new XpressionMapperVisitor(mapper, typeMappings)); - - TDestDelegate MapBody(Dictionary typeMappings, XpressionMapperVisitor visitor) - => GetLambda(typeMappings, visitor, visitor.Visit(expression.Body)); - - TDestDelegate GetLambda(Dictionary typeMappings, XpressionMapperVisitor visitor, Expression mappedBody) - { - if (mappedBody == null) - throw new InvalidOperationException(Properties.Resources.cantRemapExpression); - - return (TDestDelegate)Lambda - ( - typeDestFunc, - mappedBody, - expression.GetDestinationParameterExpressions(visitor.InfoDictionary, typeMappings) - ); - } - } - - /// /// Maps an expression given a dictionary of types where the source type is the key and the destination type is the value. /// - /// - /// + /// + /// /// /// /// - public static TDestDelegate MapExpression(this IMapper mapper, TSourceDelegate expression) - where TSourceDelegate : LambdaExpression - where TDestDelegate : LambdaExpression - => mapper.MapExpression(expression); + public static TDestExpression MapExpression(this IMapper mapper, TSourceExpression expression) + where TSourceExpression : LambdaExpression + where TDestExpression : LambdaExpression + => mapper.MapExpression(expression); /// /// Maps a collection of expressions given a dictionary of types where the source type is the key and the destination type is the value. /// - /// - /// + /// + /// /// /// /// - public static ICollection MapExpressionList(this IMapper mapper, ICollection collection) - where TSourceDelegate : LambdaExpression - where TDestDelegate : LambdaExpression - => collection?.Select(mapper.MapExpression).ToList(); + public static ICollection MapExpressionList(this IMapper mapper, ICollection collection) + where TSourceExpression : LambdaExpression + where TDestExpression : LambdaExpression + => collection?.Select(mapper.MapExpression).ToList(); /// /// Maps a collection of expressions given a dictionary of types where the source type is the key and the destination type is the value. /// - /// + /// /// /// /// - public static ICollection MapExpressionList(this IMapper mapper, IEnumerable collection) - where TDestDelegate : LambdaExpression - => collection?.Select(mapper.MapExpression).ToList(); - - /// - /// Takes a list of parameters from the source lamda expression and returns a list of parameters for the destination lambda expression. - /// - /// - /// - /// - /// - [Obsolete("This method will be moved to a public class meant for internal use.")] - public static List GetDestinationParameterExpressions(this LambdaExpression expression, MapperInfoDictionary infoDictionary, Dictionary typeMappings) - { - foreach (var p in expression.Parameters.Where(p => !infoDictionary.ContainsKey(p))) - { - infoDictionary.Add(p, typeMappings); - } - - return expression.Parameters.Select(p => infoDictionary[p].NewParameter).ToList(); - } - - private static bool HasUnderlyingType(this Type type) - { - return (type.IsGenericType() && type.GetGenericArguments().Length > 0) || type.IsArray; - } - - private static void AddUnderlyingTypes(this Dictionary typeMappings, IConfigurationProvider configurationProvider, Type sourceType, Type destType) - { - if ((sourceType.IsGenericType() && typeof(System.Collections.IEnumerable).IsAssignableFrom(sourceType)) || sourceType.IsArray) - { - typeMappings.DoAddTypeMappings - ( - configurationProvider, - !sourceType.HasUnderlyingType() ? new List() : ElementTypeHelper.GetElementTypes(sourceType).ToList(), - !destType.HasUnderlyingType() ? new List() : ElementTypeHelper.GetElementTypes(destType).ToList() - ); - } - else if (sourceType.IsGenericType() && destType.IsGenericType()) - { - typeMappings.DoAddTypeMappings - ( - configurationProvider, - !sourceType.HasUnderlyingType() ? new List() : sourceType.GetGenericArguments().ToList(), - !destType.HasUnderlyingType() ? new List() : destType.GetGenericArguments().ToList() - ); - } - } - - /// - /// Adds a new source and destination key-value pair to a dictionary of type mappings based on the arguments. - /// - /// - /// - /// - /// - /// - [Obsolete("This method will be moved to a public class meant for internal use.")] - public static Dictionary AddTypeMapping(this Dictionary typeMappings, IConfigurationProvider configurationProvider, Type sourceType, Type destType) - { - if (typeMappings == null) - throw new ArgumentException(Properties.Resources.typeMappingsDictionaryIsNull); - - if (sourceType.GetTypeInfo().IsGenericType && sourceType.GetGenericTypeDefinition() == typeof(Expression<>)) - { - sourceType = sourceType.GetGenericArguments()[0]; - destType = destType.GetGenericArguments()[0]; - } - - if (!typeMappings.ContainsKey(sourceType) && sourceType != destType) - { - typeMappings.Add(sourceType, destType); - if (typeof(Delegate).IsAssignableFrom(sourceType)) - typeMappings.AddTypeMappingsFromDelegates(configurationProvider, sourceType, destType); - else - { - typeMappings.AddUnderlyingTypes(configurationProvider, sourceType, destType); - typeMappings.FindChildPropertyTypeMaps(configurationProvider, sourceType, destType); - typeMappings.AddIncludedTypeMaps(configurationProvider, sourceType, destType); - } - } - - return typeMappings; - } - - private static void AddIncludedTypeMaps(this Dictionary typeMappings, IConfigurationProvider configurationProvider, Type source/*model*/, Type dest/*data*/)//model to date - { - //Stay with the existing design of using configured data to model maps to retrieve the type mappings. - //This is needed for property map custom expressions. - AddTypeMaps(configurationProvider.Internal().ResolveTypeMap(sourceType: dest/*data*/, destinationType: source/*model*/)); - - void AddTypeMaps(TypeMap typeMap) - { - if (typeMap == null) - return; - - foreach (TypePair baseTypePair in typeMap.IncludedBaseTypes) - typeMappings.AddTypeMapping(configurationProvider, baseTypePair.DestinationType/*model*/, baseTypePair.SourceType/*data*/); - - foreach (TypePair derivedTypePair in typeMap.IncludedDerivedTypes) - typeMappings.AddTypeMapping(configurationProvider, derivedTypePair.DestinationType/*model*/, derivedTypePair.SourceType/*data*/); - } - } - - /// - /// Replaces a type in the source expression with the corresponding destination type. - /// - /// - /// - /// - [Obsolete("This method will be moved to a public class meant for internal use.")] - public static Type ReplaceType(this Dictionary typeMappings, Type sourceType) - { - if (sourceType.IsArray) - { - if (typeMappings.TryGetValue(sourceType, out Type destType)) - return destType; - - if (typeMappings.TryGetValue(sourceType.GetElementType(), out Type destElementType)) - { - int rank = sourceType.GetArrayRank(); - return rank == 1 - ? destElementType.MakeArrayType() - : destElementType.MakeArrayType(rank); - } - - return sourceType; - } - else if (sourceType.IsGenericType) - { - if (typeMappings.TryGetValue(sourceType, out Type destType)) - return destType; - else - { - return sourceType.GetGenericTypeDefinition().MakeGenericType - ( - sourceType - .GetGenericArguments() - .Select(typeMappings.ReplaceType) - .ToArray() - ); - } - } - else - { - return typeMappings.TryGetValue(sourceType, out Type destType) ? destType : sourceType; - } - } - - private static Dictionary AddTypeMappingsFromDelegates(this Dictionary typeMappings, IConfigurationProvider configurationProvider, Type sourceType, Type destType) - { - if (typeMappings == null) - throw new ArgumentException(Properties.Resources.typeMappingsDictionaryIsNull); - - typeMappings.DoAddTypeMappingsFromDelegates - ( - configurationProvider, - sourceType.GetGenericArguments().ToList(), - destType.GetGenericArguments().ToList() - ); - - return typeMappings; - } - - private static void DoAddTypeMappingsFromDelegates(this Dictionary typeMappings, IConfigurationProvider configurationProvider, List sourceArguments, List destArguments) - { - if (sourceArguments.Count != destArguments.Count) - throw new ArgumentException(Properties.Resources.invalidArgumentCount); - - for (int i = 0; i < sourceArguments.Count; i++) - { - if (!typeMappings.ContainsKey(sourceArguments[i]) && sourceArguments[i] != destArguments[i]) - typeMappings.AddTypeMapping(configurationProvider, sourceArguments[i], destArguments[i]); - } - } - - private static void DoAddTypeMappings(this Dictionary typeMappings, IConfigurationProvider configurationProvider, List sourceArguments, List destArguments) - { - if (sourceArguments.Count != destArguments.Count) - return; - - for (int i = 0; i < sourceArguments.Count; i++) - { - if (!typeMappings.ContainsKey(sourceArguments[i]) && sourceArguments[i] != destArguments[i]) - typeMappings.AddTypeMapping(configurationProvider, sourceArguments[i], destArguments[i]); - } - } - - private static Type GetSourceMemberType(this PropertyMap propertyMap) - => propertyMap.CustomMapExpression != null - ? propertyMap.CustomMapExpression.ReturnType - : propertyMap.SourceMembers.Last().GetMemberType(); - - private static void FindChildPropertyTypeMaps(this Dictionary typeMappings, IConfigurationProvider ConfigurationProvider, Type source, Type dest) - { - //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 - var typeMap = ConfigurationProvider.Internal().ResolveTypeMap(sourceType: dest, destinationType: source); - - if (typeMap == null) - return; - - FindMaps(typeMap.PropertyMaps.ToList()); - void FindMaps(List maps) - { - foreach (PropertyMap pm in maps) - { - if (!pm.SourceMembers.Any() && pm.CustomMapExpression == null) - continue; - - AddChildMappings - ( - source.GetFieldOrProperty(pm.DestinationMember.Name).GetMemberType(), - pm.GetSourceMemberType() - ); - void AddChildMappings(Type sourcePropertyType, Type destPropertyType) - { - if (sourcePropertyType.IsLiteralType() || destPropertyType.IsLiteralType()) - return; - - typeMappings.AddTypeMapping(ConfigurationProvider, sourcePropertyType, destPropertyType); - } - } - } - } + public static ICollection MapExpressionList(this IMapper mapper, IEnumerable collection) + where TDestExpression : LambdaExpression + => collection?.Select(mapper.MapExpression).ToList(); } } diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Properties/Resources.Designer.cs b/src/AutoMapper.Extensions.ExpressionMapping/Properties/Resources.Designer.cs index 461bc9c..0def712 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/Properties/Resources.Designer.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace AutoMapper.Extensions.ExpressionMapping.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { @@ -60,6 +60,15 @@ internal Resources() { } } + /// + /// Looks up a localized string similar to Argument {0} must be a deledate type.. + /// + internal static string argumentMustBeDelegateFormat { + get { + return ResourceManager.GetString("argumentMustBeDelegateFormat", resourceCulture); + } + } + /// /// Looks up a localized string similar to Cannot create a binary expression for the following pair. Node: {0}, Type: {1} and Node: {2}, Type: {3}.. /// diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Properties/Resources.resx b/src/AutoMapper.Extensions.ExpressionMapping/Properties/Resources.resx index da39c6c..d3f05ae 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/Properties/Resources.resx +++ b/src/AutoMapper.Extensions.ExpressionMapping/Properties/Resources.resx @@ -142,5 +142,9 @@ For members of literal types, use IMappingExpression.ForMember() to make the parent property types an exact match. Parent Source Type: {0}, Parent Destination Type: {1}, Full Member Name "{2}". 0=typeSource, 1=typeDestination; 2=sourceFullName + + + Argument {0} must be a deledate type. + 0=argumentType \ No newline at end of file diff --git a/src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs index e0cee12..0fd63d9 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs @@ -52,17 +52,7 @@ public static bool IsLiteralType(this Type type) return LiteralTypes.Contains(type) || NonNetStandardLiteralTypes.Contains(type.FullName); } - private static HashSet LiteralTypes => new HashSet(_literalTypes); - - private static readonly HashSet NonNetStandardLiteralTypes = new() - { - "System.DateOnly", - "Microsoft.OData.Edm.Date", - "System.TimeOnly", - "Microsoft.OData.Edm.TimeOfDay" - }; - - private static Type[] _literalTypes => new Type[] { + private static HashSet LiteralTypes => [ typeof(bool), typeof(DateTime), typeof(DateTimeOffset), @@ -81,7 +71,15 @@ public static bool IsLiteralType(this Type type) typeof(uint), typeof(ulong), typeof(string) - }; + ]; + + private static readonly HashSet NonNetStandardLiteralTypes = + [ + "System.DateOnly", + "Microsoft.OData.Edm.Date", + "System.TimeOnly", + "Microsoft.OData.Edm.TimeOfDay" + ]; public static bool IsQueryableType(this Type type) => typeof(IQueryable).IsAssignableFrom(type); diff --git a/src/AutoMapper.Extensions.ExpressionMapping/TypeMappingsManager.cs b/src/AutoMapper.Extensions.ExpressionMapping/TypeMappingsManager.cs new file mode 100644 index 0000000..ca2759b --- /dev/null +++ b/src/AutoMapper.Extensions.ExpressionMapping/TypeMappingsManager.cs @@ -0,0 +1,216 @@ +using AutoMapper.Internal; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace AutoMapper.Extensions.ExpressionMapping +{ + public class TypeMappingsManager : ITypeMappingsManager + { + public TypeMappingsManager(IConfigurationProvider configurationProvider, Type typeSourceFunc, Type typeDestFunc) + { + if (!typeof(Delegate).IsAssignableFrom(typeSourceFunc)) + throw new ArgumentException(string.Format(System.Globalization.CultureInfo.CurrentCulture, Properties.Resources.argumentMustBeDelegateFormat, nameof(typeSourceFunc)), nameof(typeSourceFunc)); + if (!typeof(Delegate).IsAssignableFrom(typeDestFunc)) + throw new ArgumentException(string.Format(System.Globalization.CultureInfo.CurrentCulture, Properties.Resources.argumentMustBeDelegateFormat, nameof(typeDestFunc)), nameof(typeDestFunc)); + + ConfigurationProvider = configurationProvider; + InfoDictionary = new MapperInfoDictionary(new ParameterExpressionEqualityComparer()); + TypeMappings = []; + + AddTypeMappingsFromDelegates(typeSourceFunc, typeDestFunc); + } + + public MapperInfoDictionary InfoDictionary { get; } + + public Dictionary TypeMappings { get; } + + private IConfigurationProvider ConfigurationProvider { get; } + + public void AddTypeMapping(Type sourceType, Type destType) + { + if (sourceType.GetTypeInfo().IsGenericType && sourceType.GetGenericTypeDefinition() == typeof(Expression<>)) + { + sourceType = sourceType.GetGenericArguments()[0]; + destType = destType.GetGenericArguments()[0]; + } + + if (!TypeMappings.ContainsKey(sourceType) && sourceType != destType) + { + TypeMappings.Add(sourceType, destType); + if (typeof(Delegate).IsAssignableFrom(sourceType)) + AddTypeMappingsFromDelegates(sourceType, destType); + else + { + AddUnderlyingTypes(sourceType, destType); + FindChildPropertyTypeMaps(sourceType, destType); + AddIncludedTypeMaps(sourceType, destType); + } + } + } + + public void AddTypeMappingsFromDelegates(Type sourceType, Type destType) + { + DoAddTypeMappingsFromDelegates + ( + [.. sourceType.GetGenericArguments()], + [.. destType.GetGenericArguments()] + ); + } + + public List GetDestinationParameterExpressions(LambdaExpression expression) + { + foreach (var p in expression.Parameters.Where(p => !InfoDictionary.ContainsKey(p))) + { + InfoDictionary.Add(p, TypeMappings); + } + + return [.. expression.Parameters.Select(p => InfoDictionary[p].NewParameter)]; + } + + public Type ReplaceType(Type sourceType) + { + if (sourceType.IsArray) + { + if (TypeMappings.TryGetValue(sourceType, out Type destType)) + return destType; + + if (TypeMappings.TryGetValue(sourceType.GetElementType(), out Type destElementType)) + { + int rank = sourceType.GetArrayRank(); + return rank == 1 + ? destElementType.MakeArrayType() + : destElementType.MakeArrayType(rank); + } + + return sourceType; + } + else if (sourceType.IsGenericType) + { + if (TypeMappings.TryGetValue(sourceType, out Type destType)) + return destType; + else + { + return sourceType.GetGenericTypeDefinition().MakeGenericType + ( + [.. sourceType + .GetGenericArguments() + .Select(ReplaceType)] + ); + } + } + else + { + return TypeMappings.TryGetValue(sourceType, out Type destType) ? destType : sourceType; + } + } + + private void AddIncludedTypeMaps(Type source/*model*/, Type dest/*data*/)//model to data + { + //Stay with the existing design of using configured data to model maps to retrieve the type mappings. + //This is needed for property map custom expressions. + AddTypeMaps(ConfigurationProvider.Internal().ResolveTypeMap(sourceType: dest/*data*/, destinationType: source/*model*/)); + + void AddTypeMaps(TypeMap typeMap) + { + if (typeMap == null) + return; + + foreach (TypePair baseTypePair in typeMap.IncludedBaseTypes) + AddTypeMapping(baseTypePair.DestinationType/*model*/, baseTypePair.SourceType/*data*/); + + foreach (TypePair derivedTypePair in typeMap.IncludedDerivedTypes) + AddTypeMapping(derivedTypePair.DestinationType/*model*/, derivedTypePair.SourceType/*data*/); + } + } + + private void AddUnderlyingTypes(Type sourceType, Type destType) + { + if ((sourceType.IsGenericType() && typeof(System.Collections.IEnumerable).IsAssignableFrom(sourceType)) || sourceType.IsArray) + { + DoAddTypeMappings + ( + !HasUnderlyingType(sourceType) ? [] : [.. ElementTypeHelper.GetElementTypes(sourceType)], + !HasUnderlyingType(destType) ? [] : [.. ElementTypeHelper.GetElementTypes(destType)] + ); + } + else if (sourceType.IsGenericType() && destType.IsGenericType()) + { + DoAddTypeMappings + ( + !HasUnderlyingType(sourceType) ? [] : [.. sourceType.GetGenericArguments()], + !HasUnderlyingType(destType) ? [] : [.. destType.GetGenericArguments()] + ); + } + } + + private void DoAddTypeMappings(List sourceArguments, List destArguments) + { + if (sourceArguments.Count != destArguments.Count) + return; + + for (int i = 0; i < sourceArguments.Count; i++) + { + if (!TypeMappings.ContainsKey(sourceArguments[i]) && sourceArguments[i] != destArguments[i]) + AddTypeMapping(sourceArguments[i], destArguments[i]); + } + } + + private void DoAddTypeMappingsFromDelegates(List sourceArguments, List destArguments) + { + if (sourceArguments.Count != destArguments.Count) + throw new ArgumentException(Properties.Resources.invalidArgumentCount); + + for (int i = 0; i < sourceArguments.Count; i++) + { + if (!TypeMappings.ContainsKey(sourceArguments[i]) && sourceArguments[i] != destArguments[i]) + AddTypeMapping(sourceArguments[i], destArguments[i]); + } + } + + private void FindChildPropertyTypeMaps(Type source, Type dest) + { + //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 + var typeMap = ConfigurationProvider.Internal().ResolveTypeMap(sourceType: dest, destinationType: source); + + if (typeMap == null) + return; + + FindMaps([.. typeMap.PropertyMaps]); + void FindMaps(List maps) + { + foreach (PropertyMap pm in maps) + { + if (pm.SourceMembers.Length < 1 && pm.CustomMapExpression == null) + continue; + + AddChildMappings + ( + source.GetFieldOrProperty(pm.DestinationMember.Name).GetMemberType(), + GetSourceMemberType(pm) + ); + void AddChildMappings(Type sourcePropertyType, Type destPropertyType) + { + if (sourcePropertyType.IsLiteralType() || destPropertyType.IsLiteralType()) + return; + + AddTypeMapping(sourcePropertyType, destPropertyType); + } + } + } + } + + private static Type GetSourceMemberType(PropertyMap propertyMap) + => propertyMap.CustomMapExpression != null + ? propertyMap.CustomMapExpression.ReturnType + : propertyMap.SourceMembers.Last().GetMemberType(); + + private static bool HasUnderlyingType(Type type) + { + return (type.IsGenericType() && type.GetGenericArguments().Length > 0) || type.IsArray; + } + } +} diff --git a/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs b/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs index 73b8506..619d7a5 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs @@ -13,25 +13,26 @@ namespace AutoMapper.Extensions.ExpressionMapping { public class XpressionMapperVisitor : ExpressionVisitor { - public XpressionMapperVisitor(IMapper mapper, Dictionary typeMappings) + public XpressionMapperVisitor(IMapper mapper, ITypeMappingsManager typeMappingsManager) { Mapper = mapper; - TypeMappings = typeMappings; - InfoDictionary = new MapperInfoDictionary(new ParameterExpressionEqualityComparer()); + TypeMappingsManager = typeMappingsManager; ConfigurationProvider = mapper.ConfigurationProvider; } - public MapperInfoDictionary InfoDictionary { get; } + private MapperInfoDictionary InfoDictionary { get { return TypeMappingsManager.InfoDictionary; } } - public Dictionary TypeMappings { get; } + private Dictionary TypeMappings { get { return TypeMappingsManager.TypeMappings; } } - protected IConfigurationProvider ConfigurationProvider { get; } + private IConfigurationProvider ConfigurationProvider { get; } - protected IMapper Mapper { get; } + private IMapper Mapper { get; } private IConfigurationProvider anonymousTypesConfigurationProvider; private readonly MapperConfigurationExpression anonymousTypesBaseMappings = new MapperConfigurationExpression(); + private ITypeMappingsManager TypeMappingsManager { get; } + protected override Expression VisitParameter(ParameterExpression node) { InfoDictionary.Add(node, TypeMappings); @@ -98,7 +99,7 @@ Expression GetMappedMemberExpression(Expression parentExpression, List(Expression node) { var ex = this.Visit(node.Body); - var mapped = Expression.Lambda(this.TypeMappings.ReplaceType(node.Type), ex, node.GetDestinationParameterExpressions(this.InfoDictionary, this.TypeMappings)); - this.TypeMappings.AddTypeMapping(ConfigurationProvider, node.Type, mapped.Type); + var mapped = Expression.Lambda(this.TypeMappingsManager.ReplaceType(node.Type), ex, this.TypeMappingsManager.GetDestinationParameterExpressions(node)); + this.TypeMappingsManager.AddTypeMapping(node.Type, mapped.Type); return mapped; } @@ -292,7 +293,7 @@ private void ConfigureAnonymousTypeMaps(Type oldType, Type newAnonymousType) private MemberInitExpression GetAnonymousTypeMemberInitExpression(Dictionary bindingExpressions, Type oldType) { Type newAnonymousType = AnonymousTypeFactory.CreateAnonymousType(bindingExpressions.ToDictionary(a => a.Key, a => a.Value.Type)); - TypeMappings.AddTypeMapping(ConfigurationProvider, oldType, newAnonymousType); + this.TypeMappingsManager.AddTypeMapping(oldType, newAnonymousType); ConfigureAnonymousTypeMaps(oldType, newAnonymousType); return Expression.MemberInit @@ -398,7 +399,7 @@ bool ShouldBindChildReference(MemberBindingGroup group) private MemberBinding DoBind(MemberInfo sourceMember, Expression initial, Expression mapped) { mapped = mapped.ConvertTypeIfNecessary(sourceMember.GetMemberType()); - this.TypeMappings.AddTypeMapping(ConfigurationProvider, initial.Type, mapped.Type); + this.TypeMappingsManager.AddTypeMapping(initial.Type, mapped.Type); return Expression.Bind(sourceMember, mapped); } @@ -528,7 +529,7 @@ Expression DoVisitUnary(Expression updated) protected override Expression VisitConstant(ConstantExpression node) { - Type newType = this.TypeMappings.ReplaceType(node.Type); + Type newType = this.TypeMappingsManager.ReplaceType(node.Type); if (newType != node.Type) { if (node.Value == null) @@ -554,7 +555,7 @@ protected override Expression VisitMethodCall(MethodCallExpression node) var listOfArgumentsForNewMethod = node.Arguments.Aggregate(new List(), (lst, next) => { var mappedNext = this.Visit(next); - TypeMappings.AddTypeMapping(ConfigurationProvider, next.Type, mappedNext.Type); + this.TypeMappingsManager.AddTypeMapping(next.Type, mappedNext.Type); lst.Add(mappedNext); return lst; @@ -562,7 +563,7 @@ protected override Expression VisitMethodCall(MethodCallExpression node) //type args are the generic type args e.g. T1 and T2 MethodName(method arguments); var typeArgsForNewMethod = node.Method.IsGenericMethod - ? node.Method.GetGenericArguments().Select(type => this.TypeMappings.ReplaceType(type)).ToList()//not converting the type it is not in the typeMappings dictionary + ? node.Method.GetGenericArguments().Select(type => this.TypeMappingsManager.ReplaceType(type)).ToList()//not converting the type it is not in the typeMappings dictionary : null; ConvertTypesIfNecessary(node.Method.GetParameters(), listOfArgumentsForNewMethod, node.Method); @@ -713,7 +714,7 @@ TypeMap GetTypeMap() => BothTypesAreAnonymous() var sourceType = typeSource.GetFieldOrProperty(sourceFullName).GetMemberType(); var destType = typeDestination.GetFieldOrProperty(sourceFullName).GetMemberType(); - TypeMappings.AddTypeMapping(ConfigurationProvider, sourceType, destType); + this.TypeMappingsManager.AddTypeMapping(sourceType, destType); return; } @@ -725,8 +726,8 @@ TypeMap GetTypeMap() => BothTypesAreAnonymous() var sourceType = typeSource.GetFieldOrProperty(propertyName).GetMemberType(); var destType = typeDestination.GetFieldOrProperty(propertyName).GetMemberType(); - - TypeMappings.AddTypeMapping(ConfigurationProvider, sourceType, destType); + + this.TypeMappingsManager.AddTypeMapping(sourceType, destType); var childFullName = sourceFullName.Substring(sourceFullName.IndexOf(period, StringComparison.OrdinalIgnoreCase) + 1); FindDestinationFullName(sourceType, destType, childFullName, propertyMapInfoList); From da6c1ec68cacdb20379bba3782e68d7854f4d6cd Mon Sep 17 00:00:00 2001 From: Blaise Taylor Date: Fri, 20 Feb 2026 09:46:07 -0500 Subject: [PATCH 3/5] Improving coverage percentage. --- .../AnonymousTypeFactory.cs | 3 + .../ExpressionMapper.cs | 11 +- .../Impl/QueryDataSourceInjection.cs | 1 + .../Impl/SourceInjectedQuery.cs | 1 + .../Impl/SourceInjectedQueryInspector.cs | 1 + .../Impl/SourceInjectedQueryProvider.cs | 4 + .../Structures/MapperInfo.cs | 21 +- .../TypeExtensions.cs | 3 +- ...ensions.ExpressionMapping.UnitTests.csproj | 2 + .../ElementTypeHelperTest.cs | 419 ++++++++++ .../LockingConcurrentDictionaryTest.cs | 243 ++++++ .../Structures/DeclaringMemberKeyTest.cs | 251 ++++++ .../TypeExtensionsTest.cs | 733 ++++++++++++++++++ .../TypeMappingsManagerTest.cs | 690 +++++++++++++++++ 14 files changed, 2360 insertions(+), 23 deletions(-) create mode 100644 tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ElementTypeHelperTest.cs create mode 100644 tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/LockingConcurrentDictionaryTest.cs create mode 100644 tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/Structures/DeclaringMemberKeyTest.cs create mode 100644 tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/TypeExtensionsTest.cs create mode 100644 tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/TypeMappingsManagerTest.cs diff --git a/src/AutoMapper.Extensions.ExpressionMapping/AnonymousTypeFactory.cs b/src/AutoMapper.Extensions.ExpressionMapping/AnonymousTypeFactory.cs index c135a58..41c0063 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/AnonymousTypeFactory.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/AnonymousTypeFactory.cs @@ -3,6 +3,9 @@ using System.Linq; using System.Reflection; using System.Reflection.Emit; +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("AutoMapper.Extensions.ExpressionMapping.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010079dfef85ed6ba841717e154f13182c0a6029a40794a6ecd2886c7dc38825f6a4c05b0622723a01cd080f9879126708eef58f134accdc99627947425960ac2397162067507e3c627992aa6b92656ad3380999b30b5d5645ba46cc3fcc6a1de5de7afebcf896c65fb4f9547a6c0c6433045fceccb1fa15e960d519d0cd694b29a4")] namespace AutoMapper.Extensions.ExpressionMapping { diff --git a/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs b/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs index d90cba1..6bdad7e 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs @@ -1,11 +1,12 @@ -using System; +using AutoMapper.Extensions.ExpressionMapping; +using AutoMapper.Internal; +using AutoMapper.Internal.Mappers; +using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; -using AutoMapper.Extensions.ExpressionMapping; -using AutoMapper.Internal; -using AutoMapper.Internal.Mappers; using static System.Linq.Expressions.Expression; namespace AutoMapper.Mappers @@ -37,6 +38,7 @@ public Expression MapExpression(IGlobalConfiguration configurationProvider, Prof return null; } + [ExcludeFromCodeCoverage] internal class MappingVisitor : ExpressionVisitor { private IList _destSubTypes = new Type[0]; @@ -226,6 +228,7 @@ protected override Expression VisitMember(MemberExpression node) .Aggregate(replacedExpression, getExpression); } + [ExcludeFromCodeCoverage] private class IsConstantExpressionVisitor : ExpressionVisitor { public bool IsConstant { get; private set; } diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Impl/QueryDataSourceInjection.cs b/src/AutoMapper.Extensions.ExpressionMapping/Impl/QueryDataSourceInjection.cs index 4f3aca2..b6f7b92 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/Impl/QueryDataSourceInjection.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/Impl/QueryDataSourceInjection.cs @@ -48,6 +48,7 @@ public interface IQueryDataSourceInjection IQueryDataSourceInjection OnError(Action exceptionHandler); } + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public class QueryDataSourceInjection : IQueryDataSourceInjection { private readonly IQueryable _dataSource; diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQuery.cs b/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQuery.cs index 306b160..26da78b 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQuery.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQuery.cs @@ -10,6 +10,7 @@ namespace AutoMapper.Extensions.ExpressionMapping.Impl using IObjectDictionary = IDictionary; using MemberPaths = IEnumerable>; + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public class SourceSourceInjectedQuery : IOrderedQueryable, ISourceInjectedQueryable { private readonly Action _exceptionHandler; diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryInspector.cs b/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryInspector.cs index 4cde971..5b54770 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryInspector.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryInspector.cs @@ -3,6 +3,7 @@ namespace AutoMapper.Extensions.ExpressionMapping.Impl { + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public class SourceInjectedQueryInspector { public SourceInjectedQueryInspector() diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs b/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs index 2a3d040..a4d40c1 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs @@ -15,6 +15,7 @@ namespace AutoMapper.Extensions.ExpressionMapping.Impl using MemberPaths = IEnumerable>; using ParameterBag = IDictionary; + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public class SourceInjectedQueryProvider : IQueryProvider { private readonly IMapper _mapper; @@ -348,6 +349,7 @@ private static MethodInfo FindQueryableSelectMethod() } } + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal class ConstantExpressionReplacementVisitor : ExpressionVisitor { private readonly ParameterBag _paramValues; @@ -374,6 +376,7 @@ protected override Expression VisitMember(MemberExpression node) } } + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal class ReplaceableMethodNodeFinder : ExpressionVisitor { public MethodCallExpression MethodNode { get; private set; } @@ -413,6 +416,7 @@ protected override Expression VisitMethodCall(MethodCallExpression node) } } + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal class MethodNodeReplacer : ExpressionVisitor { private readonly MethodCallExpression _foundExpression; diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Structures/MapperInfo.cs b/src/AutoMapper.Extensions.ExpressionMapping/Structures/MapperInfo.cs index 003f9f9..1e2bbaa 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/Structures/MapperInfo.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/Structures/MapperInfo.cs @@ -3,23 +3,10 @@ namespace AutoMapper.Extensions.ExpressionMapping.Structures { - public class MapperInfo + public class MapperInfo(ParameterExpression newParameter, Type sourceType, Type destType) { - [Obsolete("Use MapperInfo(ParameterExpression newParameter, Type sourceType, Type destType).")] - public MapperInfo() - { - - } - - public MapperInfo(ParameterExpression newParameter, Type sourceType, Type destType) - { - NewParameter = newParameter; - SourceType = sourceType; - DestType = destType; - } - - public Type SourceType { get; set; } - public Type DestType { get; set; } - public ParameterExpression NewParameter { get; set; } + public Type SourceType { get; set; } = sourceType; + public Type DestType { get; set; } = destType; + public ParameterExpression NewParameter { get; set; } = newParameter; } } diff --git a/src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs index 0fd63d9..d02e3be 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/TypeExtensions.cs @@ -4,9 +4,8 @@ using System.Linq; using System.Reflection; -namespace AutoMapper +namespace AutoMapper.Extensions.ExpressionMapping { - internal static class TypeExtensions { public static bool Has(this Type type) where TAttribute : Attribute => type.GetTypeInfo().IsDefined(typeof(TAttribute), inherit: false); diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/AutoMapper.Extensions.ExpressionMapping.UnitTests.csproj b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/AutoMapper.Extensions.ExpressionMapping.UnitTests.csproj index 3dea78a..5b0edf5 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/AutoMapper.Extensions.ExpressionMapping.UnitTests.csproj +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/AutoMapper.Extensions.ExpressionMapping.UnitTests.csproj @@ -5,6 +5,8 @@ net481;net10.0 Exe false + ..\..\AutoMapper.snk + true diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ElementTypeHelperTest.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ElementTypeHelperTest.cs new file mode 100644 index 0000000..b417805 --- /dev/null +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ElementTypeHelperTest.cs @@ -0,0 +1,419 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Xunit; + +namespace AutoMapper.Extensions.ExpressionMapping.UnitTests +{ + public class ElementTypeHelperTest + { + #region GetElementType Tests + + [Fact] + public void GetElementType_ArrayType_ReturnsElementType() + { + // Arrange + var type = typeof(int[]); + + // Act + var result = ElementTypeHelper.GetElementType(type); + + // Assert + Assert.Equal(typeof(int), result); + } + + [Fact] + public void GetElementType_ListType_ReturnsElementType() + { + // Arrange + var type = typeof(List); + + // Act + var result = ElementTypeHelper.GetElementType(type); + + // Assert + Assert.Equal(typeof(string), result); + } + + [Fact] + public void GetElementType_IEnumerableType_ReturnsElementType() + { + // Arrange + var type = typeof(IEnumerable); + + // Act + var result = ElementTypeHelper.GetElementType(type); + + // Assert + Assert.Equal(typeof(double), result); + } + + [Fact] + public void GetElementType_NonEnumerableType_ThrowsArgumentException() + { + // Arrange + var type = typeof(int); + + // Act & Assert + Assert.Throws(() => ElementTypeHelper.GetElementType(type)); + } + + #endregion + + #region GetElementTypes with Flags Tests + + [Fact] + public void GetElementTypes_ArrayType_ReturnsElementType() + { + // Arrange + var type = typeof(string[]); + + // Act + var result = ElementTypeHelper.GetElementTypes(type); + + // Assert + Assert.Single(result); + Assert.Equal(typeof(string), result[0]); + } + + [Fact] + public void GetElementTypes_ListType_ReturnsElementType() + { + // Arrange + var type = typeof(List); + + // Act + var result = ElementTypeHelper.GetElementTypes(type); + + // Assert + Assert.Single(result); + Assert.Equal(typeof(int), result[0]); + } + + [Fact] + public void GetElementTypes_DictionaryTypeWithoutFlag_ReturnsKeyValuePairType() + { + // Arrange + var type = typeof(Dictionary); + + // Act + var result = ElementTypeHelper.GetElementTypes(type, ElementTypeFlags.None); + + // Assert + Assert.Single(result); + Assert.Equal(typeof(KeyValuePair), result[0]); + } + + [Fact] + public void GetElementTypes_DictionaryTypeWithBreakFlag_ReturnsKeyAndValueTypes() + { + // Arrange + var type = typeof(Dictionary); + + // Act + var result = ElementTypeHelper.GetElementTypes(type, ElementTypeFlags.BreakKeyValuePair); + + // Assert + Assert.Equal(2, result.Length); + Assert.Equal(typeof(int), result[0]); + Assert.Equal(typeof(string), result[1]); + } + + [Fact] + public void GetElementTypes_IDictionaryTypeWithBreakFlag_ReturnsKeyAndValueTypes() + { + // Arrange + var type = typeof(IDictionary); + + // Act + var result = ElementTypeHelper.GetElementTypes(type, ElementTypeFlags.BreakKeyValuePair); + + // Assert + Assert.Equal(2, result.Length); + Assert.Equal(typeof(string), result[0]); + Assert.Equal(typeof(double), result[1]); + } + + [Fact] + public void GetElementTypes_ReadOnlyDictionaryTypeWithBreakFlag_ReturnsKeyAndValueTypes() + { + // Arrange + var type = typeof(IReadOnlyDictionary); + + // Act + var result = ElementTypeHelper.GetElementTypes(type, ElementTypeFlags.BreakKeyValuePair); + + // Assert + Assert.Equal(2, result.Length); + Assert.Equal(typeof(long), result[0]); + Assert.Equal(typeof(bool), result[1]); + } + + [Fact] + public void GetElementTypes_ReadOnlyDictionaryTypeWithoutFlag_ReturnsKeyValuePairType() + { + // Arrange + var type = typeof(IReadOnlyDictionary); + + // Act + var result = ElementTypeHelper.GetElementTypes(type, ElementTypeFlags.None); + + // Assert + Assert.Single(result); + Assert.Equal(typeof(KeyValuePair), result[0]); + } + + [Fact] + public void GetElementTypes_NonEnumerableType_ThrowsArgumentException() + { + // Arrange + var type = typeof(int); + + // Act & Assert + var exception = Assert.Throws(() => ElementTypeHelper.GetElementTypes(type)); + Assert.Contains("Unable to find the element type", exception.Message); + } + + #endregion + + #region GetElementTypes with Enumerable Tests + + [Fact] + public void GetElementTypes_WithEnumerableContainingIntegers_ReturnsIntType() + { + // Arrange + var type = typeof(ArrayList); + IEnumerable enumerable = new ArrayList { 1, 2, 3 }; + + // Act + var result = ElementTypeHelper.GetElementTypes(type, enumerable); + + // Assert + Assert.Single(result); + Assert.Equal(typeof(int), result[0]); + } + + [Fact] + public void GetElementTypes_WithEnumerableContainingStrings_ReturnsStringType() + { + // Arrange + var type = typeof(ArrayList); + IEnumerable enumerable = new ArrayList { "test", "data" }; + + // Act + var result = ElementTypeHelper.GetElementTypes(type, enumerable); + + // Assert + Assert.Single(result); + Assert.Equal(typeof(string), result[0]); + } + + [Fact] + public void GetElementTypes_WithEmptyEnumerable_ReturnsObjectType() + { + // Arrange + var type = typeof(ArrayList); + IEnumerable enumerable = new ArrayList(); + + // Act + var result = ElementTypeHelper.GetElementTypes(type, enumerable); + + // Assert + Assert.Single(result); + Assert.Equal(typeof(object), result[0]); + } + + [Fact] + public void GetElementTypes_WithNullEnumerable_ReturnsObjectType() + { + // Arrange + var type = typeof(ArrayList); + IEnumerable enumerable = null; + + // Act + var result = ElementTypeHelper.GetElementTypes(type, enumerable); + + // Assert + Assert.Single(result); + Assert.Equal(typeof(object), result[0]); + } + + #endregion + + #region GetReadOnlyDictionaryType Tests + + [Fact] + public void GetReadOnlyDictionaryType_ReadOnlyDictionaryType_ReturnsGenericInterface() + { + // Arrange + var type = typeof(IReadOnlyDictionary); + + // Act + var result = type.GetReadOnlyDictionaryType(); + + // Assert + Assert.NotNull(result); + Assert.True(result.IsGenericType); + Assert.Equal(typeof(IReadOnlyDictionary<,>), result.GetGenericTypeDefinition()); + } + + [Fact] + public void GetReadOnlyDictionaryType_NonReadOnlyDictionaryType_ReturnsNull() + { + // Arrange + var type = typeof(List); + + // Act + var result = type.GetReadOnlyDictionaryType(); + + // Assert + Assert.Null(result); + } + + [Fact] + public void GetReadOnlyDictionaryType_RegularDictionaryType_ImplementsIReadOnlyDictionaryDoesNotReturnNull() + { + // Arrange + var type = typeof(Dictionary); + + // Act + var result = type.GetReadOnlyDictionaryType(); + + // Assert + Assert.NotNull(result); + } + + #endregion + + #region GetDictionaryType Tests + + [Fact] + public void GetDictionaryType_DictionaryType_ReturnsGenericInterface() + { + // Arrange + var type = typeof(Dictionary); + + // Act + var result = type.GetDictionaryType(); + + // Assert + Assert.NotNull(result); + Assert.True(result.IsGenericType); + Assert.Equal(typeof(IDictionary<,>), result.GetGenericTypeDefinition()); + } + + [Fact] + public void GetDictionaryType_IDictionaryType_ReturnsGenericInterface() + { + // Arrange + var type = typeof(IDictionary); + + // Act + var result = type.GetDictionaryType(); + + // Assert + Assert.NotNull(result); + Assert.True(result.IsGenericType); + Assert.Equal(typeof(IDictionary<,>), result.GetGenericTypeDefinition()); + } + + [Fact] + public void GetDictionaryType_NonDictionaryType_ReturnsNull() + { + // Arrange + var type = typeof(List); + + // Act + var result = type.GetDictionaryType(); + + // Assert + Assert.Null(result); + } + + [Fact] + public void GetDictionaryType_ReadOnlyDictionaryType_ReturnsNull() + { + // Arrange + var type = typeof(IReadOnlyDictionary); + + // Act + var result = type.GetDictionaryType(); + + // Assert + Assert.Null(result); + } + + #endregion + + #region ToType Tests + + [Fact] + public void ToType_SameType_ReturnsOriginalExpression() + { + // Arrange + Expression> expression = () => 42; + var body = expression.Body; + var type = typeof(int); + + // Act + var result = ElementTypeHelper.ToType(body, type); + + // Assert + Assert.Same(body, result); + } + + [Fact] + public void ToType_DifferentType_ReturnsConvertExpression() + { + // Arrange + Expression> expression = () => 42; + var body = expression.Body; + var type = typeof(long); + + // Act + var result = ElementTypeHelper.ToType(body, type); + + // Assert + Assert.NotSame(body, result); + Assert.IsType(result); + Assert.Equal(ExpressionType.Convert, result.NodeType); + Assert.Equal(typeof(long), result.Type); + } + + [Fact] + public void ToType_ConvertIntToObject_ReturnsConvertExpression() + { + // Arrange + Expression> expression = () => 5; + var body = expression.Body; + var type = typeof(object); + + // Act + var result = ElementTypeHelper.ToType(body, type); + + // Assert + Assert.IsType(result); + Assert.Equal(typeof(object), result.Type); + } + + [Fact] + public void ToType_ConvertStringToObject_ReturnsConvertExpression() + { + // Arrange + Expression> expression = () => "test"; + var body = expression.Body; + var type = typeof(object); + + // Act + var result = ElementTypeHelper.ToType(body, type); + + // Assert + Assert.IsType(result); + Assert.Equal(typeof(object), result.Type); + } + + #endregion + } +} \ No newline at end of file diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/LockingConcurrentDictionaryTest.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/LockingConcurrentDictionaryTest.cs new file mode 100644 index 0000000..5dfc331 --- /dev/null +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/LockingConcurrentDictionaryTest.cs @@ -0,0 +1,243 @@ +using System; +using Xunit; + +namespace AutoMapper.Extensions.ExpressionMapping.UnitTests +{ + public class LockingConcurrentDictionaryTest + { + [Fact] + public void GetOrAdd_WithKey_ReturnsValue() + { + // Arrange + var dictionary = new LockingConcurrentDictionary(key => $"Value{key}"); + + // Act + var result = dictionary.GetOrAdd(1); + + // Assert + Assert.Equal("Value1", result); + } + + [Fact] + public void GetOrAdd_WithSameKey_ReturnsSameValue() + { + // Arrange + var dictionary = new LockingConcurrentDictionary(key => $"Value{key}"); + + // Act + var result1 = dictionary.GetOrAdd(1); + var result2 = dictionary.GetOrAdd(1); + + // Assert + Assert.Equal(result1, result2); + Assert.Equal("Value1", result1); + } + + [Fact] + public void GetOrAdd_WithValueFactory_ReturnsValue() + { + // Arrange + var dictionary = new LockingConcurrentDictionary(key => $"Value{key}"); + + // Act + var result = dictionary.GetOrAdd(1, k => new Lazy(() => "CustomValue")); + + // Assert + Assert.Equal("CustomValue", result); + } + + [Fact] + public void GetOrAdd_ValueFactoryCalledOnlyOnce() + { + // Arrange + var callCount = 0; + var dictionary = new LockingConcurrentDictionary(key => + { + callCount++; + return $"Value{key}"; + }); + + // Act + var result1 = dictionary.GetOrAdd(1); + var result2 = dictionary.GetOrAdd(1); + + // Assert + Assert.Equal(1, callCount); + Assert.Equal("Value1", result1); + Assert.Equal("Value1", result2); + } + + [Fact] + public void Indexer_Get_ReturnsValue() + { + // Arrange + var dictionary = new LockingConcurrentDictionary(key => $"Value{key}"); + dictionary.GetOrAdd(1); + + // Act + var result = dictionary[1]; + + // Assert + Assert.Equal("Value1", result); + } + + [Fact] + public void Indexer_Set_UpdatesValue() + { + // Arrange + var dictionary = new LockingConcurrentDictionary(key => $"Value{key}"); + dictionary.GetOrAdd(1); + + // Act + dictionary[1] = "UpdatedValue"; + var result = dictionary[1]; + + // Assert + Assert.Equal("UpdatedValue", result); + } + + [Fact] + public void TryGetValue_ExistingKey_ReturnsTrue() + { + // Arrange + var dictionary = new LockingConcurrentDictionary(key => $"Value{key}"); + dictionary.GetOrAdd(1); + + // Act + var exists = dictionary.TryGetValue(1, out var value); + + // Assert + Assert.True(exists); + Assert.Equal("Value1", value); + } + + [Fact] + public void TryGetValue_NonExistingKey_ReturnsFalse() + { + // Arrange + var dictionary = new LockingConcurrentDictionary(key => $"Value{key}"); + + // Act + var exists = dictionary.TryGetValue(1, out var value); + + // Assert + Assert.False(exists); + Assert.Null(value); + } + + [Fact] + public void TryGetValue_ReferenceType_DefaultIsNull() + { + // Arrange + var dictionary = new LockingConcurrentDictionary(key => $"Value{key}"); + + // Act + var exists = dictionary.TryGetValue(99, out var value); + + // Assert + Assert.False(exists); + Assert.Null(value); + } + + [Fact] + public void TryGetValue_ValueType_DefaultIsZero() + { + // Arrange + var dictionary = new LockingConcurrentDictionary(key => key.Length); + + // Act + var exists = dictionary.TryGetValue("nonexistent", out var value); + + // Assert + Assert.False(exists); + Assert.Equal(0, value); + } + + [Fact] + public void ContainsKey_ExistingKey_ReturnsTrue() + { + // Arrange + var dictionary = new LockingConcurrentDictionary(key => $"Value{key}"); + dictionary.GetOrAdd(1); + + // Act + var contains = dictionary.ContainsKey(1); + + // Assert + Assert.True(contains); + } + + [Fact] + public void ContainsKey_NonExistingKey_ReturnsFalse() + { + // Arrange + var dictionary = new LockingConcurrentDictionary(key => $"Value{key}"); + + // Act + var contains = dictionary.ContainsKey(1); + + // Assert + Assert.False(contains); + } + + [Fact] + public void Keys_ReturnsAllKeys() + { + // Arrange + var dictionary = new LockingConcurrentDictionary(key => $"Value{key}"); + dictionary.GetOrAdd(1); + dictionary.GetOrAdd(2); + dictionary.GetOrAdd(3); + + // Act + var keys = dictionary.Keys; + + // Assert + Assert.Equal(3, keys.Count); + Assert.Contains(1, keys); + Assert.Contains(2, keys); + Assert.Contains(3, keys); + } + + [Fact] + public void Keys_EmptyDictionary_ReturnsEmptyCollection() + { + // Arrange + var dictionary = new LockingConcurrentDictionary(key => $"Value{key}"); + + // Act + var keys = dictionary.Keys; + + // Assert + Assert.Empty(keys); + } + + [Fact] + public void GetOrAdd_WithMultipleDifferentKeys_ReturnsCorrectValues() + { + // Arrange + var dictionary = new LockingConcurrentDictionary(key => $"Value{key}"); + + // Act + var result1 = dictionary.GetOrAdd(1); + var result2 = dictionary.GetOrAdd(2); + var result3 = dictionary.GetOrAdd(3); + + // Assert + Assert.Equal("Value1", result1); + Assert.Equal("Value2", result2); + Assert.Equal("Value3", result3); + } + + [Fact] + public void Constructor_WithNullValueFactory_ThrowsException() + { + // This test verifies the behavior when a null factory is used + // The actual exception will be thrown when trying to add a value + var dictionary = new LockingConcurrentDictionary(null); + + // Act & Assert + Assert.Throws(() => dictionary.GetOrAdd(1)); + } + } +} \ No newline at end of file diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/Structures/DeclaringMemberKeyTest.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/Structures/DeclaringMemberKeyTest.cs new file mode 100644 index 0000000..85c8cb9 --- /dev/null +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/Structures/DeclaringMemberKeyTest.cs @@ -0,0 +1,251 @@ +using System; +using System.Reflection; +using AutoMapper.Extensions.ExpressionMapping.Structures; +using Xunit; + +namespace AutoMapper.Extensions.ExpressionMapping.UnitTests.Structures +{ + public class DeclaringMemberKeyTest + { + private readonly MemberInfo _testMemberInfo1; + private readonly MemberInfo _testMemberInfo2; + private const string TestFullName1 = "TestNamespace.TestClass.TestMember1"; + private const string TestFullName2 = "TestNamespace.TestClass.TestMember2"; + + public DeclaringMemberKeyTest() + { + _testMemberInfo1 = typeof(TestClass).GetProperty(nameof(TestClass.Property1)); + _testMemberInfo2 = typeof(TestClass).GetProperty(nameof(TestClass.Property2)); + } + + #region Constructor Tests + + [Fact] + public void Constructor_InitializesPropertiesCorrectly() + { + // Act + var key = new DeclaringMemberKey(_testMemberInfo1, TestFullName1); + + // Assert + Assert.Equal(_testMemberInfo1, key.DeclaringMemberInfo); + Assert.Equal(TestFullName1, key.DeclaringMemberFullName); + } + + #endregion + + #region Equals Tests + + [Fact] + public void Equals_SameReference_ReturnsTrue() + { + // Arrange + var key = new DeclaringMemberKey(_testMemberInfo1, TestFullName1); + + // Act + var result = key.Equals(key); + + // Assert + Assert.True(result); + } + + [Fact] + public void Equals_NullObject_ReturnsFalse() + { + // Arrange + var key = new DeclaringMemberKey(_testMemberInfo1, TestFullName1); + + // Act + var result = key.Equals((object)null); + + // Assert + Assert.False(result); + } + + [Fact] + public void Equals_NullDeclaringMemberKey_ReturnsFalse() + { + // Arrange + var key = new DeclaringMemberKey(_testMemberInfo1, TestFullName1); + + // Act + var result = key.Equals((DeclaringMemberKey)null); + + // Assert + Assert.False(result); + } + + [Fact] + public void Equals_DifferentTypeObject_ReturnsFalse() + { + // Arrange + var key = new DeclaringMemberKey(_testMemberInfo1, TestFullName1); + var otherObject = "some string"; + + // Act + var result = key.Equals(otherObject); + + // Assert + Assert.False(result); + } + + [Fact] + public void Equals_SameValues_ReturnsTrue() + { + // Arrange + var key1 = new DeclaringMemberKey(_testMemberInfo1, TestFullName1); + var key2 = new DeclaringMemberKey(_testMemberInfo1, TestFullName1); + + // Act + var result = key1.Equals(key2); + + // Assert + Assert.True(result); + } + + [Fact] + public void Equals_DifferentMemberInfo_ReturnsFalse() + { + // Arrange + var key1 = new DeclaringMemberKey(_testMemberInfo1, TestFullName1); + var key2 = new DeclaringMemberKey(_testMemberInfo2, TestFullName1); + + // Act + var result = key1.Equals(key2); + + // Assert + Assert.False(result); + } + + [Fact] + public void Equals_DifferentFullName_ReturnsFalse() + { + // Arrange + var key1 = new DeclaringMemberKey(_testMemberInfo1, TestFullName1); + var key2 = new DeclaringMemberKey(_testMemberInfo1, TestFullName2); + + // Act + var result = key1.Equals(key2); + + // Assert + Assert.False(result); + } + + [Fact] + public void Equals_DifferentMemberInfoAndFullName_ReturnsFalse() + { + // Arrange + var key1 = new DeclaringMemberKey(_testMemberInfo1, TestFullName1); + var key2 = new DeclaringMemberKey(_testMemberInfo2, TestFullName2); + + // Act + var result = key1.Equals(key2); + + // Assert + Assert.False(result); + } + + [Fact] + public void Equals_ObjectOverload_SameValues_ReturnsTrue() + { + // Arrange + var key1 = new DeclaringMemberKey(_testMemberInfo1, TestFullName1); + object key2 = new DeclaringMemberKey(_testMemberInfo1, TestFullName1); + + // Act + var result = key1.Equals(key2); + + // Assert + Assert.True(result); + } + + #endregion + + #region GetHashCode Tests + + [Fact] + public void GetHashCode_SameMemberInfo_ReturnsSameHashCode() + { + // Arrange + var key1 = new DeclaringMemberKey(_testMemberInfo1, TestFullName1); + var key2 = new DeclaringMemberKey(_testMemberInfo1, TestFullName2); + + // Act + var hash1 = key1.GetHashCode(); + var hash2 = key2.GetHashCode(); + + // Assert + Assert.Equal(hash1, hash2); + } + + [Fact] + public void GetHashCode_EqualObjects_ReturnsSameHashCode() + { + // Arrange + var key1 = new DeclaringMemberKey(_testMemberInfo1, TestFullName1); + var key2 = new DeclaringMemberKey(_testMemberInfo1, TestFullName1); + + // Act + var hash1 = key1.GetHashCode(); + var hash2 = key2.GetHashCode(); + + // Assert + Assert.Equal(hash1, hash2); + } + + [Fact] + public void GetHashCode_IsConsistent() + { + // Arrange + var key = new DeclaringMemberKey(_testMemberInfo1, TestFullName1); + + // Act + var hash1 = key.GetHashCode(); + var hash2 = key.GetHashCode(); + + // Assert + Assert.Equal(hash1, hash2); + } + + #endregion + + #region ToString Tests + + [Fact] + public void ToString_ReturnsDeclaringMemberFullName() + { + // Arrange + var key = new DeclaringMemberKey(_testMemberInfo1, TestFullName1); + + // Act + var result = key.ToString(); + + // Assert + Assert.Equal(TestFullName1, result); + } + + [Fact] + public void ToString_WithDifferentFullName_ReturnsCorrectValue() + { + // Arrange + var key = new DeclaringMemberKey(_testMemberInfo1, TestFullName2); + + // Act + var result = key.ToString(); + + // Assert + Assert.Equal(TestFullName2, result); + } + + #endregion + + #region Helper Class + + private class TestClass + { + public string Property1 { get; set; } + public int Property2 { get; set; } + } + + #endregion + } +} \ No newline at end of file diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/TypeExtensionsTest.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/TypeExtensionsTest.cs new file mode 100644 index 0000000..328f69c --- /dev/null +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/TypeExtensionsTest.cs @@ -0,0 +1,733 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Xunit; + +namespace AutoMapper.Extensions.ExpressionMapping.UnitTests +{ + public class TypeExtensionsTest + { + #region Has Tests + + [AttributeUsage(AttributeTargets.Class)] + private class TestAttribute : Attribute { } + + [Test] + private class ClassWithAttribute { } + + private class ClassWithoutAttribute { } + + [Fact] + public void Has_TypeWithAttribute_ReturnsTrue() + { + // Arrange + var type = typeof(ClassWithAttribute); + + // Act + var result = type.Has(); + + // Assert + Assert.True(result); + } + + [Fact] + public void Has_TypeWithoutAttribute_ReturnsFalse() + { + // Arrange + var type = typeof(ClassWithoutAttribute); + + // Act + var result = type.Has(); + + // Assert + Assert.False(result); + } + + #endregion + + #region GetDeclaredConstructors Tests + + private class ClassWithMultipleConstructors + { + public ClassWithMultipleConstructors() { } + public ClassWithMultipleConstructors(int value) { } + public ClassWithMultipleConstructors(string text, int value) { } + } + + [Fact] + public void GetDeclaredConstructors_ReturnsAllConstructors() + { + // Arrange + var type = typeof(ClassWithMultipleConstructors); + + // Act + var constructors = type.GetDeclaredConstructors().ToList(); + + // Assert + Assert.Equal(3, constructors.Count); + } + + #endregion + + #region GetDeclaredMethod Tests + + private class ClassWithMethods + { + public void TestMethod() { } + public void AnotherMethod(int value) { } + } + + [Fact] + public void GetDeclaredMethod_ExistingMethod_ReturnsMethod() + { + // Arrange + var type = typeof(ClassWithMethods); + + // Act + var method = type.GetDeclaredMethod("TestMethod"); + + // Assert + Assert.NotNull(method); + Assert.Equal("TestMethod", method.Name); + } + + [Fact] + public void GetDeclaredMethod_NonExistingMethod_ReturnsNull() + { + // Arrange + var type = typeof(ClassWithMethods); + + // Act + var method = type.GetDeclaredMethod("NonExistingMethod"); + + // Assert + Assert.Null(method); + } + + #endregion + + #region GetDeclaredConstructor Tests + + [Fact] + public void GetDeclaredConstructor_WithMatchingParameters_ReturnsConstructor() + { + // Arrange + var type = typeof(ClassWithMultipleConstructors); + + // Act + var constructor = type.GetDeclaredConstructor(new[] { typeof(int) }); + + // Assert + Assert.NotNull(constructor); + Assert.Single(constructor.GetParameters()); + Assert.Equal(typeof(int), constructor.GetParameters()[0].ParameterType); + } + + [Fact] + public void GetDeclaredConstructor_WithNonMatchingParameters_ReturnsNull() + { + // Arrange + var type = typeof(ClassWithMultipleConstructors); + + // Act + var constructor = type.GetDeclaredConstructor(new[] { typeof(double) }); + + // Assert + Assert.Null(constructor); + } + + [Fact] + public void GetDeclaredConstructor_WithMultipleParameters_ReturnsCorrectConstructor() + { + // Arrange + var type = typeof(ClassWithMultipleConstructors); + + // Act + var constructor = type.GetDeclaredConstructor(new[] { typeof(string), typeof(int) }); + + // Assert + Assert.NotNull(constructor); + Assert.Equal(2, constructor.GetParameters().Length); + } + + #endregion + + #region GetAllMethods Tests + + [Fact] + public void GetAllMethods_ReturnsAllRuntimeMethods() + { + // Arrange + var type = typeof(ClassWithMethods); + + // Act + var methods = type.GetAllMethods().ToList(); + + // Assert + Assert.NotEmpty(methods); + Assert.Contains(methods, m => m.Name == "TestMethod"); + Assert.Contains(methods, m => m.Name == "AnotherMethod"); + } + + #endregion + + #region GetDeclaredProperties Tests + + private class ClassWithProperties + { + public int Property1 { get; set; } + public string Property2 { get; set; } + } + + [Fact] + public void GetDeclaredProperties_ReturnsAllDeclaredProperties() + { + // Arrange + var type = typeof(ClassWithProperties); + + // Act + var properties = type.GetDeclaredProperties().ToList(); + + // Assert + Assert.Equal(2, properties.Count); + Assert.Contains(properties, p => p.Name == "Property1"); + Assert.Contains(properties, p => p.Name == "Property2"); + } + + #endregion + + #region IsStatic Tests + + private class ClassWithStaticMembers + { + public static int StaticField = 0; + public int InstanceField = 0; + public static int StaticProperty { get; set; } + public int InstanceProperty { get; set; } + public static void StaticMethod() { } + public void InstanceMethod() { } + } + + [Fact] + public void IsStatic_StaticField_ReturnsTrue() + { + // Arrange + var field = typeof(ClassWithStaticMembers).GetField("StaticField"); + + // Act + var result = field.IsStatic(); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsStatic_InstanceField_ReturnsFalse() + { + // Arrange + var field = typeof(ClassWithStaticMembers).GetField("InstanceField"); + + // Act + var result = field.IsStatic(); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsStatic_NullField_ReturnsFalse() + { + // Arrange + FieldInfo field = null; + + // Act + var result = field.IsStatic(); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsStatic_StaticProperty_ReturnsTrue() + { + // Arrange + var property = typeof(ClassWithStaticMembers).GetProperty("StaticProperty"); + + // Act + var result = property.IsStatic(); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsStatic_InstanceProperty_ReturnsFalse() + { + // Arrange + var property = typeof(ClassWithStaticMembers).GetProperty("InstanceProperty"); + + // Act + var result = property.IsStatic(); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsStatic_NullProperty_ReturnsFalse() + { + // Arrange + PropertyInfo property = null; + + // Act + var result = property.IsStatic(); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsStatic_StaticMethod_ReturnsTrue() + { + // Arrange + MemberInfo member = typeof(ClassWithStaticMembers).GetMethod("StaticMethod"); + + // Act + var result = member.IsStatic(); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsStatic_InstanceMethod_ReturnsFalse() + { + // Arrange + MemberInfo member = typeof(ClassWithStaticMembers).GetMethod("InstanceMethod"); + + // Act + var result = member.IsStatic(); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsStatic_MemberInfoAsStaticField_ReturnsTrue() + { + // Arrange + MemberInfo member = typeof(ClassWithStaticMembers).GetField("StaticField"); + + // Act + var result = member.IsStatic(); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsStatic_MemberInfoAsStaticProperty_ReturnsTrue() + { + // Arrange + MemberInfo member = typeof(ClassWithStaticMembers).GetProperty("StaticProperty"); + + // Act + var result = member.IsStatic(); + + // Assert + Assert.True(result); + } + + #endregion + + #region IsEnum Tests + + private enum TestEnum { Value1, Value2 } + + [Fact] + public void IsEnum_EnumType_ReturnsTrue() + { + // Arrange + var type = typeof(TestEnum); + + // Act + var result = type.IsEnum(); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsEnum_NonEnumType_ReturnsFalse() + { + // Arrange + var type = typeof(int); + + // Act + var result = type.IsEnum(); + + // Assert + Assert.False(result); + } + + #endregion + + #region IsGenericType Tests + + [Fact] + public void IsGenericType_GenericType_ReturnsTrue() + { + // Arrange + var type = typeof(List); + + // Act + var result = type.IsGenericType(); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsGenericType_NonGenericType_ReturnsFalse() + { + // Arrange + var type = typeof(int); + + // Act + var result = type.IsGenericType(); + + // Assert + Assert.False(result); + } + + #endregion + + #region IsPrimitive Tests + + [Fact] + public void IsPrimitive_PrimitiveType_ReturnsTrue() + { + // Arrange + var type = typeof(int); + + // Act + var result = type.IsPrimitive(); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsPrimitive_NonPrimitiveType_ReturnsFalse() + { + // Arrange + var type = typeof(string); + + // Act + var result = type.IsPrimitive(); + + // Assert + Assert.False(result); + } + + #endregion + + #region IsValueType Tests + + [Fact] + public void IsValueType_StructType_ReturnsTrue() + { + // Arrange + var type = typeof(int); + + // Act + var result = type.IsValueType(); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsValueType_ClassType_ReturnsFalse() + { + // Arrange + var type = typeof(string); + + // Act + var result = type.IsValueType(); + + // Assert + Assert.False(result); + } + + #endregion + + #region IsLiteralType Tests + + [Theory] + [InlineData(typeof(bool))] + [InlineData(typeof(DateTime))] + [InlineData(typeof(DateTimeOffset))] + [InlineData(typeof(TimeSpan))] + [InlineData(typeof(Guid))] + [InlineData(typeof(decimal))] + [InlineData(typeof(byte))] + [InlineData(typeof(short))] + [InlineData(typeof(int))] + [InlineData(typeof(long))] + [InlineData(typeof(float))] + [InlineData(typeof(double))] + [InlineData(typeof(char))] + [InlineData(typeof(sbyte))] + [InlineData(typeof(ushort))] + [InlineData(typeof(uint))] + [InlineData(typeof(ulong))] + [InlineData(typeof(string))] + public void IsLiteralType_LiteralType_ReturnsTrue(Type type) + { + // Act + var result = type.IsLiteralType(); + + // Assert + Assert.True(result); + } + + [Theory] + [InlineData(typeof(int?))] + [InlineData(typeof(bool?))] + [InlineData(typeof(DateTime?))] + [InlineData(typeof(decimal?))] + public void IsLiteralType_NullableLiteralType_ReturnsTrue(Type type) + { + // Act + var result = type.IsLiteralType(); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsLiteralType_NonLiteralType_ReturnsFalse() + { + // Arrange + var type = typeof(ClassWithProperties); + + // Act + var result = type.IsLiteralType(); + + // Assert + Assert.False(result); + } + + #endregion + + #region IsQueryableType Tests + + [Fact] + public void IsQueryableType_QueryableType_ReturnsTrue() + { + // Arrange + var type = typeof(IQueryable); + + // Act + var result = type.IsQueryableType(); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsQueryableType_QueryableNonGenericType_ReturnsTrue() + { + // Arrange + var type = typeof(IQueryable); + + // Act + var result = type.IsQueryableType(); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsQueryableType_NonQueryableType_ReturnsFalse() + { + // Arrange + var type = typeof(List); + + // Act + var result = type.IsQueryableType(); + + // Assert + Assert.False(result); + } + + #endregion + + #region GetGenericElementType Tests + + [Fact] + public void GetGenericElementType_ArrayType_ReturnsElementType() + { + // Arrange + var type = typeof(int[]); + + // Act + var result = type.GetGenericElementType(); + + // Assert + Assert.Equal(typeof(int), result); + } + + [Fact] + public void GetGenericElementType_GenericType_ReturnsGenericArgument() + { + // Arrange + var type = typeof(List); + + // Act + var result = type.GetGenericElementType(); + + // Assert + Assert.Equal(typeof(string), result); + } + + #endregion + + #region IsEnumerableType Tests + + [Fact] + public void IsEnumerableType_ListType_ReturnsTrue() + { + // Arrange + var type = typeof(List); + + // Act + var result = type.IsEnumerableType(); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsEnumerableType_IEnumerableType_ReturnsTrue() + { + // Arrange + var type = typeof(IEnumerable); + + // Act + var result = type.IsEnumerableType(); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsEnumerableType_NonEnumerableType_ReturnsFalse() + { + // Arrange + var type = typeof(int); + + // Act + var result = type.IsEnumerableType(); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsEnumerableType_NonGenericEnumerableType_ReturnsFalse() + { + // Arrange + var type = typeof(System.Collections.IEnumerable); + + // Act + var result = type.IsEnumerableType(); + + // Assert + Assert.False(result); + } + + #endregion + + #region ReplaceItemType Tests + + [Fact] + public void ReplaceItemType_SimpleTypeMatch_ReturnsNewType() + { + // Arrange + var targetType = typeof(int); + var oldType = typeof(int); + var newType = typeof(string); + + // Act + var result = targetType.ReplaceItemType(oldType, newType); + + // Assert + Assert.Equal(typeof(string), result); + } + + [Fact] + public void ReplaceItemType_SimpleTypeNoMatch_ReturnsOriginalType() + { + // Arrange + var targetType = typeof(int); + var oldType = typeof(string); + var newType = typeof(double); + + // Act + var result = targetType.ReplaceItemType(oldType, newType); + + // Assert + Assert.Equal(typeof(int), result); + } + + [Fact] + public void ReplaceItemType_GenericType_ReplacesTypeArgument() + { + // Arrange + var targetType = typeof(List); + var oldType = typeof(int); + var newType = typeof(string); + + // Act + var result = targetType.ReplaceItemType(oldType, newType); + + // Assert + Assert.Equal(typeof(List), result); + } + + [Fact] + public void ReplaceItemType_NestedGenericType_ReplacesTypeArgument() + { + // Arrange + var targetType = typeof(Dictionary); + var oldType = typeof(int); + var newType = typeof(long); + + // Act + var result = targetType.ReplaceItemType(oldType, newType); + + // Assert + Assert.Equal(typeof(Dictionary), result); + } + + [Fact] + public void ReplaceItemType_MultipleGenericArguments_ReplacesAll() + { + // Arrange + var targetType = typeof(Dictionary); + var oldType = typeof(int); + var newType = typeof(string); + + // Act + var result = targetType.ReplaceItemType(oldType, newType); + + // Assert + Assert.Equal(typeof(Dictionary), result); + } + + #endregion + } +} \ No newline at end of file diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/TypeMappingsManagerTest.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/TypeMappingsManagerTest.cs new file mode 100644 index 0000000..48b4b61 --- /dev/null +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/TypeMappingsManagerTest.cs @@ -0,0 +1,690 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using AutoMapper; +using Xunit; + +namespace AutoMapper.Extensions.ExpressionMapping.UnitTests +{ + public class TypeMappingsManagerTest + { + #region Test Models + + private class SourceModel + { + public int Id { get; set; } + public string Name { get; set; } + public SourceChild Child { get; set; } + } + + private class DestModel + { + public int Id { get; set; } + public string Name { get; set; } + public DestChild Child { get; set; } + } + + private class SourceChild + { + public string Value { get; set; } + } + + private class DestChild + { + public string Value { get; set; } + } + + private class SourceItem + { + public int ItemId { get; set; } + } + + private class DestItem + { + public int ItemId { get; set; } + } + + #endregion + + #region Constructor Tests + + [Fact] + public void Constructor_ValidDelegateTypes_CreatesInstance() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + + // Act + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + + // Assert + Assert.NotNull(manager); + Assert.NotNull(manager.InfoDictionary); + Assert.NotNull(manager.TypeMappings); + } + + [Fact] + public void Constructor_NonDelegateSourceType_ThrowsArgumentException() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + + // Act & Assert + var exception = Assert.Throws(() => + new TypeMappingsManager(config, typeof(int), typeof(Func))); + + Assert.Contains("must be a deledate type", exception.Message); + } + + [Fact] + public void Constructor_NonDelegateDestType_ThrowsArgumentException() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + + // Act & Assert + var exception = Assert.Throws(() => + new TypeMappingsManager(config, typeof(Func), typeof(string))); + + Assert.Contains("must be a deledate type", exception.Message); + } + + [Fact] + public void Constructor_AddsMappingsFromDelegateTypes() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + + // Act + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + + // Assert + Assert.True(manager.TypeMappings.ContainsKey(typeof(SourceModel))); + Assert.Equal(typeof(DestModel), manager.TypeMappings[typeof(SourceModel)]); + } + + #endregion + + #region AddTypeMapping Tests + + [Fact] + public void AddTypeMapping_SimpleTypes_AddsMapping() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + + // Act + manager.AddTypeMapping(typeof(SourceChild), typeof(DestChild)); + + // Assert + Assert.True(manager.TypeMappings.ContainsKey(typeof(SourceChild))); + Assert.Equal(typeof(DestChild), manager.TypeMappings[typeof(SourceChild)]); + } + + [Fact] + public void AddTypeMapping_SameTypes_DoesNotAddMapping() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + var initialCount = manager.TypeMappings.Count; + + // Act + manager.AddTypeMapping(typeof(int), typeof(int)); + + // Assert + Assert.Equal(initialCount, manager.TypeMappings.Count); + } + + [Fact] + public void AddTypeMapping_ExpressionTypes_UnwrapsAndAddsMapping() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + + // Act + manager.AddTypeMapping( + typeof(Expression>), + typeof(Expression>)); + + // Assert + Assert.True(manager.TypeMappings.ContainsKey(typeof(Func))); + Assert.Equal(typeof(Func), manager.TypeMappings[typeof(Func)]); + } + + [Fact] + public void AddTypeMapping_DuplicateMapping_DoesNotAddAgain() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + + // Act + manager.AddTypeMapping(typeof(SourceChild), typeof(DestChild)); + var countAfterFirst = manager.TypeMappings.Count; + manager.AddTypeMapping(typeof(SourceChild), typeof(DestChild)); + + // Assert + Assert.Equal(countAfterFirst, manager.TypeMappings.Count); + } + + [Fact] + public void AddTypeMapping_ListTypes_AddsUnderlyingTypeMappings() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + + // Act + manager.AddTypeMapping(typeof(List), typeof(List)); + + // Assert + Assert.True(manager.TypeMappings.ContainsKey(typeof(List))); + Assert.True(manager.TypeMappings.ContainsKey(typeof(SourceItem))); + Assert.Equal(typeof(DestItem), manager.TypeMappings[typeof(SourceItem)]); + } + + [Fact] + public void AddTypeMapping_ArrayTypes_AddsUnderlyingTypeMappings() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + + // Act + manager.AddTypeMapping(typeof(SourceItem[]), typeof(DestItem[])); + + // Assert + Assert.True(manager.TypeMappings.ContainsKey(typeof(SourceItem[]))); + Assert.True(manager.TypeMappings.ContainsKey(typeof(SourceItem))); + Assert.Equal(typeof(DestItem), manager.TypeMappings[typeof(SourceItem)]); + } + + #endregion + + #region ReplaceType Tests + + [Fact] + public void ReplaceType_MappedType_ReturnsDestinationType() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + + // Act + var result = manager.ReplaceType(typeof(SourceModel)); + + // Assert + Assert.Equal(typeof(DestModel), result); + } + + [Fact] + public void ReplaceType_UnmappedType_ReturnsOriginalType() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + + // Act + var result = manager.ReplaceType(typeof(string)); + + // Assert + Assert.Equal(typeof(string), result); + } + + [Fact] + public void ReplaceType_ArrayTypeWithMapping_ReturnsReplacedArrayType() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + manager.AddTypeMapping(typeof(SourceItem), typeof(DestItem)); + + // Act + var result = manager.ReplaceType(typeof(SourceItem[])); + + // Assert + Assert.Equal(typeof(DestItem[]), result); + } + + [Fact] + public void ReplaceType_MultiDimensionalArrayWithMapping_ReturnsReplacedArrayType() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + manager.AddTypeMapping(typeof(SourceItem), typeof(DestItem)); + + // Act + var result = manager.ReplaceType(typeof(SourceItem[,])); + + // Assert + Assert.Equal(typeof(DestItem[,]), result); + Assert.Equal(2, result.GetArrayRank()); + } + + [Fact] + public void ReplaceType_GenericTypeWithMapping_ReturnsReplacedGenericType() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + manager.AddTypeMapping(typeof(SourceItem), typeof(DestItem)); + + // Act + var result = manager.ReplaceType(typeof(List)); + + // Assert + Assert.Equal(typeof(List), result); + } + + [Fact] + public void ReplaceType_GenericTypeWithMultipleArguments_ReplacesAllArguments() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + cfg.CreateMap(); + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + manager.AddTypeMapping(typeof(SourceItem), typeof(DestItem)); + manager.AddTypeMapping(typeof(SourceChild), typeof(DestChild)); + + // Act + var result = manager.ReplaceType(typeof(Dictionary)); + + // Assert + Assert.Equal(typeof(Dictionary), result); + } + + [Fact] + public void ReplaceType_DirectlyMappedArrayType_ReturnsDirectMapping() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + manager.AddTypeMapping(typeof(SourceItem[]), typeof(List)); + + // Act + var result = manager.ReplaceType(typeof(SourceItem[])); + + // Assert + Assert.Equal(typeof(List), result); + } + + [Fact] + public void ReplaceType_DirectlyMappedGenericType_ReturnsDirectMapping() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + manager.AddTypeMapping(typeof(List), typeof(HashSet)); + + // Act + var result = manager.ReplaceType(typeof(List)); + + // Assert + Assert.Equal(typeof(HashSet), result); + } + + #endregion + + #region GetDestinationParameterExpressions Tests + + [Fact] + public void GetDestinationParameterExpressions_SingleParameter_ReturnsDestinationParameter() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + + Expression> expression = s => s.Id > 0; + + // Act + var result = manager.GetDestinationParameterExpressions(expression); + + // Assert + Assert.Single(result); + Assert.Equal(typeof(DestModel), result[0].Type); + } + + [Fact] + public void GetDestinationParameterExpressions_MultipleParameters_ReturnsAllDestinationParameters() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + + var param1 = Expression.Parameter(typeof(SourceModel), "s"); + var param2 = Expression.Parameter(typeof(SourceChild), "c"); + var body = Expression.Constant(true); + var expression = Expression.Lambda>( + body, + param1, + param2); + + // Act + var result = manager.GetDestinationParameterExpressions(expression); + + // Assert + Assert.Equal(2, result.Count); + Assert.Equal(typeof(DestModel), result[0].Type); + Assert.Equal(typeof(DestChild), result[1].Type); + } + + [Fact] + public void GetDestinationParameterExpressions_UnmappedParameter_ReturnsOriginalParameterType() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + + Expression> expression = x => x > 0; + + // Act + var result = manager.GetDestinationParameterExpressions(expression); + + // Assert + Assert.Single(result); + Assert.Equal(typeof(int), result[0].Type); + } + + [Fact] + public void GetDestinationParameterExpressions_CalledTwiceWithSameParameter_ReturnsSameInstance() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + + var param = Expression.Parameter(typeof(SourceModel), "s"); + var body = Expression.Constant(true); + var expression = Expression.Lambda>(body, param); + + // Act + var result1 = manager.GetDestinationParameterExpressions(expression); + var result2 = manager.GetDestinationParameterExpressions(expression); + + // Assert + Assert.Same(result1[0], result2[0]); + } + + [Fact] + public void GetDestinationParameterExpressions_PreservesParameterName() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + + Expression> expression = myCustomName => myCustomName.Id > 0; + + // Act + var result = manager.GetDestinationParameterExpressions(expression); + + // Assert + Assert.Single(result); + Assert.Equal("myCustomName", result[0].Name); + } + + #endregion + + #region AddTypeMappingsFromDelegates Tests + + [Fact] + public void AddTypeMappingsFromDelegates_MatchingArgumentCounts_AddsMappings() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + + // Act + manager.AddTypeMappingsFromDelegates( + typeof(Func), + typeof(Func)); + + // Assert + Assert.True(manager.TypeMappings.ContainsKey(typeof(SourceChild))); + Assert.Equal(typeof(DestChild), manager.TypeMappings[typeof(SourceChild)]); + } + + [Fact] + public void AddTypeMappingsFromDelegates_MismatchedArgumentCounts_ThrowsArgumentException() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + + // Act & Assert + Assert.Throws(() => + manager.AddTypeMappingsFromDelegates( + typeof(Func), + typeof(Func))); + } + + [Fact] + public void AddTypeMappingsFromDelegates_ActionTypes_AddsMappings() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + cfg.CreateMap(); + }); + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + + // Act + manager.AddTypeMappingsFromDelegates( + typeof(Action), + typeof(Action)); + + // Assert + Assert.True(manager.TypeMappings.ContainsKey(typeof(SourceChild))); + Assert.Equal(typeof(DestChild), manager.TypeMappings[typeof(SourceChild)]); + } + + #endregion + + #region InfoDictionary Tests + + [Fact] + public void InfoDictionary_IsInitialized() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + + // Act + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + + // Assert + Assert.NotNull(manager.InfoDictionary); + Assert.IsType(manager.InfoDictionary); + } + + #endregion + + #region TypeMappings Tests + + [Fact] + public void TypeMappings_IsInitialized() + { + // Arrange + var config = ConfigurationHelper.GetMapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + + // Act + var manager = new TypeMappingsManager( + config, + typeof(Func), + typeof(Func)); + + // Assert + Assert.NotNull(manager.TypeMappings); + Assert.IsType>(manager.TypeMappings); + } + + #endregion + } +} \ No newline at end of file From 960551cfd80dc560e041582c059298ffe9d4a3c7 Mon Sep 17 00:00:00 2001 From: Blaise Taylor Date: Fri, 20 Feb 2026 12:02:56 -0500 Subject: [PATCH 4/5] Fixing VS messages in the source project. --- .../AnonymousTypeFactory.cs | 4 +- .../ConfigurationExtensions.cs | 1 - .../ElementTypeHelper.cs | 4 +- .../ExpressionExtensions.cs | 3 +- .../ExpressionMapper.cs | 47 +++--- .../GlobalSuppressions.cs | 13 ++ .../Impl/QueryDataSourceInjection.cs | 24 ++- .../Impl/SourceInjectedQueryProvider.cs | 91 +++++------ .../LockingConcurrentDictionary.cs | 16 +- .../MapperInfoDictionary.cs | 11 +- .../MemberVisitor.cs | 2 +- .../NullsafeQueryRewriter.cs | 4 +- .../PrependParentNameVisitor.cs | 15 +- .../ReplaceExpressionVisitor.cs | 12 +- .../Structures/DeclaringMemberKey.cs | 19 +-- .../Structures/MemberAssignmentInfo.cs | 16 +- .../Structures/MemberBindingGroup.cs | 17 +-- .../Structures/PropertyMapInfo.cs | 12 +- .../TypeMapHelper.cs | 16 +- .../XpressionMapperVisitor.cs | 143 ++++++++---------- .../ExpressionMapping.cs | 11 +- .../Impl/SourceInjectedQuery.cs | 5 +- .../XpressionMapper.Structs.Tests.cs | 4 +- .../XpressionMapperTests.cs | 6 +- 24 files changed, 208 insertions(+), 288 deletions(-) create mode 100644 src/AutoMapper.Extensions.ExpressionMapping/GlobalSuppressions.cs diff --git a/src/AutoMapper.Extensions.ExpressionMapping/AnonymousTypeFactory.cs b/src/AutoMapper.Extensions.ExpressionMapping/AnonymousTypeFactory.cs index 41c0063..b06de39 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/AnonymousTypeFactory.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/AnonymousTypeFactory.cs @@ -18,7 +18,7 @@ public static Type CreateAnonymousType(IEnumerable memberDetails) public static Type CreateAnonymousType(IDictionary memberDetails) { - AssemblyName dynamicAssemblyName = new AssemblyName("TempAssembly.AutoMapper.Extensions.ExpressionMapping"); + AssemblyName dynamicAssemblyName = new("TempAssembly.AutoMapper.Extensions.ExpressionMapping"); AssemblyBuilder dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(dynamicAssemblyName, AssemblyBuilderAccess.Run); ModuleBuilder dynamicModule = dynamicAssembly.DefineDynamicModule("TempAssembly.AutoMapper.Extensions.ExpressionMapping"); TypeBuilder typeBuilder = dynamicModule.DefineType(GetAnonymousTypeName(), TypeAttributes.Public); @@ -35,7 +35,7 @@ public static Type CreateAnonymousType(IDictionary memberDetails) FieldBuilder = typeBuilder.DefineField(string.Concat("_", memberName), memberType, FieldAttributes.Private), PropertyBuilder = typeBuilder.DefineProperty(memberName, PropertyAttributes.HasDefault, memberType, null), GetMethodBuilder = typeBuilder.DefineMethod(string.Concat("get_", memberName), getSetAttr, memberType, Type.EmptyTypes), - SetMethodBuilder = typeBuilder.DefineMethod(string.Concat("set_", memberName), getSetAttr, null, new Type[] { memberType }) + SetMethodBuilder = typeBuilder.DefineMethod(string.Concat("set_", memberName), getSetAttr, null, [memberType]) }; } ); diff --git a/src/AutoMapper.Extensions.ExpressionMapping/ConfigurationExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/ConfigurationExtensions.cs index bd15214..dac882a 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/ConfigurationExtensions.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/ConfigurationExtensions.cs @@ -1,5 +1,4 @@ using AutoMapper.Internal; -using AutoMapper.Mappers; namespace AutoMapper.Extensions.ExpressionMapping { diff --git a/src/AutoMapper.Extensions.ExpressionMapping/ElementTypeHelper.cs b/src/AutoMapper.Extensions.ExpressionMapping/ElementTypeHelper.cs index b942cf7..64b2eea 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/ElementTypeHelper.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/ElementTypeHelper.cs @@ -18,7 +18,7 @@ public static Type[] GetElementTypes(Type enumerableType, System.Collections.IEn { if (enumerableType.HasElementType) { - return new[] { enumerableType.GetElementType() }; + return [enumerableType.GetElementType()]; } var iDictionaryType = enumerableType.GetDictionaryType(); @@ -43,7 +43,7 @@ public static Type[] GetElementTypes(Type enumerableType, System.Collections.IEn { var first = enumerable?.Cast().FirstOrDefault(); - return new[] { first?.GetType() ?? typeof(object) }; + return [first?.GetType() ?? typeof(object)]; } throw new ArgumentException($"Unable to find the element type for type '{enumerableType}'.", diff --git a/src/AutoMapper.Extensions.ExpressionMapping/ExpressionExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/ExpressionExtensions.cs index daa4108..1629cf3 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/ExpressionExtensions.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/ExpressionExtensions.cs @@ -5,9 +5,8 @@ using System.Reflection; using AutoMapper.Internal; -namespace AutoMapper +namespace AutoMapper.Extensions.ExpressionMapping { - using AutoMapper.Extensions.ExpressionMapping; using static Expression; internal static class ExpressionExtensions diff --git a/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs b/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs index 6bdad7e..6186ddc 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs @@ -1,5 +1,4 @@ -using AutoMapper.Extensions.ExpressionMapping; -using AutoMapper.Internal; +using AutoMapper.Internal; using AutoMapper.Internal.Mappers; using System; using System.Collections.Generic; @@ -9,7 +8,7 @@ using System.Reflection; using static System.Linq.Expressions.Expression; -namespace AutoMapper.Mappers +namespace AutoMapper.Extensions.ExpressionMapping { public class ExpressionMapper : IObjectMapper { @@ -24,7 +23,7 @@ public bool IsMatch(TypePair context) => typeof(LambdaExpression).IsAssignableFr && typeof(LambdaExpression).IsAssignableFrom(context.DestinationType) && context.DestinationType != typeof(LambdaExpression); - public Expression MapExpression(IGlobalConfiguration configurationProvider, ProfileMap profileMap, MemberMap memberMap, Expression sourceExpression, Expression destExpression) + public Expression MapExpression(IGlobalConfiguration configurationProvider, ProfileMap profileMap, MemberMap memberMap, Expression sourceExpression, Expression destExpression) => Call ( null, @@ -41,7 +40,7 @@ public Expression MapExpression(IGlobalConfiguration configurationProvider, Prof [ExcludeFromCodeCoverage] internal class MappingVisitor : ExpressionVisitor { - private IList _destSubTypes = new Type[0]; + private IList _destSubTypes = []; private readonly IConfigurationProvider _configurationProvider; private readonly TypeMap _typeMap; @@ -83,7 +82,7 @@ private MethodCallExpression GetConvertedMethodCall(MethodCallExpression node) return Call(convertedMethodCall, convertedArguments); } - private static Type GetConvertingTypeIfExists(IList args, Type t, IList arguments) + private static Type GetConvertingTypeIfExists(System.Collections.ObjectModel.ReadOnlyCollection args, Type t, IList arguments) { var matchingArgument = args.Where(a => !a.Type.IsGenericType()).FirstOrDefault(a => a.Type == t); if (matchingArgument != null) @@ -111,7 +110,7 @@ protected override Expression VisitBinary(BinaryExpression node) CheckNullableToNonNullableChanges(node.Left, node.Right, ref newLeft, ref newRight); CheckNullableToNonNullableChanges(node.Right, node.Left, ref newRight, ref newLeft); return MakeBinary(node.NodeType, newLeft, newRight); - bool IsNullConstant(Expression expression) => expression is ConstantExpression constant && constant.Value == null; + static bool IsNullConstant(Expression expression) => expression is ConstantExpression constant && constant.Value == null; } private static void CheckNullableToNonNullableChanges(Expression left, Expression right, ref Expression newLeft, ref Expression newRight) @@ -147,30 +146,30 @@ private static void UpdateToNonNullableExpression(Expression right, out Expressi : right.Type; newRight = Constant(expression.Value, t); } - else if (right is UnaryExpression) - newRight = ((UnaryExpression) right).Operand; + else if (right is UnaryExpression unaryExpression) + newRight = unaryExpression.Operand; else throw new AutoMapperMappingException( "Mapping a BinaryExpression where one side is nullable and the other isn't"); } - private static bool GoingFromNonNullableToNullable(Expression node, Expression newLeft) + private static bool GoingFromNonNullableToNullable(Expression node, Expression newLeft) => !node.Type.IsNullableType() && newLeft.Type.IsNullableType(); - private static bool BothAreNullable(Expression node, Expression newLeft) + private static bool BothAreNullable(Expression node, Expression newLeft) => node.Type.IsNullableType() && newLeft.Type.IsNullableType(); - private static bool BothAreNonNullable(Expression node, Expression newLeft) + private static bool BothAreNonNullable(Expression node, Expression newLeft) => !node.Type.IsNullableType() && !newLeft.Type.IsNullableType(); protected override Expression VisitLambda(Expression node) { - return node.Parameters.Any(b => b.Type == _oldParam.Type) - ? VisitLambdaExpression(node) + return node.Parameters.Any(b => b.Type == _oldParam.Type) + ? VisitLambdaExpression(node) : VisitAllParametersExpression(node); } - private Expression VisitLambdaExpression(Expression expression) + private LambdaExpression VisitLambdaExpression(Expression expression) { var convertedBody = Visit(expression.Body); var convertedArguments = expression.Parameters.Select(e => Visit(e) as ParameterExpression).ToList(); @@ -254,22 +253,22 @@ private Expression GetConvertedSubMemberCall(MemberExpression node) var typeMap = _configurationProvider.Internal().ResolveTypeMap(sourceType, destType); var subVisitor = new MappingVisitor(_configurationProvider, typeMap, node.Expression, baseExpression, this); var newExpression = subVisitor.Visit(node); - _destSubTypes = _destSubTypes.Concat(subVisitor._destSubTypes).ToArray(); + _destSubTypes = [.. _destSubTypes, .. subVisitor._destSubTypes]; return newExpression; } - private Type GetSourceType(PropertyMap propertyMap) => + private static Type GetSourceType(PropertyMap propertyMap) => propertyMap.SourceType ?? throw new AutoMapperMappingException( - "Could not determine source property type. Make sure the property is mapped.", - null, + "Could not determine source property type. Make sure the property is mapped.", + null, propertyMap); private PropertyMap FindPropertyMapOfExpression(MemberExpression expression) { var propertyMap = PropertyMap(expression); - return propertyMap == null && expression.Expression is MemberExpression - ? FindPropertyMapOfExpression((MemberExpression) expression.Expression) + return propertyMap == null && expression.Expression is MemberExpression memberExpression + ? FindPropertyMapOfExpression(memberExpression) : propertyMap; } @@ -280,9 +279,9 @@ private PropertyMap PropertyMap(MemberExpression node) ? null : (!node.Member.DeclaringType.IsAssignableFrom(_typeMap.DestinationType) ? null - : GetExistingPropertyMapFor(node.Member, _typeMap))); + : MappingVisitor.GetExistingPropertyMapFor(node.Member, _typeMap))); - private PropertyMap GetExistingPropertyMapFor(MemberInfo destinationProperty, TypeMap typeMap) + private static PropertyMap GetExistingPropertyMapFor(MemberInfo destinationProperty, TypeMap typeMap) { if (!destinationProperty.DeclaringType.IsAssignableFrom(typeMap.DestinationType)) return null; @@ -293,7 +292,7 @@ private PropertyMap GetExistingPropertyMapFor(MemberInfo destinationProperty, Ty private void SetSourceSubTypes(PropertyMap propertyMap) { if (propertyMap.SourceMember is PropertyInfo info) - _destSubTypes = info.PropertyType.GetTypeInfo().GenericTypeArguments.Concat(new[] { info.PropertyType }).ToList(); + _destSubTypes = [.. info.PropertyType.GetTypeInfo().GenericTypeArguments, info.PropertyType]; else if (propertyMap.SourceMember is FieldInfo fInfo) _destSubTypes = fInfo.FieldType.GetTypeInfo().GenericTypeArguments; } diff --git a/src/AutoMapper.Extensions.ExpressionMapping/GlobalSuppressions.cs b/src/AutoMapper.Extensions.ExpressionMapping/GlobalSuppressions.cs new file mode 100644 index 0000000..61dd6a3 --- /dev/null +++ b/src/AutoMapper.Extensions.ExpressionMapping/GlobalSuppressions.cs @@ -0,0 +1,13 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0057:Use range operator", Justification = "Unavialable in .Net Standard 2.0.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.XpressionMapperVisitor.FindDestinationFullName(System.Type,System.Type,System.String,System.Collections.Generic.List{AutoMapper.Extensions.ExpressionMapping.Structures.PropertyMapInfo})")] +[assembly: SuppressMessage("Style", "IDE0056:Use index operator", Justification = "Unavialable in .Net Standard 2.0.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.XpressionMapperVisitor.FindDestinationFullName(System.Type,System.Type,System.String,System.Collections.Generic.List{AutoMapper.Extensions.ExpressionMapping.Structures.PropertyMapInfo})")] +[assembly: SuppressMessage("Style", "IDE0057:Use range operator", Justification = "Unavialable in .Net Standard 2.0.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.Impl.ConstantExpressionReplacementVisitor.VisitMember(System.Linq.Expressions.MemberExpression)~System.Linq.Expressions.Expression")] +[assembly: SuppressMessage("Maintainability", "CA1510:Use ArgumentNullException throw helper", Justification = "Unavialable in .Net Standard 2.0.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.NullsafeQueryRewriter.VisitMember(System.Linq.Expressions.MemberExpression)~System.Linq.Expressions.Expression")] +[assembly: SuppressMessage("Maintainability", "CA1510:Use ArgumentNullException throw helper", Justification = "Unavialable in .Net Standard 2.0.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.NullsafeQueryRewriter.VisitMethodCall(System.Linq.Expressions.MethodCallExpression)~System.Linq.Expressions.Expression")] +[assembly: SuppressMessage("Performance", "CA1864:Prefer the 'IDictionary.TryAdd(TKey, TValue)' method", Justification = "Unavialable in .Net Standard 2.0.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.XpressionMapperVisitor.ConfigureAnonymousTypeMaps(System.Type,System.Type)")] diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Impl/QueryDataSourceInjection.cs b/src/AutoMapper.Extensions.ExpressionMapping/Impl/QueryDataSourceInjection.cs index b6f7b92..e527cb7 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/Impl/QueryDataSourceInjection.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/Impl/QueryDataSourceInjection.cs @@ -49,12 +49,12 @@ public interface IQueryDataSourceInjection } [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - public class QueryDataSourceInjection : IQueryDataSourceInjection + public class QueryDataSourceInjection(IQueryable dataSource, IMapper mapper) : IQueryDataSourceInjection { - private readonly IQueryable _dataSource; - private readonly IMapper _mapper; - private readonly List _beforeMappingVisitors = new List(); - private readonly List _afterMappingVisitors = new List(); + private readonly IQueryable _dataSource = dataSource; + private readonly IMapper _mapper = mapper; + private readonly List _beforeMappingVisitors = []; + private readonly List _afterMappingVisitors = []; private readonly ExpressionVisitor _sourceExpressionTracer = null; private readonly ExpressionVisitor _destinationExpressionTracer = null; private Action _exceptionHandler = x => { }; @@ -62,12 +62,6 @@ public class QueryDataSourceInjection : IQueryDataSourceInjection dataSource, IMapper mapper) - { - _dataSource = dataSource; - _mapper = mapper; - } - public ISourceInjectedQueryable For() => CreateQueryable(); public ISourceInjectedQueryable For(object parameters, params Expression>[] membersToExpand) @@ -143,9 +137,9 @@ public IQueryDataSourceInjection OnError(Action exceptionHan return this; } - private ISourceInjectedQueryable CreateQueryable() => - new SourceSourceInjectedQuery(_dataSource, - new TDestination[0].AsQueryable(), + private SourceSourceInjectedQuery CreateQueryable() => + new(_dataSource, + Array.Empty().AsQueryable(), _mapper, _beforeMappingVisitors, _afterMappingVisitors, @@ -154,7 +148,7 @@ private ISourceInjectedQueryable CreateQueryable() = _membersToExpand, _inspector); - private static IObjectDictionary GetParameters(object parameters) + private static Dictionary GetParameters(object parameters) { return (parameters ?? new object()).GetType() .GetDeclaredProperties() diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs b/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs index a4d40c1..4e72a69 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs @@ -1,5 +1,4 @@ using AutoMapper.Internal; -using AutoMapper.Mappers; using AutoMapper.QueryableExtensions.Impl; using System; using System.Collections; @@ -16,34 +15,22 @@ namespace AutoMapper.Extensions.ExpressionMapping.Impl using ParameterBag = IDictionary; [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - public class SourceInjectedQueryProvider : IQueryProvider + public class SourceInjectedQueryProvider(IMapper mapper, + IQueryable dataSource, IQueryable destQuery, + IEnumerable beforeVisitors, + IEnumerable afterVisitors, + Action exceptionHandler, + IDictionary parameters, + MemberPaths membersToExpand) : IQueryProvider { - private readonly IMapper _mapper; - private readonly IQueryable _dataSource; - private readonly IQueryable _destQuery; - private readonly IEnumerable _beforeVisitors; - private readonly IEnumerable _afterVisitors; - private readonly IDictionary _parameters; - private readonly MemberPaths _membersToExpand; - private readonly Action _exceptionHandler; - - public SourceInjectedQueryProvider(IMapper mapper, - IQueryable dataSource, IQueryable destQuery, - IEnumerable beforeVisitors, - IEnumerable afterVisitors, - Action exceptionHandler, - IDictionary parameters, - MemberPaths membersToExpand) - { - _mapper = mapper; - _dataSource = dataSource; - _destQuery = destQuery; - _beforeVisitors = beforeVisitors; - _afterVisitors = afterVisitors; - _parameters = parameters ?? new Dictionary(); - _membersToExpand = membersToExpand ?? Enumerable.Empty>(); - _exceptionHandler = exceptionHandler ?? (x => { }); - } + private readonly IMapper _mapper = mapper; + private readonly IQueryable _dataSource = dataSource; + private readonly IQueryable _destQuery = destQuery; + private readonly IEnumerable _beforeVisitors = beforeVisitors; + private readonly IEnumerable _afterVisitors = afterVisitors; + private readonly IDictionary _parameters = parameters ?? new Dictionary(); + private readonly MemberPaths _membersToExpand = membersToExpand ?? []; + private readonly Action _exceptionHandler = exceptionHandler ?? (x => { }); public SourceInjectedQueryInspector Inspector { get; set; } internal Action> EnumerationHandler { get; set; } @@ -100,16 +87,16 @@ public TResult Execute(Expression expression) sourceResult.ElementType, typeof(TDestination), _parameters, - _membersToExpand.Select + [.. _membersToExpand.Select ( m => new MemberPath ( m.Distinct().ToArray() ) - ).ToArray() + )] ); - destResult = (IQueryable)GetMapExpressions(queryExpressions).Aggregate(sourceResult, Select); + destResult = (IQueryable)SourceInjectedQueryProvider.GetMapExpressions(queryExpressions).Aggregate(sourceResult, Select); } // case #2: query is arbitrary ("manual") projection // example: users.UseAsDataSource().For().Select(user => user.Age).ToList() @@ -119,7 +106,7 @@ public TResult Execute(Expression expression) { var sourceResult = _dataSource.Provider.CreateQuery(sourceExpression); var elementType = ElementTypeHelper.GetElementType(typeof(TResult)); - var constructorInfo = typeof(List<>).MakeGenericType(elementType).GetDeclaredConstructor(new Type[0]); + var constructorInfo = typeof(List<>).MakeGenericType(elementType).GetDeclaredConstructor([]); if (constructorInfo != null) { var listInstance = (IList)constructorInfo.Invoke(null); @@ -188,16 +175,16 @@ as the final value will be projected automatically sourceResultType, destResultType, _parameters, - _membersToExpand.Select + [.. _membersToExpand.Select ( m => new MemberPath ( m.Distinct().ToArray() ) - ).ToArray() + )] ); // add projection via "select" operator - var expr = GetMapExpressions(queryExpressions).Aggregate(sourceExpression, (source, lambda) => Select(source, lambda)); + var expr = SourceInjectedQueryProvider.GetMapExpressions(queryExpressions).Aggregate(sourceExpression, (source, lambda) => Select(source, lambda)); // in case an element operator without predicate expression was found (and thus not replaced) var replacementMethod = replacer.ElementOperator; // in case an element operator with predicate expression was replaced @@ -243,24 +230,24 @@ as the final value will be projected automatically } } - private LambdaExpression[] GetMapExpressions(QueryExpressions queryExpressions) + private static LambdaExpression[] GetMapExpressions(QueryExpressions queryExpressions) { if (queryExpressions.LetClause != null && queryExpressions.Projection != null) - return new LambdaExpression[] { queryExpressions.LetClause, queryExpressions.Projection }; + return [queryExpressions.LetClause, queryExpressions.Projection]; else if (queryExpressions.LetClause != null) - return new LambdaExpression[] { queryExpressions.LetClause }; + return [queryExpressions.LetClause]; else if (queryExpressions.Projection != null) - return new LambdaExpression[] { queryExpressions.Projection }; + return [queryExpressions.Projection]; - return new LambdaExpression[] { }; + return []; } - private static Expression Select(Expression source, LambdaExpression lambda) + private static MethodCallExpression Select(Expression source, LambdaExpression lambda) { return Call( null, QueryableSelectMethod.MakeGenericMethod(lambda.Parameters[0].Type, lambda.ReturnType), - new[] { source, Quote(lambda) } + [source, Quote(lambda)] ); } @@ -268,7 +255,7 @@ private static IQueryable Select(IQueryable source, LambdaExpression lambda) => Call( null, QueryableSelectMethod.MakeGenericMethod(source.ElementType, lambda.ReturnType), - new[] { source.Expression, Expression.Quote(lambda) } + [source.Expression, Expression.Quote(lambda)] ) ); @@ -317,7 +304,7 @@ private Expression ConvertDestinationExpressionToSourceExpression(Expression exp var typeMap = _mapper.ConfigurationProvider.Internal().ResolveTypeMap(typeof(TDestination), typeof(TSource)); var visitor = new ExpressionMapper.MappingVisitor(_mapper.ConfigurationProvider, typeMap, _destQuery.Expression, _dataSource.Expression, null, - new[] { typeof(TSource) }); + [typeof(TSource)]); var sourceExpression = visitor.Visit(expression); // apply parameters @@ -350,12 +337,10 @@ private static MethodInfo FindQueryableSelectMethod() } [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal class ConstantExpressionReplacementVisitor : ExpressionVisitor + internal class ConstantExpressionReplacementVisitor( + ParameterBag paramValues) : ExpressionVisitor { - private readonly ParameterBag _paramValues; - - public ConstantExpressionReplacementVisitor( - ParameterBag paramValues) => _paramValues = paramValues; + private readonly ParameterBag _paramValues = paramValues; protected override Expression VisitMember(MemberExpression node) { @@ -381,7 +366,7 @@ internal class ReplaceableMethodNodeFinder : ExpressionVisitor { public MethodCallExpression MethodNode { get; private set; } private bool _ignoredMethodFound; - private static readonly string[] IgnoredMethods = { "Select" }; + private static readonly string[] IgnoredMethods = ["Select"]; protected override Expression VisitMethodCall(MethodCallExpression node) { @@ -417,9 +402,9 @@ protected override Expression VisitMethodCall(MethodCallExpression node) } [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal class MethodNodeReplacer : ExpressionVisitor + internal class MethodNodeReplacer(MethodCallExpression foundExpression) : ExpressionVisitor { - private readonly MethodCallExpression _foundExpression; + private readonly MethodCallExpression _foundExpression = foundExpression; private static readonly MethodInfo QueryableWhereMethod = FindQueryableWhereMethod(); @@ -430,8 +415,6 @@ private static MethodInfo FindQueryableWhereMethod() return method; } - public MethodNodeReplacer(MethodCallExpression foundExpression) => _foundExpression = foundExpression; - public MethodInfo ReplacedMethod { get; private set; } public MethodInfo ElementOperator { get; private set; } diff --git a/src/AutoMapper.Extensions.ExpressionMapping/LockingConcurrentDictionary.cs b/src/AutoMapper.Extensions.ExpressionMapping/LockingConcurrentDictionary.cs index 642b393..fc57e41 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/LockingConcurrentDictionary.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/LockingConcurrentDictionary.cs @@ -2,18 +2,12 @@ using System.Collections.Concurrent; using System.Collections.Generic; -namespace AutoMapper +namespace AutoMapper.Extensions.ExpressionMapping { - internal struct LockingConcurrentDictionary + internal readonly struct LockingConcurrentDictionary(Func valueFactory) { - private readonly ConcurrentDictionary> _dictionary; - private readonly Func> _valueFactory; - - public LockingConcurrentDictionary(Func valueFactory) - { - _dictionary = new ConcurrentDictionary>(); - _valueFactory = key => new Lazy(() => valueFactory(key)); - } + private readonly ConcurrentDictionary> _dictionary = new(); + private readonly Func> _valueFactory = key => new Lazy(() => valueFactory(key)); public TValue GetOrAdd(TKey key) => _dictionary.GetOrAdd(key, _valueFactory).Value; public TValue GetOrAdd(TKey key, Func> valueFactory) => _dictionary.GetOrAdd(key, valueFactory).Value; @@ -31,7 +25,7 @@ public bool TryGetValue(TKey key, out TValue value) value = lazy.Value; return true; } - value = default(TValue); + value = default; return false; } diff --git a/src/AutoMapper.Extensions.ExpressionMapping/MapperInfoDictionary.cs b/src/AutoMapper.Extensions.ExpressionMapping/MapperInfoDictionary.cs index 51f2e25..7910567 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/MapperInfoDictionary.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/MapperInfoDictionary.cs @@ -5,20 +5,15 @@ namespace AutoMapper.Extensions.ExpressionMapping { - public class MapperInfoDictionary : Dictionary + public class MapperInfoDictionary(ParameterExpressionEqualityComparer comparer) : Dictionary(comparer) { - public MapperInfoDictionary(ParameterExpressionEqualityComparer comparer) : base(comparer) - { - } - - //const string PREFIX = "p"; public void Add(ParameterExpression key, Dictionary typeMappings) { if (ContainsKey(key)) return; - Add(key, typeMappings.ContainsKey(key.Type) - ? new MapperInfo(Expression.Parameter(typeMappings[key.Type], key.Name), key.Type,typeMappings[key.Type]) + Add(key, typeMappings.TryGetValue(key.Type, out Type valueType) + ? new MapperInfo(Expression.Parameter(valueType, key.Name), key.Type, valueType) : new MapperInfo(Expression.Parameter(key.Type, key.Name), key.Type, key.Type)); } } diff --git a/src/AutoMapper.Extensions.ExpressionMapping/MemberVisitor.cs b/src/AutoMapper.Extensions.ExpressionMapping/MemberVisitor.cs index 0d9d9fc..11ba74a 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/MemberVisitor.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/MemberVisitor.cs @@ -21,7 +21,7 @@ protected override Expression VisitMember(MemberExpression node) return node; } - private readonly List _members = new List(); + private readonly List _members = []; public IEnumerable MemberPath => _members; } } diff --git a/src/AutoMapper.Extensions.ExpressionMapping/NullsafeQueryRewriter.cs b/src/AutoMapper.Extensions.ExpressionMapping/NullsafeQueryRewriter.cs index 133f5c7..1e164eb 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/NullsafeQueryRewriter.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/NullsafeQueryRewriter.cs @@ -39,7 +39,7 @@ namespace AutoMapper.Extensions.ExpressionMapping /// internal class NullsafeQueryRewriter : ExpressionVisitor { - static readonly LockingConcurrentDictionary Cache = new LockingConcurrentDictionary(Fallback); + static readonly LockingConcurrentDictionary Cache = new(Fallback); /// protected override Expression VisitMember(MemberExpression node) @@ -137,7 +137,7 @@ static Expression Fallback(Type type) return null; } - static Expression CollectionFallback(Type definition, Type type) + static UnaryExpression CollectionFallback(Type definition, Type type) { var collection = definition.MakeGenericType(type.GetTypeInfo().GenericTypeArguments); diff --git a/src/AutoMapper.Extensions.ExpressionMapping/PrependParentNameVisitor.cs b/src/AutoMapper.Extensions.ExpressionMapping/PrependParentNameVisitor.cs index 5397f18..b29b617 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/PrependParentNameVisitor.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/PrependParentNameVisitor.cs @@ -2,18 +2,11 @@ namespace AutoMapper.Extensions.ExpressionMapping { - internal class PrependParentNameVisitor : ExpressionVisitor + internal class PrependParentNameVisitor(ParameterExpression currentParameter, string parentFullName, Expression newParameter) : ExpressionVisitor { - public PrependParentNameVisitor(ParameterExpression currentParameter, string parentFullName, Expression newParameter) - { - CurrentParameter = currentParameter; - ParentFullName = parentFullName; - NewParameter = newParameter; - } - - public ParameterExpression CurrentParameter { get; } - public string ParentFullName { get; } - public Expression NewParameter { get; } + public ParameterExpression CurrentParameter { get; } = currentParameter; + public string ParentFullName { get; } = parentFullName; + public Expression NewParameter { get; } = newParameter; protected override Expression VisitParameter(ParameterExpression node) { diff --git a/src/AutoMapper.Extensions.ExpressionMapping/ReplaceExpressionVisitor.cs b/src/AutoMapper.Extensions.ExpressionMapping/ReplaceExpressionVisitor.cs index 83e856c..e6a3514 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/ReplaceExpressionVisitor.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/ReplaceExpressionVisitor.cs @@ -2,16 +2,10 @@ namespace AutoMapper.Extensions.ExpressionMapping { - internal class ReplaceExpressionVisitor : ExpressionVisitor + internal class ReplaceExpressionVisitor(Expression oldExpression, Expression newExpression) : ExpressionVisitor { - private readonly Expression _oldExpression; - private readonly Expression _newExpression; - - public ReplaceExpressionVisitor(Expression oldExpression, Expression newExpression) - { - _oldExpression = oldExpression; - _newExpression = newExpression; - } + private readonly Expression _oldExpression = oldExpression; + private readonly Expression _newExpression = newExpression; public override Expression Visit(Expression node) { diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Structures/DeclaringMemberKey.cs b/src/AutoMapper.Extensions.ExpressionMapping/Structures/DeclaringMemberKey.cs index 7603a9f..d8451e2 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/Structures/DeclaringMemberKey.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/Structures/DeclaringMemberKey.cs @@ -3,31 +3,24 @@ namespace AutoMapper.Extensions.ExpressionMapping.Structures { - internal class DeclaringMemberKey : IEquatable + internal class DeclaringMemberKey(MemberInfo declaringMemberInfo, string declaringMemberFullName) : IEquatable { - public DeclaringMemberKey(MemberInfo declaringMemberInfo, string declaringMemberFullName) - { - DeclaringMemberInfo = declaringMemberInfo; - DeclaringMemberFullName = declaringMemberFullName; - } - - public MemberInfo DeclaringMemberInfo { get; set; } - public string DeclaringMemberFullName { get; set; } + public MemberInfo DeclaringMemberInfo { get; set; } = declaringMemberInfo; + public string DeclaringMemberFullName { get; set; } = declaringMemberFullName; public override bool Equals(object obj) { - if (ReferenceEquals(null, obj)) return false; + if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; - DeclaringMemberKey key = obj as DeclaringMemberKey; - if (key == null) return false; + if (obj is not DeclaringMemberKey key) return false; return Equals(key); } public bool Equals(DeclaringMemberKey other) { - if (ReferenceEquals(null, other)) return false; + if (other is null) return false; if (ReferenceEquals(this, other)) return true; return this.DeclaringMemberInfo.Equals(other.DeclaringMemberInfo) diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Structures/MemberAssignmentInfo.cs b/src/AutoMapper.Extensions.ExpressionMapping/Structures/MemberAssignmentInfo.cs index d6a1636..8fa0ae1 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/Structures/MemberAssignmentInfo.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/Structures/MemberAssignmentInfo.cs @@ -1,26 +1,18 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using System.Text; +using System.Linq.Expressions; namespace AutoMapper.Extensions.ExpressionMapping.Structures { - internal class MemberAssignmentInfo + internal class MemberAssignmentInfo(PropertyMap propertyMap, MemberAssignment memberAssignment) { - 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; } + public PropertyMap PropertyMap { get; set; } = propertyMap; /// /// 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; } + public MemberAssignment MemberAssignment { get; set; } = memberAssignment; } } diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Structures/MemberBindingGroup.cs b/src/AutoMapper.Extensions.ExpressionMapping/Structures/MemberBindingGroup.cs index d847a81..f3f8c0c 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/Structures/MemberBindingGroup.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/Structures/MemberBindingGroup.cs @@ -8,35 +8,28 @@ namespace AutoMapper.Extensions.ExpressionMapping.Structures /// The new bound members will be matched using MemberAssignmentInfos.PropertyMap and /// assigned to the mapped expression (mapped from MemberAssignmentInfos.MemberAssignment.Expression). /// - internal class MemberBindingGroup + internal class MemberBindingGroup(DeclaringMemberKey declaringMemberKey, bool isRootMemberAssignment, Type newType, List memberAssignmentInfos) { - 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; } + public DeclaringMemberKey DeclaringMemberKey { get; set; } = declaringMemberKey; /// /// MemberAssignment is true if it is a member binding of OldType on the initial (root) TypeMap (OldType -> NewType) /// - public bool IsRootMemberAssignment { get; set; } + public bool IsRootMemberAssignment { get; set; } = isRootMemberAssignment; /// /// 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; } + public Type NewType { get; set; } = newType; /// /// List of members to be mapped and bound to the new type /// - public List MemberAssignmentInfos { get; set; } + public List MemberAssignmentInfos { get; set; } = memberAssignmentInfos; } } diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Structures/PropertyMapInfo.cs b/src/AutoMapper.Extensions.ExpressionMapping/Structures/PropertyMapInfo.cs index 85d86be..02048c9 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/Structures/PropertyMapInfo.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/Structures/PropertyMapInfo.cs @@ -4,15 +4,9 @@ namespace AutoMapper.Extensions.ExpressionMapping.Structures { - public class PropertyMapInfo + public class PropertyMapInfo(LambdaExpression customExpression, List destinationPropertyInfos) { - public PropertyMapInfo(LambdaExpression customExpression, List destinationPropertyInfos) - { - CustomExpression = customExpression; - DestinationPropertyInfos = destinationPropertyInfos; - } - - public LambdaExpression CustomExpression { get; set; } - public List DestinationPropertyInfos { get; set; } + public LambdaExpression CustomExpression { get; set; } = customExpression; + public List DestinationPropertyInfos { get; set; } = destinationPropertyInfos; } } diff --git a/src/AutoMapper.Extensions.ExpressionMapping/TypeMapHelper.cs b/src/AutoMapper.Extensions.ExpressionMapping/TypeMapHelper.cs index de99e10..038b8d6 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/TypeMapHelper.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/TypeMapHelper.cs @@ -72,11 +72,7 @@ public static MemberMap GetMemberMapByDestinationProperty(this TypeMap typeMap, public static TypeMap CheckIfTypeMapExists(this IConfigurationProvider config, Type sourceType, Type destinationType) { - var typeMap = config.Internal().ResolveTypeMap(sourceType, destinationType); - if (typeMap == null) - { - throw MissingMapException(sourceType, destinationType); - } + var typeMap = config.Internal().ResolveTypeMap(sourceType, destinationType) ?? throw MissingMapException(sourceType, destinationType); return typeMap; } @@ -88,16 +84,16 @@ public static string GetDestinationName(this MemberMap memberMap) if (memberMap is ConstructorParameterMap constructorMap) return constructorMap.Parameter.Name; - throw new ArgumentException(nameof(memberMap)); + throw new ArgumentException("Invalid member map type.", nameof(memberMap)); } public static PathMap FindPathMapByDestinationFullPath(this TypeMap typeMap, string destinationFullPath) => typeMap.PathMaps.SingleOrDefault(item => string.Join(".", item.MemberPath.Members.Select(m => m.Name)) == destinationFullPath); - private static Exception PropertyConfigurationException(TypeMap typeMap, params string[] unmappedPropertyNames) - => new AutoMapperConfigurationException(new[] { new AutoMapperConfigurationException.TypeMapConfigErrors(typeMap, unmappedPropertyNames, true) }); + private static AutoMapperConfigurationException PropertyConfigurationException(TypeMap typeMap, params string[] unmappedPropertyNames) + => new([new AutoMapperConfigurationException.TypeMapConfigErrors(typeMap, unmappedPropertyNames, true)]); - private static Exception MissingMapException(Type sourceType, Type destinationType) - => new InvalidOperationException($"Missing map from {sourceType} to {destinationType}. Create using CreateMap<{sourceType.Name}, {destinationType.Name}>."); + private static InvalidOperationException MissingMapException(Type sourceType, Type destinationType) + => new($"Missing map from {sourceType} to {destinationType}. Create using CreateMap<{sourceType.Name}, {destinationType.Name}>."); } } diff --git a/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs b/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs index 619d7a5..09bbc65 100644 --- a/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs +++ b/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs @@ -1,7 +1,9 @@ -using AutoMapper.Extensions.ExpressionMapping.Extensions; +using AutoMapper.Extensions.ExpressionMapping; +using AutoMapper.Extensions.ExpressionMapping.Extensions; using AutoMapper.Extensions.ExpressionMapping.Structures; using AutoMapper.Internal; using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -11,27 +13,20 @@ namespace AutoMapper.Extensions.ExpressionMapping { - public class XpressionMapperVisitor : ExpressionVisitor + public class XpressionMapperVisitor(IMapper mapper, ITypeMappingsManager typeMappingsManager) : ExpressionVisitor { - public XpressionMapperVisitor(IMapper mapper, ITypeMappingsManager typeMappingsManager) - { - Mapper = mapper; - TypeMappingsManager = typeMappingsManager; - ConfigurationProvider = mapper.ConfigurationProvider; - } - private MapperInfoDictionary InfoDictionary { get { return TypeMappingsManager.InfoDictionary; } } private Dictionary TypeMappings { get { return TypeMappingsManager.TypeMappings; } } - private IConfigurationProvider ConfigurationProvider { get; } + private IConfigurationProvider ConfigurationProvider { get; } = mapper.ConfigurationProvider; - private IMapper Mapper { get; } + private IMapper Mapper { get; } = mapper; private IConfigurationProvider anonymousTypesConfigurationProvider; - private readonly MapperConfigurationExpression anonymousTypesBaseMappings = new MapperConfigurationExpression(); + private readonly MapperConfigurationExpression anonymousTypesBaseMappings = new(); - private ITypeMappingsManager TypeMappingsManager { get; } + private ITypeMappingsManager TypeMappingsManager { get; } = typeMappingsManager; protected override Expression VisitParameter(ParameterExpression node) { @@ -40,7 +35,7 @@ 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) + private static object GetConstantValue(object constantObject, string fullName, Type parentType) { return fullName.Split('.').Aggregate(constantObject, (parent, memberName) => { @@ -80,7 +75,7 @@ protected override Expression VisitMember(MemberExpression node) } InfoDictionary.Add(parameterExpression, TypeMappings); - return GetMappedMemberExpression(node.GetBaseOfMemberExpression(), new List()); + return GetMappedMemberExpression(node.GetBaseOfMemberExpression(), []); Expression GetMappedMemberExpression(Expression parentExpression, List propertyMapInfoList) { @@ -103,7 +98,7 @@ Expression GetMappedMemberExpression(Expression parentExpression, List ExpressionHelpers.MemberAccesses(fullName, visitedParentExpr); - private Expression GetMemberExpressionFromCustomExpression(PropertyMapInfo lastWithCustExpression, + private static Expression GetMemberExpressionFromCustomExpression(PropertyMapInfo lastWithCustExpression, PropertyMapInfo lastInList, List beforeCustExpression, List afterCustExpression, @@ -141,7 +136,7 @@ Expression PrependParentMemberExpression(PrependParentNameVisitor visitor) ); } - private bool ShouldConvertMemberExpression(Type initialType, Type mappedType) + private static bool ShouldConvertMemberExpression(Type initialType, Type mappedType) { if (initialType.IsLiteralType()) return true; @@ -158,7 +153,7 @@ private bool ShouldConvertMemberExpression(Type initialType, Type mappedType) return mappedType == Enum.GetUnderlyingType(initialType); } - protected Expression GetMemberExpressionFromCustomExpression(List propertyMapInfoList, PropertyMapInfo lastWithCustExpression, Expression mappedParentExpr) + protected static Expression GetMemberExpressionFromCustomExpression(List propertyMapInfoList, PropertyMapInfo lastWithCustExpression, Expression mappedParentExpr) => GetMemberExpressionFromCustomExpression ( lastWithCustExpression, @@ -196,7 +191,7 @@ protected override Expression VisitNew(NewExpression node) else if (node.Arguments.Count > 0 && IsAnonymousType(node.Type)) { ParameterInfo[] parameters = node.Type.GetConstructors()[0].GetParameters(); - Dictionary bindingExpressions = new Dictionary(); + Dictionary bindingExpressions = []; for (int i = 0; i < parameters.Length; i++) bindingExpressions.Add(parameters[i].Name, this.Visit(node.Arguments[i])); @@ -269,7 +264,7 @@ protected override Expression VisitMemberInit(MemberInitExpression node) private void ConfigureAnonymousTypeMaps(Type oldType, Type newAnonymousType) { anonymousTypesBaseMappings.CreateMap(newAnonymousType, oldType); - Dictionary memberTypeMaps = new Dictionary(); + Dictionary memberTypeMaps = []; newAnonymousType.GetMembers() .OfType() .ToList() @@ -307,7 +302,7 @@ private MemberInitExpression GetAnonymousTypeMemberInitExpression(Dictionary> includedMembers = new Dictionary>(); + Dictionary> includedMembers = []; List bindings = memberBindingGroup.MemberAssignmentInfos.Aggregate(new List(), (list, next) => { @@ -318,8 +313,7 @@ private MemberInitExpression GetMemberInit(MemberBindingGroup memberBindingGroup if (sourceMember == null) return list; - DeclaringMemberKey declaringMemberKey = new DeclaringMemberKey - ( + DeclaringMemberKey declaringMemberKey = new ( GetParentMember(propertyMap), BuildParentFullName(propertyMap) ); @@ -339,21 +333,20 @@ private MemberInitExpression GetMemberInit(MemberBindingGroup memberBindingGroup else { if (declaringMemberKey.DeclaringMemberInfo == null) - throw new ArgumentNullException(nameof(declaringMemberKey.DeclaringMemberInfo)); + throw new InvalidOperationException("DeclaringMemberInfo is null."); if (!includedMembers.TryGetValue(declaringMemberKey, out List assignments)) { includedMembers.Add ( declaringMemberKey, - new List - { + [ new MemberAssignmentInfo ( propertyMap, binding ) - } + ] ); } else @@ -376,7 +369,7 @@ bool ShouldBindPropertyMap(MemberAssignmentInfo memberAssignmentInfo) declaringMemberKey: kvp.Key, isRootMemberAssignment: false, newType: kvp.Key.DeclaringMemberInfo.GetMemberType(), - memberAssignmentInfos: includedMembers.Values.SelectMany(m => m).ToList() + memberAssignmentInfos: [.. includedMembers.Values.SelectMany(m => m)] ) ) .ToList() @@ -396,45 +389,43 @@ bool ShouldBindChildReference(MemberBindingGroup group) return Expression.MemberInit(Expression.New(memberBindingGroup.NewType), bindings); } - private MemberBinding DoBind(MemberInfo sourceMember, Expression initial, Expression mapped) + private MemberAssignment DoBind(MemberInfo sourceMember, Expression initial, Expression mapped) { mapped = mapped.ConvertTypeIfNecessary(sourceMember.GetMemberType()); this.TypeMappingsManager.AddTypeMapping(initial.Type, mapped.Type); return Expression.Bind(sourceMember, mapped); } - private MemberInfo GetSourceMember(PropertyMap propertyMap) + private static MemberInfo GetSourceMember(PropertyMap propertyMap) => propertyMap.CustomMapExpression != null ? propertyMap.CustomMapExpression.GetMemberExpression()?.Member : propertyMap.SourceMembers.Last(); - private MemberInfo GetParentMember(PropertyMap propertyMap) + private static MemberInfo GetParentMember(PropertyMap propertyMap) => propertyMap.IncludedMember?.ProjectToCustomSource != null ? propertyMap.IncludedMember.ProjectToCustomSource.GetMemberExpression().Member : GetSourceParentMember(propertyMap); - private MemberInfo GetSourceParentMember(PropertyMap propertyMap) + private static 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]; + if (propertyMap.SourceMembers.Length > 1) + return new List(propertyMap.SourceMembers)[propertyMap.SourceMembers.Length - 2]; return null; } - private string BuildParentFullName(PropertyMap propertyMap) + private static string BuildParentFullName(PropertyMap propertyMap) { - List propertyMapInfos = new List(); + List propertyMapInfos = []; if (propertyMap.IncludedMember?.ProjectToCustomSource != null) - propertyMapInfos.Add(new PropertyMapInfo(propertyMap.IncludedMember.ProjectToCustomSource, new List())); + propertyMapInfos.Add(new PropertyMapInfo(propertyMap.IncludedMember.ProjectToCustomSource, [])); - propertyMapInfos.Add(new PropertyMapInfo(propertyMap.CustomMapExpression, propertyMap.SourceMembers.ToList())); + propertyMapInfos.Add(new PropertyMapInfo(propertyMap.CustomMapExpression, [.. propertyMap.SourceMembers])); - List fullNameArray = BuildFullName(propertyMapInfos) - .Split(new char[] { '.' }) - .ToList(); + List fullNameArray = [.. BuildFullName(propertyMapInfos).Split(['.'])]; fullNameArray.Remove(fullNameArray.Last()); @@ -495,13 +486,11 @@ Expression MapTypeBinary(Expression mapped) if (mapped == node.Expression) return base.VisitTypeBinary(node); - switch (node.NodeType) + return node.NodeType switch { - case ExpressionType.TypeIs: - return Expression.TypeIs(mapped, mappedType); - } - - return base.VisitTypeBinary(node); + ExpressionType.TypeIs => Expression.TypeIs(mapped, mappedType), + _ => base.VisitTypeBinary(node), + }; } } @@ -575,25 +564,25 @@ protected override Expression VisitMethodCall(MethodCallExpression node) MethodCallExpression GetInstanceExpression(Expression instance) { return node.Method.IsGenericMethod - ? Expression.Call(instance, node.Method.Name, typeArgsForNewMethod.ToArray(), listOfArgumentsForNewMethod.ToArray()) - : Expression.Call(instance, GetMethodInfoForNonGeneric(), listOfArgumentsForNewMethod.ToArray()); + ? Expression.Call(instance, node.Method.Name, [.. typeArgsForNewMethod], [.. listOfArgumentsForNewMethod]) + : Expression.Call(instance, GetMethodInfoForNonGeneric(), [.. listOfArgumentsForNewMethod]); MethodInfo GetMethodInfoForNonGeneric() { - MethodInfo methodInfo = instance.Type.GetMethod(node.Method.Name, listOfArgumentsForNewMethod.Select(a => a.Type).ToArray()); + MethodInfo methodInfo = instance.Type.GetMethod(node.Method.Name, [.. listOfArgumentsForNewMethod.Select(a => a.Type)]); if (methodInfo.DeclaringType != instance.Type) - methodInfo = methodInfo.DeclaringType.GetMethod(node.Method.Name, listOfArgumentsForNewMethod.Select(a => a.Type).ToArray()); + methodInfo = methodInfo.DeclaringType.GetMethod(node.Method.Name, [.. listOfArgumentsForNewMethod.Select(a => a.Type)]); return methodInfo; } } MethodCallExpression GetStaticExpression() => node.Method.IsGenericMethod - ? Expression.Call(node.Method.DeclaringType, node.Method.Name, typeArgsForNewMethod.ToArray(), listOfArgumentsForNewMethod.ToArray()) - : Expression.Call(node.Method, listOfArgumentsForNewMethod.ToArray()); + ? Expression.Call(node.Method.DeclaringType, node.Method.Name, [.. typeArgsForNewMethod], [.. listOfArgumentsForNewMethod]) + : Expression.Call(node.Method, [.. listOfArgumentsForNewMethod]); } - void ConvertTypesIfNecessary(ParameterInfo[] parameters, List listOfArgumentsForNewMethod, MethodInfo mInfo) + static void ConvertTypesIfNecessary(ParameterInfo[] parameters, List listOfArgumentsForNewMethod, MethodInfo mInfo) { if (mInfo.IsGenericMethod) return; @@ -606,7 +595,7 @@ void ConvertTypesIfNecessary(ParameterInfo[] parameters, List listOf } } - protected string BuildFullName(List propertyMapInfoList) + protected static string BuildFullName(List propertyMapInfoList) { var fullName = string.Empty; foreach (var info in propertyMapInfoList) @@ -625,7 +614,7 @@ protected string BuildFullName(List propertyMapInfoList) sb.Append(next.Name); else { - sb.Append("."); + sb.Append('.'); sb.Append(next.Name); } return sb; @@ -644,15 +633,15 @@ private static void AddPropertyMapInfo(Type parentType, string name, List { propertyInfo })); + propertyMapInfoList.Add(new PropertyMapInfo(null, [propertyInfo])); break; case FieldInfo fieldInfo: - propertyMapInfoList.Add(new PropertyMapInfo(null, new List { fieldInfo })); + propertyMapInfoList.Add(new PropertyMapInfo(null, [fieldInfo])); break; } } - private bool GenericTypeDefinitionsAreEquivalent(Type typeSource, Type typeDestination) + private static bool GenericTypeDefinitionsAreEquivalent(Type typeSource, Type typeDestination) => typeSource.IsGenericType() && typeDestination.IsGenericType() && typeSource.GetGenericTypeDefinition() == typeDestination.GetGenericTypeDefinition(); protected void FindDestinationFullName(Type typeSource, Type typeDestination, string sourceFullName, List propertyMapInfoList) @@ -686,22 +675,23 @@ TypeMap GetTypeMap() => BothTypesAreAnonymous() if (typeSource == typeDestination) { - var sourceFullNameArray = sourceFullName.Split(new[] { period[0] }, StringSplitOptions.RemoveEmptyEntries); - sourceFullNameArray.Aggregate(propertyMapInfoList, (list, next) => + var sourceFullNameArray = sourceFullName.Split([period[0]], StringSplitOptions.RemoveEmptyEntries); + + foreach (string next in sourceFullNameArray) { - if (list.Count == 0) + if (propertyMapInfoList.Count == 0) { - AddPropertyMapInfo(typeSource, next, list); + AddPropertyMapInfo(typeSource, next, propertyMapInfoList); } else { - var last = list[list.Count - 1]; + var last = propertyMapInfoList[propertyMapInfoList.Count - 1]; AddPropertyMapInfo(last.CustomExpression == null ? last.DestinationPropertyInfos[last.DestinationPropertyInfos.Count - 1].GetMemberType() - : last.CustomExpression.ReturnType, next, list); + : last.CustomExpression.ReturnType, next, propertyMapInfoList); } - return list; - }); + } + return; } @@ -740,7 +730,7 @@ TypeMap GetTypeMap() => BothTypesAreAnonymous() PathMap pathMap = typeMap.FindPathMapByDestinationFullPath(destinationFullPath: sourceFullName); if (pathMap != null) { - propertyMapInfoList.Add(new PropertyMapInfo(pathMap.CustomMapExpression, new List())); + propertyMapInfoList.Add(new PropertyMapInfo(pathMap.CustomMapExpression, [])); return; } @@ -748,15 +738,14 @@ TypeMap GetTypeMap() => BothTypesAreAnonymous() if (sourceFullName.IndexOf(period, StringComparison.OrdinalIgnoreCase) < 0) { var propertyMap = typeMap.GetMemberMapByDestinationProperty(sourceFullName); - var sourceMemberInfo = typeSource.GetFieldOrProperty(propertyMap.GetDestinationName()); - if (propertyMap.CustomMapExpression == null && !propertyMap.SourceMembers.Any()) + if (propertyMap.CustomMapExpression == null && propertyMap.SourceMembers.Length == 0) throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Properties.Resources.srcMemberCannotBeNullFormat, typeSource.Name, typeDestination.Name, sourceFullName)); if (propertyMap.IncludedMember?.ProjectToCustomSource != null) - propertyMapInfoList.Add(new PropertyMapInfo(propertyMap.IncludedMember.ProjectToCustomSource, new List())); + propertyMapInfoList.Add(new PropertyMapInfo(propertyMap.IncludedMember.ProjectToCustomSource, [])); - propertyMapInfoList.Add(new PropertyMapInfo(propertyMap.CustomMapExpression, propertyMap.SourceMembers.ToList())); + propertyMapInfoList.Add(new PropertyMapInfo(propertyMap.CustomMapExpression, [.. propertyMap.SourceMembers])); } else { @@ -764,13 +753,13 @@ TypeMap GetTypeMap() => BothTypesAreAnonymous() var propertyMap = typeMap.GetMemberMapByDestinationProperty(propertyName); var sourceMemberInfo = typeSource.GetFieldOrProperty(propertyMap.GetDestinationName()); - 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. + if (propertyMap.CustomMapExpression == null && propertyMap.SourceMembers.Length == 0)//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, Properties.Resources.srcMemberCannotBeNullFormat, typeSource.Name, typeDestination.Name, propertyName)); if (propertyMap.IncludedMember?.ProjectToCustomSource != null) - propertyMapInfoList.Add(new PropertyMapInfo(propertyMap.IncludedMember.ProjectToCustomSource, new List())); + propertyMapInfoList.Add(new PropertyMapInfo(propertyMap.IncludedMember.ProjectToCustomSource, [])); - propertyMapInfoList.Add(new PropertyMapInfo(propertyMap.CustomMapExpression, propertyMap.SourceMembers.ToList())); + propertyMapInfoList.Add(new PropertyMapInfo(propertyMap.CustomMapExpression, [.. propertyMap.SourceMembers])); var childFullName = sourceFullName.Substring(sourceFullName.IndexOf(period, StringComparison.OrdinalIgnoreCase) + 1); FindDestinationFullName(sourceMemberInfo.GetMemberType(), propertyMap.CustomMapExpression == null diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMapping.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMapping.cs index 6638c39..96189af 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMapping.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMapping.cs @@ -95,8 +95,7 @@ public Parent Parent set { _parent = value; - if (GrandChild != null) - GrandChild.Parent = _parent; + GrandChild?.Parent = _parent; } } @@ -142,7 +141,7 @@ public void GrandParent_Mapping_To_Sub_Sub_Property_Condition() { Expression> _predicateExpression = gp => gp.Parent.Children.Any(c => c.ID2 == 3); var expression = Mapper.Map>>(_predicateExpression); - var items = new[] {new GrandParent(){Parent = new Parent(){Children = new[]{new Child(){ID2 = 3}}, Child = new Child(){ID2 = 3}}}}.AsQueryable(); + var items = new[] {new GrandParent(){Parent = new Parent(){Children = [new Child(){ID2 = 3}], Child = new Child(){ID2 = 3}}}}.AsQueryable(); items.Where(expression).ShouldContain(items.First()); var items2 = items.UseAsDataSource(Mapper).For().Where(_predicateExpression); items2.Count().ShouldBe(1); @@ -164,7 +163,7 @@ public void When_Use_Outside_Class_Method_Call() { var ids = new[] { 4, 5 }; _predicateExpression = p => p.Children.Where((c, i) => c.ID_ > 4).Any(c => ids.Contains(c.ID_)); - _valid = new Parent { Children = new[] { new Child { ID = 5 } } }; + _valid = new Parent { Children = [new Child { ID = 5 }] }; } [Fact] @@ -206,7 +205,7 @@ public void When_Use_Reverse_Null_Substitution_Mappings_Against_Constants_Revers public void When_Use_Sub_Lambda_Statement() { _predicateExpression = p => p.Children.Any(c => c.ID_ > 4); - _valid = new Parent { Children = new[] { new Child { ID = 5 } } }; + _valid = new Parent { Children = [new Child { ID = 5 }] }; } [Fact] @@ -325,7 +324,9 @@ public void Should_map_with_closures() { var req = new TestData { Code = "DD" }; Expression> f = s => s.Code == req.Code; +#pragma warning disable CA2263 // Prefer generic overload when type is known var result = (Expression>) Mapper.Map(f, typeof(Expression>), typeof(Expression>)); +#pragma warning restore CA2263 // Prefer generic overload when type is known var func = result.Compile(); diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/Impl/SourceInjectedQuery.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/Impl/SourceInjectedQuery.cs index b3739e2..06d9412 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/Impl/SourceInjectedQuery.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/Impl/SourceInjectedQuery.cs @@ -1,11 +1,10 @@ -using System; +using Shouldly; +using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Linq.Expressions; -using AutoMapper.Mappers; -using Shouldly; using Xunit; namespace AutoMapper.Extensions.ExpressionMapping.UnitTests.Impl diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.Structs.Tests.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.Structs.Tests.cs index 0ca9286..b6c2eb4 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.Structs.Tests.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.Structs.Tests.cs @@ -209,8 +209,8 @@ public void Can_map_local_variable_literal_in_filter() DateTime firstReleaseDate = new DateTime(); DateTime lastReleaseDate = new DateTime(); - Expression> exp = x => (firstReleaseDate == null || x.CreateDate >= firstReleaseDate) && - (lastReleaseDate == null || x.CreateDate <= lastReleaseDate); + Expression> exp = x => (firstReleaseDate == default || x.CreateDate >= firstReleaseDate) && + (lastReleaseDate == default || x.CreateDate <= lastReleaseDate); //Act Expression> expMapped = mapper.MapExpression>>(exp); diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapperTests.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapperTests.cs index 6a39db1..e8a1b23 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapperTests.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapperTests.cs @@ -701,8 +701,8 @@ public void Map_ItemDto_to_ItemDto_with_local_literal_types() DateTime firstReleaseDate = new DateTime(); DateTime lastReleaseDate = new DateTime(); - Expression> exp = x => (firstReleaseDate == null || x.CreateDate >= firstReleaseDate) && - (lastReleaseDate == null || x.CreateDate <= lastReleaseDate); + Expression> exp = x => (firstReleaseDate == default || x.CreateDate >= firstReleaseDate) && + (lastReleaseDate == default || x.CreateDate <= lastReleaseDate); //Act Expression> expMapped = mapper.MapExpression>>(exp); @@ -716,7 +716,7 @@ public void Map_accountModel_to_account_with_null_checks_against_value_types() { //Arrange Expression> exp = f => (f != null ? f.Id : 0) > 10 - && (f != null && f.DateCreated != null ? f.DateCreated : default(DateTime)) > new DateTime(2007, 02, 17); + && (f != null && f.DateCreated != default ? f.DateCreated : default(DateTime)) > new DateTime(2007, 02, 17); //Act From 1650066f57a572ca63e0eace1cf5ea313b475827 Mon Sep 17 00:00:00 2001 From: Blaise Taylor Date: Fri, 20 Feb 2026 14:03:26 -0500 Subject: [PATCH 5/5] Fixing VS recommended messages in test project. --- .../AssertionExtensions.cs | 4 +- .../AutoMapperSpecBase.cs | 3 +- .../CanMapExpressionWithListConstants.cs | 46 ++--- .../CanMapMemberFromTypeBinaryExpression.cs | 8 +- ...omChildReferenceWithoutMemberExpression.cs | 2 +- ...MapParameterBodyWithoutMemberExpression.cs | 2 +- .../EF.cs | 2 + ...merableDotContainsWorksOnLocalVariables.cs | 8 +- .../ExplicitExpansionAsDataSource.cs | 2 +- .../ExpressionMapping.cs | 17 +- .../ExpressionMappingEnumToNumericOrString.cs | 19 +- .../ExpressionMappingPropertyFromBaseClass.cs | 4 +- ...xpressionMappingPropertyFromDerviedType.cs | 10 +- .../ExpressionMappingWithUseAsDataSource.cs | 16 +- .../GlobalSuppressions.cs | 22 +++ .../Impl/SourceInjectedQuery.cs | 95 +++++---- .../MappingMemberInitWithCustomExpressions.cs | 12 +- ...pingMemberInitWithPropertiesInBaseClass.cs | 26 ++- ...MappingWithIncludeMembersConfigurations.cs | 26 ++- .../ParameterizedQueriesTestsAsDataSource.cs | 12 +- .../ShouldOnlyMapExistingTypeMaps.cs | 4 +- .../TypeExtensionsTest.cs | 12 +- .../XpressionMapper.ForPath.Tests.cs | 32 ++- .../XpressionMapper.Structs.Tests.cs | 66 +++---- .../XpressionMapperTests.cs | 185 +++++++++--------- 25 files changed, 331 insertions(+), 304 deletions(-) create mode 100644 tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/GlobalSuppressions.cs diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/AssertionExtensions.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/AssertionExtensions.cs index 83576c5..cb2d904 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/AssertionExtensions.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/AssertionExtensions.cs @@ -31,7 +31,9 @@ public static void ShouldThrowException(this Action action, Action customA throws.ShouldBeTrue(); } - public static void ShouldNotBeThrownBy(this Type exceptionType, Action action) +#pragma warning disable IDE0060 // Remove unused parameter + public static void ShouldNotBeThrownBy(this Type exceptionType, Action action) +#pragma warning restore IDE0060 // Remove unused parameter => action.ShouldNotThrow(); } } \ No newline at end of file diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/AutoMapperSpecBase.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/AutoMapperSpecBase.cs index 1e7a61d..ec16bc3 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/AutoMapperSpecBase.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/AutoMapperSpecBase.cs @@ -20,7 +20,7 @@ public abstract class NonValidatingSpecBase : SpecBase protected abstract MapperConfiguration Configuration { get; } protected IConfigurationProvider ConfigProvider => Configuration; - protected IMapper Mapper => mapper ?? (mapper = Configuration.CreateMapper()); + protected IMapper Mapper => mapper ??= Configuration.CreateMapper(); } public abstract class SpecBaseBase @@ -59,6 +59,7 @@ protected SpecBase() public void Dispose() { Cleanup(); + GC.SuppressFinalize(this); } } } \ No newline at end of file diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapExpressionWithListConstants.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapExpressionWithListConstants.cs index bfbc466..a9bf919 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapExpressionWithListConstants.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapExpressionWithListConstants.cs @@ -22,13 +22,13 @@ public void Map_expression_with_constant_array() ); config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); - List source1 = new() { + List source1 = [ new EntityModel { SimpleEnum = SimpleEnumModel.Value3 } - }; - List source2 = new() { + ]; + List source2 = [ new EntityModel { SimpleEnum = SimpleEnumModel.Value1 } - }; - Entity[] entities = new Entity[] { new Entity { SimpleEnum = SimpleEnum.Value1 }, new Entity { SimpleEnum = SimpleEnum.Value2 } }; + ]; + Entity[] entities = [new Entity { SimpleEnum = SimpleEnum.Value1 }, new Entity { SimpleEnum = SimpleEnum.Value2 }]; Expression> filter = e => entities.Any(en => e.SimpleEnum == en.SimpleEnum); //act @@ -52,13 +52,13 @@ public void Map_expression_with_constant_list_using_generic_list_dot_contains() ); config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); - List source1 = new() { + List source1 = [ new EntityModel { SimpleEnum = SimpleEnumModel.Value3 } - }; - List source2 = new() { + ]; + List source2 = [ new EntityModel { SimpleEnum = SimpleEnumModel.Value1 } - }; - List enums = new() { SimpleEnum.Value1, SimpleEnum.Value2 }; + ]; + List enums = [SimpleEnum.Value1, SimpleEnum.Value2]; Expression> filter = e => enums.Contains(e.SimpleEnum); //act @@ -82,13 +82,13 @@ public void Map_expression_with_constant_list_using_generic_enumerable_dot_conta ); config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); - List source1 = new() { + List source1 = [ new EntityModel { SimpleEnum = SimpleEnumModel.Value3 } - }; - List source2 = new() { + ]; + List source2 = [ new EntityModel { SimpleEnum = SimpleEnumModel.Value1 } - }; - List enums = new() { SimpleEnum.Value1, SimpleEnum.Value2 }; + ]; + List enums = [SimpleEnum.Value1, SimpleEnum.Value2]; Expression> filter = e => Enumerable.Contains(enums, e.SimpleEnum); //act @@ -112,12 +112,12 @@ public void Map_expression_with_constant_dictionary() ); config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); - List source1 = new() { + List source1 = [ new EntityModel { SimpleEnum = SimpleEnumModel.Value3 } - }; - List source2 = new() { + ]; + List source2 = [ new EntityModel { SimpleEnum = SimpleEnumModel.Value1 } - }; + ]; Dictionary enumDictionary = new() { ["A"] = SimpleEnum.Value1, ["B"] = SimpleEnum.Value2 }; Expression> filter = e => enumDictionary.Any(i => i.Value == e.SimpleEnum); @@ -143,12 +143,12 @@ public void Map_expression_with_constant_dictionary_mapping_both_Key_and_value() ); config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); - List source1 = new() { + List source1 = [ new EntityModel { SimpleEnum = SimpleEnumModel.Value3 } - }; - List source2 = new() { + ]; + List source2 = [ new EntityModel { SimpleEnum = SimpleEnumModel.Value1 } - }; + ]; Dictionary enumDictionary = new() { [SimpleEnum.Value1] = new Entity { SimpleEnum = SimpleEnum.Value1 }, [SimpleEnum.Value2] = new Entity { SimpleEnum = SimpleEnum.Value2 } }; Expression> filter = e => enumDictionary.Any(i => i.Key == e.SimpleEnum && i.Value.SimpleEnum == e.SimpleEnum); diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapMemberFromTypeBinaryExpression.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapMemberFromTypeBinaryExpression.cs index 69ddf35..58c642b 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapMemberFromTypeBinaryExpression.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapMemberFromTypeBinaryExpression.cs @@ -66,8 +66,8 @@ public void Can_map_using_type_binary_expression_to_test_a_member_expression() Expression> whereMapped = mapper.MapExpression>>(where); var list = new List { - new ShapeHolder { Shape = new Circle() }, - new ShapeHolder { Shape = new Triangle() } + new() { Shape = new Circle() }, + new() { Shape = new Triangle() } } .AsQueryable() .Where(whereMapped).ToList(); @@ -130,7 +130,7 @@ public void Can_map_using_static_method_call_to_test_the_parameter_expression() //act Expression> whereMapped = mapper.MapExpression>>(where); - var list = new List { new Circle() { Messages = new List { "" } }, new Triangle() { Messages = new List() } }.AsQueryable().Where(whereMapped).ToList(); + var list = new List { new Circle() { Messages = [""] }, new Triangle() { Messages = [] } }.AsQueryable().Where(whereMapped).ToList(); //assert Assert.Single(list); @@ -218,7 +218,7 @@ public static class ShapeExtentions { public static bool HasMessages(this CanMapMemberFromTypeBinaryExpression.Shape shape) { - return shape.Messages.Any(); + return shape.Messages.Count != 0; } public static bool IsCircle(this T shape) diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapParameterBodyFromChildReferenceWithoutMemberExpression.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapParameterBodyFromChildReferenceWithoutMemberExpression.cs index 5848f28..11fe968 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapParameterBodyFromChildReferenceWithoutMemberExpression.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapParameterBodyFromChildReferenceWithoutMemberExpression.cs @@ -25,7 +25,7 @@ public void Can_map_parameter_body_from_child_reference_without_member_expressio var mapper = config.CreateMapper(); var products = new List() { - new TestProduct { } + new() { } }.AsQueryable(); //Act diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapParameterBodyWithoutMemberExpression.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapParameterBodyWithoutMemberExpression.cs index c4676a0..499109a 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapParameterBodyWithoutMemberExpression.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapParameterBodyWithoutMemberExpression.cs @@ -22,7 +22,7 @@ public void Can_map_parameter_body_without_member_expression() var mapper = config.CreateMapper(); var products = new List() { - new TestProduct { } + new() { } }.AsQueryable(); //Act diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/EF.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/EF.cs index 6836154..17c407d 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/EF.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/EF.cs @@ -2,7 +2,9 @@ { internal class EF { +#pragma warning disable IDE0060 // Remove unused parameter internal static T Property(object p, string v) +#pragma warning restore IDE0060 // Remove unused parameter { return default; } diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/EnumerableDotContainsWorksOnLocalVariables.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/EnumerableDotContainsWorksOnLocalVariables.cs index 78ea148..0699a8e 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/EnumerableDotContainsWorksOnLocalVariables.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/EnumerableDotContainsWorksOnLocalVariables.cs @@ -33,10 +33,10 @@ public void Issue87() var mapped3 = mapper.MapExpression>>(expression3); var mapped4 = mapper.MapExpression>>(expression4); - Assert.Equal(1, new Source[] { new Source { } }.AsQueryable().Where(mapped1).Count()); - Assert.Equal(0, new Source[] { new Source { } }.AsQueryable().Where(mapped2).Count()); - Assert.Equal(1, new Source[] { new Source { Items = new List { new SubSource { Name = "item1" } } } }.AsQueryable().Where(mapped3).Count()); - Assert.Equal(0, new Source[] { new Source { Items = new List { new SubSource { Name = "" } } } }.AsQueryable().Where(mapped4).Count()); + Assert.Equal(1, new Source[] { new() { } }.AsQueryable().Where(mapped1).Count()); + Assert.Equal(0, new Source[] { new() { } }.AsQueryable().Where(mapped2).Count()); + Assert.Equal(1, new Source[] { new() { Items = [new SubSource { Name = "item1" }] } }.AsQueryable().Where(mapped3).Count()); + Assert.Equal(0, new Source[] { new() { Items = [new SubSource { Name = "" }] } }.AsQueryable().Where(mapped4).Count()); } public class Source { public ICollection Items { get; set; } } diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExplicitExpansionAsDataSource.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExplicitExpansionAsDataSource.cs index 224d0c2..52bae22 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExplicitExpansionAsDataSource.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExplicitExpansionAsDataSource.cs @@ -74,7 +74,7 @@ protected override void Because_of() } }; - _dests = sourceList.AsQueryable().UseAsDataSource(Configuration).For(d => d.Child2, d => d.Child4, d => d.Child4.GrandChild).ToArray(); + _dests = [.. sourceList.AsQueryable().UseAsDataSource(Configuration).For(d => d.Child2, d => d.Child4, d => d.Child4.GrandChild)]; } [Fact] diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMapping.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMapping.cs index 96189af..14a8ef6 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMapping.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMapping.cs @@ -212,14 +212,14 @@ public void When_Use_Sub_Lambda_Statement() public void When_Use_Multiple_Parameter_Lambda_Statement() { _predicateExpression = p => p.Children.Any((c, i) => c.ID_ > 4); - _valid = new Parent { Children = new[] { new Child { ID = 5 } } }; + _valid = new Parent { Children = [new Child { ID = 5 }] }; } [Fact] public void When_Use_Lambda_Statement_With_TypeMapped_Property_Being_Other_Than_First() { _predicateExpression = p => p.Children.AnyParamReverse((c, c2) => c.ID_ > 4); - _valid = new Parent {Children = new[] {new Child {ID = 5}}}; + _valid = new Parent {Children = [new Child {ID = 5}]}; } [Fact] @@ -228,10 +228,10 @@ public void When_Use_Child_TypeMap_In_Sub_Lambda_Statement() _predicateExpression = p => p.Children.Any(c => c.GrandChild.GrandChild.ID_ == 4); _valid = new Parent { - Children = new[] - { + Children = + [ new Child {GrandChild = new Child {GrandChild = new Child {ID = 4}}} - } + ] }; } @@ -242,7 +242,7 @@ public void When_Use_Parent_And_Child_Lambda_Parameters_In_Child_Lambda_Statemen _valid = new Parent { Child = new Child {ID = 4}, - Children = new[] {new Child {GrandChild = new Child {ID = 4}}} + Children = [new Child {GrandChild = new Child {ID = 4}}] }; } @@ -295,7 +295,7 @@ public void When_Using_Everything_At_Once() { var year = DateTime.Now.Year; _predicateExpression = p => p.DateTime.Year == year && p.Child.Parent.Child.GrandChild.Parent.Child.GrandChild.GrandChild.ID_ == 4 && p.Children.Any(c => c.GrandChild.GrandChild.ID_ == 4); - _valid = new Parent { DateTime = DateTime.Now, Child = new Child { GrandChild = new Child { GrandChild = new Child { ID = 4 } } }, Children = new[] { new Child { GrandChild = new Child { GrandChild = new Child { ID = 4 } } } } }; + _valid = new Parent { DateTime = DateTime.Now, Child = new Child { GrandChild = new Child { GrandChild = new Child { ID = 4 } } }, Children = [new Child { GrandChild = new Child { GrandChild = new Child { ID = 4 } } }] }; } [Fact] @@ -324,9 +324,8 @@ public void Should_map_with_closures() { var req = new TestData { Code = "DD" }; Expression> f = s => s.Code == req.Code; -#pragma warning disable CA2263 // Prefer generic overload when type is known + var result = (Expression>) Mapper.Map(f, typeof(Expression>), typeof(Expression>)); -#pragma warning restore CA2263 // Prefer generic overload when type is known var func = result.Compile(); diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMappingEnumToNumericOrString.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMappingEnumToNumericOrString.cs index 75d841a..2cba843 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMappingEnumToNumericOrString.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMappingEnumToNumericOrString.cs @@ -185,7 +185,7 @@ public void BinaryExpressionEquals(TEnum enumConstant, TNumeric Expression, bool>> mappedExpression; { var param = Expression.Parameter(typeof(EntityDto), "x"); - var property = Expression.Property(param, nameof(EntityDto.Value)); + var property = Expression.Property(param, nameof(EntityDto<>.Value)); var constantExp = Expression.Constant(enumConstant, typeof(TEnum)); var binaryExpression = Expression.Equal(property, constantExp); var lambdaExpression = Expression.Lambda(binaryExpression, param); @@ -208,7 +208,7 @@ public void BinaryExpressionEqualsWithNullable() Expression, bool>> mappedExpression; { var param = Expression.Parameter(typeof(EntityDto), "x"); - var property = Expression.Property(param, nameof(EntityDto.Value)); + var property = Expression.Property(param, nameof(EntityDto<>.Value)); var constantExp = Expression.Constant(enumConstant, typeof(SimpleEnumInt)); var binaryExpression = Expression.Equal(property, constantExp); var lambdaExpression = Expression.Lambda(binaryExpression, param); @@ -225,15 +225,26 @@ public void BinaryExpressionEqualsWithNullable() private class ComplexEntity { +#pragma warning disable IDE1006 // Naming Styles public int intToEnum { get; set; } +#pragma warning restore IDE1006 // Naming Styles +#pragma warning disable IDE1006 // Naming Styles public SimpleEnumInt enumToInt { get; set; } +#pragma warning restore IDE1006 // Naming Styles +#pragma warning disable IDE1006 // Naming Styles public SimpleEnumInt enumToEnum { get; set; } +#pragma warning restore IDE1006 // Naming Styles +#pragma warning disable IDE1006 // Naming Styles public int intToInt { get; set; } +#pragma warning restore IDE1006 // Naming Styles } private class ComplexEntityDto { +#pragma warning disable IDE1006 // Naming Styles public SimpleEnumInt intToEnum { get; set; } +#pragma warning restore IDE1006 // Naming Styles +#pragma warning disable IDE1006 // Naming Styles public int enumToInt { get; set; } public SimpleEnumInt enumToEnum { get; set; } @@ -256,12 +267,12 @@ public void BinaryExpressionPartialTranslation() var constant5 = Expression.Constant(SimpleEnumInt.Value3, typeof(SimpleEnumInt)); var constant6 = Expression.Constant(SimpleEnumInt.Value2, typeof(SimpleEnumInt)); - Expression[] equals = new Expression[]{ + Expression[] equals = [ Expression.Equal(property1, constant1), Expression.Equal(property2, constant2), Expression.Equal(property5, constant5), Expression.Equal(property6, constant6), - }; + ]; Expression andExpression = equals[0]; for (int i = 1; i < equals.Length; i++) diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMappingPropertyFromBaseClass.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMappingPropertyFromBaseClass.cs index 1c471e3..68852c6 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMappingPropertyFromBaseClass.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMappingPropertyFromBaseClass.cs @@ -55,7 +55,7 @@ protected override void Because_of() //Arrange var guid = Guid.NewGuid(); var entity = new Entity { Id = guid, Name = "Sofia" }; - _source = new List { entity }; + _source = [entity]; // Act Expression> dtoQueryExpression = r => r.Id == guid; @@ -68,7 +68,7 @@ protected override void Because_of() public void Should_support_propertypath_expressions_with_properties_from_assignable_types() { // Assert - entityQuery.ToList().Count().ShouldBe(1); + entityQuery.ToList().Count.ShouldBe(1); } } } diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMappingPropertyFromDerviedType.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMappingPropertyFromDerviedType.cs index 1833f21..097208e 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMappingPropertyFromDerviedType.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMappingPropertyFromDerviedType.cs @@ -20,7 +20,7 @@ protected override MapperConfiguration Configuration var config = ConfigurationHelper.GetMapperConfiguration(cfg => { cfg.AddExpressionMapping(); - cfg.AddProfile(typeof(DerivedTypeProfile)); + cfg.AddProfile(); }); return config; } @@ -29,11 +29,11 @@ protected override MapperConfiguration Configuration protected override void Because_of() { //Arrange - _source = new List { + _source = [ new Entity { Id = Guid.NewGuid(), Name = "Sofia" }, new Entity { Id = Guid.NewGuid(), Name = "Rafael" }, new BaseEntity { Id = Guid.NewGuid() } - }; + ]; } [Fact] @@ -45,7 +45,7 @@ public void Should_support_propertypath_expressions_with_properties_from_sub_typ entityQuery = _source.AsQueryable().Where(entityQueryExpression); // Assert - entityQuery.ToList().Count().ShouldBe(1); + entityQuery.ToList().Count.ShouldBe(1); } [Fact] @@ -57,7 +57,7 @@ public void Should_support_propertypath_expressions_with_properties_from_sub_typ entityQuery = _source.AsQueryable().Where(entityQueryExpression); // Assert - entityQuery.ToList().Count().ShouldBe(1); + entityQuery.ToList().Count.ShouldBe(1); } public class DerivedTypeProfile : Profile diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMappingWithUseAsDataSource.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMappingWithUseAsDataSource.cs index 13e59d4..a807a96 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMappingWithUseAsDataSource.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ExpressionMappingWithUseAsDataSource.cs @@ -19,10 +19,10 @@ public void When_Apply_Where_Clause_Over_Queryable_As_Data_Source() var models = new List() { - new Model { ABoolean = true }, - new Model { ABoolean = false }, - new Model { ABoolean = true }, - new Model { ABoolean = false } + new() { ABoolean = true }, + new() { ABoolean = false }, + new() { ABoolean = true }, + new() { ABoolean = false } }; var queryable = models.AsQueryable(); @@ -50,10 +50,10 @@ public void Should_Map_From_Generic_Type() var models = new List>() { - new GenericModel {ABoolean = true}, - new GenericModel {ABoolean = false}, - new GenericModel {ABoolean = true}, - new GenericModel {ABoolean = false} + new() {ABoolean = true}, + new() {ABoolean = false}, + new() {ABoolean = true}, + new() {ABoolean = false} }; var queryable = models.AsQueryable(); diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/GlobalSuppressions.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/GlobalSuppressions.cs new file mode 100644 index 0000000..03142d7 --- /dev/null +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/GlobalSuppressions.cs @@ -0,0 +1,22 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Performance", "CA1866:Use char overload", Justification = "For testing purposes.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.UnitTests.Impl.SourceInjectedQuery.Shoud_support_enumerable_return_type_with_result")] +[assembly: SuppressMessage("Performance", "CA1866:Use char overload", Justification = "For testing purposes.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.UnitTests.Impl.SourceInjectedQuery.Shoud_support_enumerable_return_type_with_result_toList")] +[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "For testing purposes.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.UnitTests.TypeExtensionsTest.ClassWithMethods.TestMethod")] +[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "For testing purposes.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.UnitTests.TypeExtensionsTest.ClassWithMethods.AnotherMethod(System.Int32)")] +[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "For testing purposes.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.UnitTests.TypeExtensionsTest.ClassWithStaticMembers.InstanceMethod")] +[assembly: SuppressMessage("Performance", "CA1866:Use char overload", Justification = "For testing purposes.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.UnitTests.XpressionMapperTests.Map_projection")] +[assembly: SuppressMessage("Performance", "CA1862:Use the 'StringComparison' method overloads to perform case-insensitive string comparisons", Justification = "For testing purposes.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.UnitTests.XpressionMapperTests.Map__flattened_property")] +[assembly: SuppressMessage("Performance", "CA1866:Use char overload", Justification = "For testing purposes.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.UnitTests.XpressionMapperTests.Map__select_method")] +[assembly: SuppressMessage("Performance", "CA1866:Use char overload", Justification = "For testing purposes.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.UnitTests.XpressionMapperTests.Map__select_method_projecting_to_anonymous_type")] +[assembly: SuppressMessage("Performance", "CA1866:Use char overload", Justification = "For testing purposes.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.UnitTests.XpressionMapperTests.Map__select_method_projecting_to_model_type")] +[assembly: SuppressMessage("Usage", "CA2263:Prefer generic overload when type is known", Justification = "For testing purposes.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.UnitTests.XpressionMapperTests.Map_orderBy_thenBy_To_Dictionary_Select_expression_without_generic_types")] +[assembly: SuppressMessage("Usage", "CA2263:Prefer generic overload when type is known", Justification = "For testing purposes.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.UnitTests.ExpressionsMappingWithClosures.Should_map_with_closures")] +[assembly: SuppressMessage("Usage", "CA2263:Prefer generic overload when type is known", Justification = "For testing purposes.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.UnitTests.XpressionMapperTests.Map_to_anonymous_type_when_init_member_is_not_a_literal")] +[assembly: SuppressMessage("Usage", "CA2263:Prefer generic overload when type is known", Justification = "For testing purposes.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.UnitTests.XpressionMapperTests.Map_to_anonymous_type_when_init_member_is_not_a_literal_and_parameter_is_anonymous_type")] +[assembly: SuppressMessage("Usage", "CA2263:Prefer generic overload when type is known", Justification = "For testing purposes.", Scope = "member", Target = "~M:AutoMapper.Extensions.ExpressionMapping.UnitTests.XpressionMapperTests.Map_to_anonymous_type_when_init_member_is_not_a_literal_with_navigation_property")] diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/Impl/SourceInjectedQuery.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/Impl/SourceInjectedQuery.cs index 06d9412..30c6190 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/Impl/SourceInjectedQuery.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/Impl/SourceInjectedQuery.cs @@ -11,12 +11,12 @@ namespace AutoMapper.Extensions.ExpressionMapping.UnitTests.Impl { public class SourceInjectedQuery : AutoMapperSpecBase { - readonly Source[] _source = new[] - { + readonly Source[] _source = + [ new Source {SrcValue = 5, InttoStringValue = 5}, new Source {SrcValue = 4, InttoStringValue = 4}, new Source {SrcValue = 7, InttoStringValue = 7} - }; + ]; public class Source { @@ -81,7 +81,7 @@ public void Shoud_support_IEnumerable_result() .UseAsDataSource(Mapper).For() .Where(s => s.DestValue > 6); - List list = result.ToList(); + List list = [.. result]; } [Fact] @@ -147,9 +147,9 @@ public void Shoud_support_enumerable_return_type_with_result() { var source = new[] { - new Source {SrcValue = 5, Strings = new [] {"lala5", "lili5"}}, - new Source {SrcValue = 4, Strings = new [] {"lala4", "lili4"}}, - new Source {SrcValue = 7, Strings = new [] {"lala7", "lili7"}} + new Source {SrcValue = 5, Strings = ["lala5", "lili5"]}, + new Source {SrcValue = 4, Strings = ["lala4", "lili4"]}, + new Source {SrcValue = 7, Strings = ["lala7", "lili7"]} }; var result = source.AsQueryable() @@ -174,11 +174,11 @@ public void Shoud_support_any_stupid_thing_you_can_throw_at_it_with_annonumus_ty result.First().A.ShouldBe(_source.Max(s => s.SrcValue)); } - readonly User[] _source2 = new[] - { + readonly User[] _source2 = + [ new User { UserId = 2, Account = new Account(){ Id = 4,Things = {new Thing(){Bar = "Bar"}, new Thing(){ Bar ="Bar 2"}}}}, new User { UserId = 1, Account = new Account(){ Id = 3,Things = {new Thing(){Bar = "Bar 3"}, new Thing(){ Bar ="Bar 4"}}}}, - }; + ]; [Fact] public void Map_select_method() @@ -270,9 +270,9 @@ public void Shoud_support_enumerable_return_type_with_result_toList() { var source = new[] { - new Source {SrcValue = 5, Strings = new [] {"lala5", "lili5"}}, - new Source {SrcValue = 4, Strings = new [] {"lala4", "lili4"}}, - new Source {SrcValue = 7, Strings = new [] {"lala7", "lili7"}} + new Source {SrcValue = 5, Strings = ["lala5", "lili5"]}, + new Source {SrcValue = 4, Strings = ["lala4", "lili4"]}, + new Source {SrcValue = 7, Strings = ["lala7", "lili7"]} }; var result = source.AsQueryable() @@ -346,7 +346,7 @@ public void CanMapCyclicObjectGraph() var dto = mapper.Map(detail); // Assert - AssertValidDtoGraph(detail, master, dto); + SourceInjectedQuery.AssertValidDtoGraph(detail, master, dto); } [Fact] @@ -375,7 +375,7 @@ public void CanMapCaclicExpressionGraph() // Assert var dto = detailDtoQuery.Single(); - AssertValidDtoGraph(detail, master, dto); + SourceInjectedQuery.AssertValidDtoGraph(detail, master, dto); } [Fact] @@ -405,7 +405,7 @@ public void CanMapCaclicExpressionGraph_WithPropertyFilter() // Assert var dto = detailDtoQuery.Single(); - AssertValidDtoGraph(detail, master, dto); + SourceInjectedQuery.AssertValidDtoGraph(detail, master, dto); } [Fact] @@ -435,7 +435,7 @@ public void CanMapCaclicExpressionGraph_WithPropertyPathEqualityFilter_Single() // Assert var dto = detailDtoQuery.Single(); - AssertValidDtoGraph(detail, master, dto); + SourceInjectedQuery.AssertValidDtoGraph(detail, master, dto); } [Fact] @@ -456,10 +456,10 @@ public void Should_support_propertypath_expressons_with_equally_named_properties .Where(d => d.Master.Name == "Harry Marry"); // Assert - detailDtoQuery.ToList().Count().ShouldBe(1); + detailDtoQuery.ToList().Count.ShouldBe(1); } - private void AssertValidDtoGraph(Detail detail, Master master, DetailCyclicDto dto) + private static void AssertValidDtoGraph(Detail detail, Master master, DetailCyclicDto dto) { dto.ShouldNotBeNull(); detail.Id.ShouldBe(dto.Id); @@ -530,27 +530,24 @@ public void SupportsParameterizationInQuery() // Arrange var sources = new List() { - new ResourceWithPermissions - { + new() { Title = "Resource 01", - Permissions = new List - { - new Permission {PermissionName = "Edit", UserId = 22}, - new Permission {PermissionName = "Read", UserId = 4} - } + Permissions = + [ + new() {PermissionName = "Edit", UserId = 22}, + new() {PermissionName = "Read", UserId = 4} + ] }, - new ResourceWithPermissions - { + new() { Title = "Resource 02", - Permissions = new List - { + Permissions = + [ new Permission {PermissionName = "Edit", UserId = 4} - } + ] }, - new ResourceWithPermissions - { + new() { Title = "Resource 03", - Permissions = new List() + Permissions = [] } }; @@ -562,7 +559,7 @@ public void SupportsParameterizationInQuery() var config = ConfigurationHelper.GetMapperConfiguration(cfg => { // parameter defined in mapping part - long userId = default(long); + long userId = default; cfg.CreateMap() .ForMember(t => t.HasEditPermission, o => o.MapFrom(s => s.Permissions.Any(p => p.PermissionName == "Edit" && p.UserId == userId))); @@ -574,7 +571,7 @@ public void SupportsParameterizationInQuery() var factoryFunc = new Func, IQueryable>((list) => { // same parameter defined in query part - long userId = default(long); + long userId = default; return list.AsQueryable().Where(r => r.Permissions.Any(p => p.UserId == userId)); }); @@ -630,7 +627,7 @@ public void Should_dispose_enumerator_when_arbitrary_projection_is_enumerated() var source = new NotSingleQueryingEnumerable(); // Act - source.AsQueryable() + _ = source.AsQueryable() .UseAsDataSource(mapper).For() .Select(m => m.Name) .ToList(); @@ -656,7 +653,7 @@ private static IMapper SetupAutoMapper() cfg.CreateMap() .ForMember(d => d.UserId, opt => opt.MapFrom(s => s.Id)) .ForMember(d => d.Name, opt => opt.MapFrom(s => s.FullName)) - .ForMember(d => d.IsLoggedOn, opt => opt.MapFrom(s => s.LoggedOn.ToUpper() == "Y")) + .ForMember(d => d.IsLoggedOn, opt => opt.MapFrom(s => s.LoggedOn.Equals("Y", StringComparison.CurrentCultureIgnoreCase))) .ForMember(d => d.Age, opt => opt.MapFrom(s => s.AgeInYears)) .ForMember(d => d.Active, opt => opt.MapFrom(s => s.IsActive)) .ForMember(d => d.Account, opt => opt.MapFrom(s => s.AccountModel)); @@ -699,7 +696,7 @@ public class Account { public Account() { - Things = new List(); + Things = []; } public int Id { get; set; } public double Balance { get; set; } @@ -713,7 +710,7 @@ public class AccountModel { public AccountModel() { - ThingModels = new List(); + ThingModels = []; } public int Id { get; set; } public double Bal { get; set; } @@ -760,7 +757,7 @@ public class Master { public Master() { - Details = new List(); + Details = []; } public Guid Id { get; set; } public string Name { get; set; } @@ -792,7 +789,7 @@ public class MasterCyclicDto { public MasterCyclicDto() { - Details = new List(); + Details = []; } public Guid Id { get; set; } public string Name { get; set; } @@ -855,6 +852,7 @@ public NotRelationalDataReader() public void Dispose() { Instance = null; + GC.SuppressFinalize(this); } } @@ -910,19 +908,13 @@ private sealed class Enumerator : IEnumerator public void Dispose() { - if (_dataReader is not null) - { - _dataReader.Dispose(); - _dataReader = null; - } + _dataReader?.Dispose(); + _dataReader = null; } public bool MoveNext() { - if (_dataReader == null) - { - _dataReader = new NotRelationalDataReader(); - } + _dataReader ??= new NotRelationalDataReader(); return false; } @@ -935,3 +927,4 @@ public void Reset() } + diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingMemberInitWithCustomExpressions.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingMemberInitWithCustomExpressions.cs index 5792f5e..79a8ef2 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingMemberInitWithCustomExpressions.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingMemberInitWithCustomExpressions.cs @@ -12,7 +12,7 @@ public class MappingMemberInitWithCustomExpressions public void Map_member_init_using_custom_expressions() { //Arrange - var config = GetConfiguration(); + var config = MappingMemberInitWithCustomExpressions.GetConfiguration(); config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); Expression> selection = s => new PlayerDto @@ -33,7 +33,7 @@ public void Map_member_init_using_custom_expressions() //Act Expression> selectionMapped = mapper.MapExpression>>(selection); - List result = Players.Select(selectionMapped).ToList(); + List result = [.. Players.Select(selectionMapped)]; //Assert Assert.Equal("Jack", result[0].Name); @@ -44,7 +44,7 @@ public void Map_member_init_using_custom_expressions() Assert.Equal("Atlanta", result[0].StatsA.StatsBuilder.City); } - MapperConfiguration GetConfiguration() + static MapperConfiguration GetConfiguration() => ConfigurationHelper.GetMapperConfiguration ( cfg => @@ -69,8 +69,7 @@ MapperConfiguration GetConfiguration() readonly IQueryable Players = new List { - new Player - { + new() { Name = "Jack", StatsA = new Stats { @@ -84,8 +83,7 @@ MapperConfiguration GetConfiguration() } } }, - new Player - { + new() { Name = "Jane", StatsA = new Stats { diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingMemberInitWithPropertiesInBaseClass.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingMemberInitWithPropertiesInBaseClass.cs index 2001408..96b314e 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingMemberInitWithPropertiesInBaseClass.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingMemberInitWithPropertiesInBaseClass.cs @@ -17,7 +17,7 @@ public void ShouldMapWhenParenIsConfiguredUsingIncludeMembers() var mapper = config.CreateMapper(); //Act - List models = mapper.ProjectTo(Players).ToList(); + List models = [.. mapper.ProjectTo(Players)]; //Assert Assert.Equal("Jack", models[0].Name); @@ -45,7 +45,7 @@ public void Map_member_init_with_include_members() //Act Expression> selectionMapped = mapper.MapExpression>>(selection); - List result = Players.Select(selectionMapped).ToList(); + List result = [.. Players.Select(selectionMapped)]; //Assert Assert.Equal("Jack", result[0].Name); @@ -78,7 +78,7 @@ public void Map_member_init_with_include_members_and_nested_include_members() //Act Expression> selectionMapped = mapper.MapExpression>>(selection); - List result = Players.Select(selectionMapped).ToList(); + List result = [.. Players.Select(selectionMapped)]; //Assert Assert.Equal("Jack", result[0].Name); @@ -106,7 +106,7 @@ public void Map_member_expression_with_include_members_and_nested_include_member //Act Expression> selectionMapped = mapper.MapExpression>>(selection); - List result = Players.Select(selectionMapped).ToList(); + List result = [.. Players.Select(selectionMapped)]; //Assert Assert.Equal("Atlanta", result[0]); @@ -117,14 +117,14 @@ public void Map_member_expression_with_include_members_and_nested_include_member public void Map_member_init_without_include_members() { //Arrange - var config = GetConfigurationWiithoutIncludeMembers(); + var config = MappingMemberInitWithPropertiesInBaseClass.GetConfigurationWiithoutIncludeMembers(); config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); 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(); + List result = [.. Players.Select(selectionMapped)]; //Assert Assert.Equal("Jack", result[0].Name); @@ -137,7 +137,7 @@ public void Map_member_init_without_include_members() public void Map_member_init_without_include_members_and_including_nested_members() { //Arrange - var config = GetConfigurationWiithoutIncludeMembers(); + var config = MappingMemberInitWithPropertiesInBaseClass.GetConfigurationWiithoutIncludeMembers(); config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); Expression> selection = s => new PlayerModel @@ -157,7 +157,7 @@ public void Map_member_init_without_include_members_and_including_nested_members //Act Expression> selectionMapped = mapper.MapExpression>>(selection); - List result = Players.Select(selectionMapped).ToList(); + List result = [.. Players.Select(selectionMapped)]; //Assert Assert.Equal("Jack", result[0].Name); @@ -174,7 +174,7 @@ public void Map_member_init_without_include_members_and_including_nested_members Assert.Equal("Columbia", result[0].StatsB.StatsBuilder.City); } - MapperConfiguration GetConfigurationWiithoutIncludeMembers() + static MapperConfiguration GetConfigurationWiithoutIncludeMembers() => ConfigurationHelper.GetMapperConfiguration(cfg => { cfg.AddExpressionMapping(); @@ -190,7 +190,7 @@ MapperConfiguration GetConfigurationWiithoutIncludeMembers() .ForMember(d => d.StatsBSpeed, opt => opt.MapFrom(s => s.StatsB.SpeedValue)); }); - MapperConfiguration GetConfigurationWiithIncludeMembers() + static MapperConfiguration GetConfigurationWiithIncludeMembers() => ConfigurationHelper.GetMapperConfiguration(cfg => { cfg.AddExpressionMapping(); @@ -224,8 +224,7 @@ MapperConfiguration GetConfigurationWiithIncludeMembers() readonly IQueryable Players = new List { - new Player - { + new() { Name = "Jack", StatsA = new Stats { @@ -250,8 +249,7 @@ MapperConfiguration GetConfigurationWiithIncludeMembers() } } }, - new Player - { + new() { Name = "Jane", StatsA = new Stats { diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingWithIncludeMembersConfigurations.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingWithIncludeMembersConfigurations.cs index 4e126be..ea3cd4a 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingWithIncludeMembersConfigurations.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MappingWithIncludeMembersConfigurations.cs @@ -17,7 +17,7 @@ public void ShouldMapWhenParenIsConfiguredUsingIncludeMembers() var mapper = config.CreateMapper(); //Act - List models = mapper.ProjectTo(Players).ToList(); + List models = [.. mapper.ProjectTo(Players)]; //Assert Assert.Equal("Jack", models[0].Name); @@ -45,7 +45,7 @@ public void Map_member_init_with_include_members() //Act Expression> selectionMapped = mapper.MapExpression>>(selection); - List result = Players.Select(selectionMapped).ToList(); + List result = [.. Players.Select(selectionMapped)]; //Assert Assert.Equal("Jack", result[0].Name); @@ -78,7 +78,7 @@ public void Map_member_init_with_include_members_and_nested_include_members() //Act Expression> selectionMapped = mapper.MapExpression>>(selection); - List result = Players.Select(selectionMapped).ToList(); + List result = [.. Players.Select(selectionMapped)]; //Assert Assert.Equal("Jack", result[0].Name); @@ -106,7 +106,7 @@ public void Map_member_expression_with_include_members_and_nested_include_member //Act Expression> selectionMapped = mapper.MapExpression>>(selection); - List result = Players.Select(selectionMapped).ToList(); + List result = [.. Players.Select(selectionMapped)]; //Assert Assert.Equal("Atlanta", result[0]); @@ -117,14 +117,14 @@ public void Map_member_expression_with_include_members_and_nested_include_member public void Map_member_init_without_include_members() { //Arrange - var config = GetConfigurationWiithoutIncludeMembers(); + var config = MappingWithIncludeMembersConfigurations.GetConfigurationWiithoutIncludeMembers(); config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); 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(); + List result = [.. Players.Select(selectionMapped)]; //Assert Assert.Equal("Jack", result[0].Name); @@ -137,7 +137,7 @@ public void Map_member_init_without_include_members() public void Map_member_init_without_include_members_and_including_nested_members() { //Arrange - var config = GetConfigurationWiithoutIncludeMembers(); + var config = MappingWithIncludeMembersConfigurations.GetConfigurationWiithoutIncludeMembers(); config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); Expression> selection = s => new PlayerModel @@ -157,7 +157,7 @@ public void Map_member_init_without_include_members_and_including_nested_members //Act Expression> selectionMapped = mapper.MapExpression>>(selection); - List result = Players.Select(selectionMapped).ToList(); + List result = [.. Players.Select(selectionMapped)]; //Assert Assert.Equal("Jack", result[0].Name); @@ -174,7 +174,7 @@ public void Map_member_init_without_include_members_and_including_nested_members Assert.Equal("Columbia", result[0].StatsB.StatsBuilder.City); } - MapperConfiguration GetConfigurationWiithoutIncludeMembers() + static MapperConfiguration GetConfigurationWiithoutIncludeMembers() => ConfigurationHelper.GetMapperConfiguration(cfg => { cfg.AddExpressionMapping(); @@ -190,7 +190,7 @@ MapperConfiguration GetConfigurationWiithoutIncludeMembers() .ForMember(d => d.StatsBSpeed, opt => opt.MapFrom(s => s.StatsB.SpeedValue)); }); - MapperConfiguration GetConfigurationWiithIncludeMembers() + static MapperConfiguration GetConfigurationWiithIncludeMembers() => ConfigurationHelper.GetMapperConfiguration(cfg => { cfg.AddExpressionMapping(); @@ -224,8 +224,7 @@ MapperConfiguration GetConfigurationWiithIncludeMembers() readonly IQueryable Players = new List { - new Player - { + new() { Name = "Jack", StatsA = new Stats { @@ -250,8 +249,7 @@ MapperConfiguration GetConfigurationWiithIncludeMembers() } } }, - new Player - { + new() { Name = "Jane", StatsA = new Stats { diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ParameterizedQueriesTestsAsDataSource.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ParameterizedQueriesTestsAsDataSource.cs index 97c8a7d..02e9c53 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ParameterizedQueriesTestsAsDataSource.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ParameterizedQueriesTestsAsDataSource.cs @@ -37,7 +37,7 @@ protected override void Because_of() new Source() }.AsQueryable(); - _dests = _sources.UseAsDataSource(Configuration).For(new { value = 10 }).ToArray(); + _dests = [.. _sources.UseAsDataSource(Configuration).For(new { value = 10 })]; } [Fact] @@ -85,7 +85,7 @@ protected override void Because_of() new Source() }.AsQueryable(); - _dests = _sources.UseAsDataSource(Configuration).For(new Dictionary { { "value", 10 } }).ToArray(); + _dests = [.. _sources.UseAsDataSource(Configuration).For(new Dictionary { { "value", 10 } })]; } [Fact] @@ -117,7 +117,9 @@ public class UserViewModel public int Id { get; set; } public string Name { get; set; } public DateTime? DateActivated { get; set; } +#pragma warning disable IDE1006 // Naming Styles public int position { get; set; } +#pragma warning restore IDE1006 // Naming Styles } public class DB @@ -126,9 +128,9 @@ public DB() { Users = new List() { - new User {DateActivated = new DateTime(2000, 1, 1), Id = 1, Name = "Joe Schmoe"}, - new User {DateActivated = new DateTime(2000, 2, 1), Id = 2, Name = "John Schmoe"}, - new User {DateActivated = new DateTime(2000, 3, 1), Id = 3, Name = "Jim Schmoe"}, + new() {DateActivated = new DateTime(2000, 1, 1), Id = 1, Name = "Joe Schmoe"}, + new() {DateActivated = new DateTime(2000, 2, 1), Id = 2, Name = "John Schmoe"}, + new() {DateActivated = new DateTime(2000, 3, 1), Id = 3, Name = "Jim Schmoe"}, }.AsQueryable(); } public IQueryable Users { get; } diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ShouldOnlyMapExistingTypeMaps.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ShouldOnlyMapExistingTypeMaps.cs index 3cb00a0..b3473fa 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ShouldOnlyMapExistingTypeMaps.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ShouldOnlyMapExistingTypeMaps.cs @@ -47,9 +47,11 @@ public void Issue93() var mapper = config.CreateMapper(); +#pragma warning disable IDE0029 // Use coalesce expression Expression> expression1 = src => ((src != null ? src : null) != null) && src.Items.Any(x => x == "item1"); - +#pragma warning restore IDE0029 // Use coalesce expression + var mapped1 = mapper.MapExpression>>(expression1); Assert.NotNull(mapped1); diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/TypeExtensionsTest.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/TypeExtensionsTest.cs index 328f69c..35c0c55 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/TypeExtensionsTest.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/TypeExtensionsTest.cs @@ -51,8 +51,12 @@ public void Has_TypeWithoutAttribute_ReturnsFalse() private class ClassWithMultipleConstructors { public ClassWithMultipleConstructors() { } +#pragma warning disable IDE0060 // Remove unused parameter public ClassWithMultipleConstructors(int value) { } +#pragma warning restore IDE0060 // Remove unused parameter +#pragma warning disable IDE0060 // Remove unused parameter public ClassWithMultipleConstructors(string text, int value) { } +#pragma warning restore IDE0060 // Remove unused parameter } [Fact] @@ -75,7 +79,9 @@ public void GetDeclaredConstructors_ReturnsAllConstructors() private class ClassWithMethods { public void TestMethod() { } +#pragma warning disable IDE0060 // Remove unused parameter public void AnotherMethod(int value) { } +#pragma warning restore IDE0060 // Remove unused parameter } [Fact] @@ -116,7 +122,7 @@ public void GetDeclaredConstructor_WithMatchingParameters_ReturnsConstructor() var type = typeof(ClassWithMultipleConstructors); // Act - var constructor = type.GetDeclaredConstructor(new[] { typeof(int) }); + var constructor = type.GetDeclaredConstructor([typeof(int)]); // Assert Assert.NotNull(constructor); @@ -131,7 +137,7 @@ public void GetDeclaredConstructor_WithNonMatchingParameters_ReturnsNull() var type = typeof(ClassWithMultipleConstructors); // Act - var constructor = type.GetDeclaredConstructor(new[] { typeof(double) }); + var constructor = type.GetDeclaredConstructor([typeof(double)]); // Assert Assert.Null(constructor); @@ -144,7 +150,7 @@ public void GetDeclaredConstructor_WithMultipleParameters_ReturnsCorrectConstruc var type = typeof(ClassWithMultipleConstructors); // Act - var constructor = type.GetDeclaredConstructor(new[] { typeof(string), typeof(int) }); + var constructor = type.GetDeclaredConstructor([typeof(string), typeof(int)]); // Assert Assert.NotNull(constructor); diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.ForPath.Tests.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.ForPath.Tests.cs index da9c071..cac0c96 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.ForPath.Tests.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.ForPath.Tests.cs @@ -11,7 +11,7 @@ public class XpressionMapperForPathTests public XpressionMapperForPathTests() { SetupAutoMapper(); - SetupQueryableCollection(); + XpressionMapperForPathTests.SetupQueryableCollection(); } #region Tests @@ -23,10 +23,10 @@ public void Works_for_inherited_properties() //Act Expression> selectionMapped = mapper.Map>>(selection); - List items = DataObjects.Where(selectionMapped).ToList(); + List items = [.. DataObjects.Where(selectionMapped)]; //Assert - Assert.True(items.Count == 1); + Assert.Single(items); } [Fact] @@ -37,10 +37,10 @@ public void Works_for_inherited_properties_on_base_types() //Act Expression> selectionMapped = mapper.MapExpression>>(selection); - List items = DataObjects.Where(selectionMapped).ToList(); + List items = [.. DataObjects.Where(selectionMapped)]; //Assert - Assert.True(items.Count == 1); + Assert.Single(items); } [Fact] @@ -51,10 +51,10 @@ public void Works_for_top_level_string_member() //Act Expression> selectionMapped = mapper.Map>>(selection); - List items = Orders.Where(selectionMapped).ToList(); + List items = [.. Orders.Where(selectionMapped)]; //Assert - Assert.True(items.Count == 1); + Assert.Single(items); } [Fact] @@ -65,32 +65,30 @@ public void Works_for_top_level_value_type() //Act Expression> selectionMapped = mapper.Map>>(selection); - List items = Orders.Where(selectionMapped).ToList(); + List items = [.. Orders.Where(selectionMapped)]; //Assert - Assert.True(items.Count == 1); + Assert.Single(items); } #endregion Tests - private void SetupQueryableCollection() + private static void SetupQueryableCollection() { DataObjects = new DerivedData[] { - new DerivedData() { OtherID = 2, Title2 = "nested test", ID = 1, Title = "test", DescendantField = "descendant field" }, - new DerivedData() { OtherID = 3, Title2 = "nested", ID = 4, Title = "title", DescendantField = "some text" } + new() { OtherID = 2, Title2 = "nested test", ID = 1, Title = "test", DescendantField = "descendant field" }, + new() { OtherID = 3, Title2 = "nested", ID = 4, Title = "title", DescendantField = "some text" } }.AsQueryable(); Orders = new OrderDto[] { - new OrderDto - { + new() { Customer = new CustomerDto{ Name = "George Costanza", Total = 7 }, CustomerAddress = "333 First Ave", CustomerAge = 32 }, - new OrderDto - { + new() { Customer = new CustomerDto{ Name = "Jerry Springer", Total = 8 }, CustomerAddress = "444 First Ave", CustomerAge = 31 @@ -101,7 +99,7 @@ private void SetupQueryableCollection() private static IQueryable Orders { get; set; } private static IQueryable DataObjects { get; set; } - private void SetupAutoMapper() + private static void SetupAutoMapper() { var config = ConfigurationHelper.GetMapperConfiguration(cfg => { diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.Structs.Tests.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.Structs.Tests.cs index b6c2eb4..3787667 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.Structs.Tests.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapper.Structs.Tests.cs @@ -27,9 +27,9 @@ public void Can_map_value_types_constants_with_instance_methods() var mapper = config.CreateMapper(); - List source = new List { + List source = [ new Garage { Truck = default } - }; + ]; //Act var output1 = source.AsQueryable().GetItems(mapper, q => q.Truck.Equals(default(TruckModel))); @@ -57,9 +57,9 @@ public void Can_convert_return_type() var mapper = config.CreateMapper(); - List source = new List { + List source = [ new Garage { Truck = new Truck { Color = "blue", Year = 2000 } } - }; + ]; //Act var output1 = source.AsQueryable().GetItems(mapper, q => q.Truck.Year == 2000).Select(g => g.Truck); @@ -105,7 +105,7 @@ public void Can_map_local_variable_in_filter() config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); - Truck truck = new Truck { Color = "Red", Year = 1999 }; + Truck truck = new() { Color = "Red", Year = 1999 }; Expression> filter = m => m.Truck == truck; //Act @@ -130,7 +130,7 @@ public void Can_map_child_property_of_local_variable_in_filter() config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); - Garage garage = new Garage { Truck = new Truck { Color = "Red", Year = 1999 } }; + Garage garage = new() { Truck = new Truck { Color = "Red", Year = 1999 } }; Expression> filter = m => m.Truck == garage.Truck; //Act @@ -156,7 +156,7 @@ public void Can_map_listeral_child_property_of_local_variable_in_filter() config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); - GarageModel garage = new GarageModel { Color = "Blue", Truck = new TruckModel { Color = "Red", Year = 1999 } }; + GarageModel garage = new() { Color = "Blue", Truck = new TruckModel { Color = "Red", Year = 1999 } }; Expression> filter = m => m.Color == garage.Color; //Act @@ -182,7 +182,7 @@ public void Ignore_member_expressions_where_type_is_literal_and_node_type_is_con config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); - GarageModel garage = new GarageModel { Color = "Blue", Truck = new TruckModel { Color = "Red", Year = 1999 } }; + GarageModel garage = new() { Color = "Blue", Truck = new TruckModel { Color = "Red", Year = 1999 } }; Expression> filter = m => m.Color == garage.Color; //Act @@ -206,8 +206,8 @@ public void Can_map_local_variable_literal_in_filter() config.AssertConfigurationIsValid(); var mapper = config.CreateMapper(); - DateTime firstReleaseDate = new DateTime(); - DateTime lastReleaseDate = new DateTime(); + DateTime firstReleaseDate = new(); + DateTime lastReleaseDate = new(); Expression> exp = x => (firstReleaseDate == default || x.CreateDate >= firstReleaseDate) && (lastReleaseDate == default || x.CreateDate <= lastReleaseDate); @@ -245,10 +245,9 @@ public void Can_map_local_variable_nullable_in_filter() } } - public struct Source + public struct Source(int i) { - public Source(int i) { val = i; } - public int val; + public int val = i; public static implicit operator int(Source i) { @@ -300,25 +299,24 @@ public static implicit operator Source(int i) return a != b.val; } - public override int GetHashCode() + public override readonly int GetHashCode() { return this.val.GetHashCode(); } - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { - if (obj is Source) - return this == (Source)obj; - if (obj is int) - return this == (Source)((int)obj); + if (obj is Source source) + return this == source; + if (obj is int intVal) + return this == (Source)intVal; return false; } } - public struct Dest + public struct Dest(int i) { - public Dest(int i) { val = i; } - public int val; + public int val = i; public static implicit operator int(Dest i) { @@ -370,17 +368,17 @@ public static implicit operator Dest(int i) return a != b.val; } - public override int GetHashCode() + public override readonly int GetHashCode() { return this.val.GetHashCode(); } - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { - if (obj is Dest) - return this == (Dest)obj; - if (obj is int) - return this == (Dest)((int)obj); + if (obj is Dest dest) + return this == dest; + if (obj is int intVal) + return this == (Dest)intVal; return false; } } @@ -409,7 +407,7 @@ public struct Truck { return !(m1 == m2); } - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { if (obj is Truck mb) { @@ -417,7 +415,7 @@ public override bool Equals(object obj) } return false; } - public override int GetHashCode() + public override readonly int GetHashCode() { return Year.GetHashCode(); } @@ -436,7 +434,7 @@ public struct TruckModel { return !(m1 == m2); } - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { if (obj is TruckModel mb) { @@ -444,7 +442,7 @@ public override bool Equals(object obj) } return false; } - public override int GetHashCode() + public override readonly int GetHashCode() { return Year.GetHashCode(); } @@ -474,10 +472,10 @@ internal static ICollection GetItems(this IQueryable list = queryableFunc != null ? queryableFunc(query).ToList() : query.ToList(); + ICollection list = queryableFunc != null ? queryableFunc(query).ToList() : [.. query]; //Map and return the data - return mapper.Map, IEnumerable>(list).ToList(); + return [.. mapper.Map, IEnumerable>(list)]; } diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapperTests.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapperTests.cs index e8a1b23..f902053 100644 --- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapperTests.cs +++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapperTests.cs @@ -27,10 +27,10 @@ public void Map_object_type_change() //Act Expression> selectionMapped = mapper.Map>>(selection); - List users = Users.Where(selectionMapped).ToList(); + List users = [.. Users.Where(selectionMapped)]; //Assert - Assert.True(users.Count == 1); + Assert.Single(users); } [Fact] @@ -41,10 +41,10 @@ public void Map_works_with_members_from_interfaces() //Act Expression> selectionMapped = mapper.Map>>(selection); - List users = Users.Where(selectionMapped).ToList(); + List users = [.. Users.Where(selectionMapped)]; //Assert - Assert.True(users.Count == 1); + Assert.Single(users); } [Fact] @@ -55,10 +55,10 @@ public void Map_works_with_members_from_base_interfaces() //Act Expression> selectionMapped = mapper.Map>>(selection); - List users = Users.Where(selectionMapped).ToList(); + List users = [.. Users.Where(selectionMapped)]; //Assert - Assert.True(users.Count == 1); + Assert.Single(users); } [Fact] @@ -69,10 +69,10 @@ public void Map_object_type_change_again() //Act Expression> selectionMapped = mapper.Map>>(selection); - List users = Users.Where(selectionMapped).ToList(); + List users = [.. Users.Where(selectionMapped)]; //Assert - Assert.True(users.Count == 0); + Assert.Empty(users); } [Fact] @@ -83,10 +83,10 @@ public void Uses_the_correct_Add_expression_when_mapping_string_plus_operator() //Act Expression> selectionMapped = mapper.MapExpression>>(selection); - List users = Users.Where(selectionMapped).ToList(); + List users = [.. Users.Where(selectionMapped)]; //Assert - Assert.True(users.Count == 0); + Assert.Empty(users); } [Fact] @@ -97,10 +97,10 @@ public void Map__object_including_child_and_grandchild() //Act Expression> selectionMapped = mapper.Map>>(selection); - List users = Users.Where(selectionMapped).ToList(); + List users = [.. Users.Where(selectionMapped)]; //Assert - Assert.True(users.Count == 2); + Assert.Equal(2, users.Count); } [Fact] @@ -142,10 +142,10 @@ public void Map__object_including_child_and_grandchild_with_conditional_filter() ); Expression> selectionMapped = mapper.Map>>(selection); - List users = Users.Where(selectionMapped).ToList(); + List users = [.. Users.Where(selectionMapped)]; //Assert - Assert.True(users.Count == 1); + Assert.Single(users); } [Fact] @@ -175,10 +175,10 @@ public void Map_object_when_null_values_are_typed() //Act Expression> selectionMapped = mapper.MapExpression>>(selection); - List users = Users.Where(selectionMapped).ToList(); + List users = [.. Users.Where(selectionMapped)]; //Assert - Assert.True(users.Count == 2); + Assert.Equal(2, users.Count); } [Fact] @@ -251,10 +251,10 @@ public void Map_project_truncated_time() //Act Expression> selectionMapped = mapper.Map>>(selection); - List users = Users.Where(selectionMapped).ToList(); + List users = [.. Users.Where(selectionMapped)]; //Assert - Assert.True(users.Count == 0); + Assert.Empty(users); } [Fact] @@ -265,10 +265,10 @@ public void Map_projection() //Act Expression> selectionMapped = mapper.Map>>(selection); - List users = Users.Where(selectionMapped).ToList(); + List users = [.. Users.Where(selectionMapped)]; //Assert - Assert.True(users.Count == 1); + Assert.Single(users); } [Fact] @@ -280,10 +280,10 @@ public void Map__flattened_property() //Act Expression> selectionMapped = mapper.Map>>(selection); - List users = Users.Where(selectionMapped).ToList(); + List users = [.. Users.Where(selectionMapped)]; //Assert - Assert.True(users.Count == 1); + Assert.Single(users); } [Fact] @@ -294,10 +294,10 @@ public void Map__select_method() //Act Expression>> selectionMapped = mapper.Map>>>(selection); - List bars = Users.SelectMany(selectionMapped).ToList(); + List bars = [.. Users.SelectMany(selectionMapped)]; //Assert - Assert.True(bars.Count == 1); + Assert.Single(bars); } [Fact] @@ -308,10 +308,10 @@ public void Map__select_method_projecting_to_anonymous_type() //Act Expression>> selectionMapped = mapper.Map>>>(selection); - List bars = Users.SelectMany(selectionMapped).ToList(); + List bars = [.. Users.SelectMany(selectionMapped)]; //Assert - Assert.True(bars.Count == 1); + Assert.Single(bars); } [Fact] @@ -322,10 +322,10 @@ public void Map__select_method_projecting_to_model_type() //Act Expression>> selectionMapped = mapper.MapExpression>>>(selection); - List things = Users.SelectMany(selectionMapped).ToList(); + List things = [.. Users.SelectMany(selectionMapped)]; //Assert - Assert.True(things.Count == 2); + Assert.Equal(2, things.Count); } [Fact] @@ -388,10 +388,10 @@ public void Map__select_method_where_parent_type_is_grandchild_type() //Act Expression>> selectionMapped = mapper.Map>>>(selection); - List bars = Users.SelectMany(selectionMapped).ToList(); + List bars = [.. Users.SelectMany(selectionMapped)]; //Assert - Assert.True(bars.Count == 2); + Assert.Equal(2, bars.Count); } [Fact] @@ -402,10 +402,10 @@ public void Map_where_method() //Act Expression>> selectionMapped = mapper.Map>>>(selection); - List things = Users.SelectMany(selectionMapped).ToList(); + List things = [.. Users.SelectMany(selectionMapped)]; //Assert - Assert.True(things.Count == 0); + Assert.Empty(things); } [Fact] @@ -430,10 +430,10 @@ public void Map_orderBy_thenBy_expression() //Act Expression, IQueryable>> expMapped = mapper.Map, IQueryable>>>(exp); - List users = expMapped.Compile().Invoke(Users).ToList(); + List users = [.. expMapped.Compile().Invoke(Users)]; //Assert - Assert.True(users[0].UserId == 14); + Assert.Equal(14, users[0].UserId); } [Fact] @@ -444,9 +444,9 @@ public void Map_orderBy_thenBy_GroupBy_expression() //Act Expression, IQueryable>>> expMapped = mapper.Map, IQueryable>>>>(grouped); - List> users = expMapped.Compile().Invoke(Users).ToList(); + List> users = [.. expMapped.Compile().Invoke(Users)]; - Assert.True(users[0].Count() == 2); + Assert.Equal(2, users[0].Count()); } [Fact] @@ -470,9 +470,9 @@ public void Map_orderBy_thenBy_GroupBy_SelectMany_expression() //Act Expression, IQueryable>> expMapped = mapper.MapExpression, IQueryable>>>(grouped); - List users = expMapped.Compile().Invoke(Users).ToList(); + List users = [.. expMapped.Compile().Invoke(Users)]; - Assert.True(users.Count == 2); + Assert.Equal(2, users.Count); } [Fact] @@ -551,7 +551,7 @@ public void Map_to_anonymous_type_when_init_member_is_not_a_literal_and_paramete public void Map_to_anonymous_type_when_init_member_is_not_a_literal_with_navigation_property() { //Arrange - Expression, IEnumerable>> expression = q => q.OrderBy(s => s.Id).Select(u => new { UserId = u.Id, Branch = u.AccountModel.Branch }); + Expression, IEnumerable>> expression = q => q.OrderBy(s => s.Id).Select(u => new { UserId = u.Id, u.AccountModel.Branch }); //Act Expression, IEnumerable>> expMapped = (Expression, IEnumerable>>)mapper.MapExpression @@ -578,7 +578,7 @@ public void Map_dynamic_return_type() List users = Enumerable.ToList(expMapped.Compile().Invoke(Users)); //Assert - Assert.True(users[0].UserId == 11); + Assert.Equal(11, users[0].UserId); } [Fact] @@ -623,10 +623,10 @@ public void Map_parentDto_to_parent() public void Can_map_expression_where_parent_of_member_expression_is_typeAsExpression() { //Arrange - Parent[] parents = new Parent[] - { + Parent[] parents = + [ new Parent { DateTimeObject = new DateTime?(new DateTime(2019, 9, 7))} - }; + ]; Expression> exp = p => (p.DateTimeObject as DateTime?).Value.Year.ToString() == "2019"; //Act @@ -657,10 +657,10 @@ public void Map_accountModel_to_account() //Act Expression> expMapped = mapper.Map>>(exp); - List accounts = Users.Select(u => u.Account).Where(expMapped).ToList(); + List accounts = [.. Users.Select(u => u.Account).Where(expMapped)]; //Assert - Assert.True(accounts.Count == 2); + Assert.Equal(2, accounts.Count); } [Fact] @@ -698,8 +698,8 @@ public void Map_ItemDto_to_ItemDto_with_local_nullables() [Fact] public void Map_ItemDto_to_ItemDto_with_local_literal_types() { - DateTime firstReleaseDate = new DateTime(); - DateTime lastReleaseDate = new DateTime(); + DateTime firstReleaseDate = new(); + DateTime lastReleaseDate = new(); Expression> exp = x => (firstReleaseDate == default || x.CreateDate >= firstReleaseDate) && (lastReleaseDate == default || x.CreateDate <= lastReleaseDate); @@ -716,15 +716,15 @@ public void Map_accountModel_to_account_with_null_checks_against_value_types() { //Arrange Expression> exp = f => (f != null ? f.Id : 0) > 10 - && (f != null && f.DateCreated != default ? f.DateCreated : default(DateTime)) > new DateTime(2007, 02, 17); + && (f != null && f.DateCreated != default ? f.DateCreated : default) > new DateTime(2007, 02, 17); //Act Expression> expMapped = mapper.MapExpression>>(exp); - List accounts = Users.Select(u => u.Account).Where(expMapped).ToList(); + List accounts = [.. Users.Select(u => u.Account).Where(expMapped)]; //Assert - Assert.True(accounts.Count == 1); + Assert.Single(accounts); } [Fact] @@ -736,10 +736,10 @@ public void Map_accountModel_to_account_with_null_checks_against_string_type() //Act Expression> expMapped = mapper.MapExpression>>(exp); - List accounts = Users.Select(u => u.Account).Where(expMapped).ToList(); + List accounts = [.. Users.Select(u => u.Account).Where(expMapped)]; //Assert - Assert.True(accounts.Count == 0); + Assert.Empty(accounts); } [Fact] @@ -764,10 +764,10 @@ public void Map_accountModel_to_account_with_left_null_checks_against_string_typ //Act Expression> expMapped = mapper.MapExpression>>(exp); - List accounts = Users.Select(u => u.Account).Where(expMapped).ToList(); + List accounts = [.. Users.Select(u => u.Account).Where(expMapped)]; //Assert - Assert.True(accounts.Count == 0); + Assert.Empty(accounts); } @@ -860,29 +860,29 @@ public void Can_map_expression_when_mapped_properties_have_a_different_generic_a public void Can_map_expression_when_mapped_when_members_parent_is_a_method() { //Arrange - List empEntity = new List - { - new EmployeeEntity { Id = 1, Name = "Jean-Louis", Age = 39, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-1) } }.ToList() }, - new EmployeeEntity { Id = 2, Name = "Jean-Paul", Age = 32, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-2) } }.ToList() }, - new EmployeeEntity { Id = 3, Name = "Jean-Christophe", Age = 19, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-1) } }.ToList() }, - new EmployeeEntity { Id = 4, Name = "Jean-Marie", Age = 27, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-3) } }.ToList() }, - new EmployeeEntity { Id = 5, Name = "Jean-Marc", Age = 22, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-5) } }.ToList() }, - new EmployeeEntity { Id = 5, Name = "Jean-Pierre", Age = 22, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-5) } }.ToList() }, - new EmployeeEntity { Id = 6, Name = "Christophe", Age = 55, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-1) } }.ToList() }, - new EmployeeEntity { Id = 7, Name = "Marc", Age = 23, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-2) } }.ToList() }, - new EmployeeEntity { Id = 8, Name = "Paul", Age = 38, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-10) }, new EventEntity { EventType = "Stop", EventDate = DateTime.Today.AddYears(-1) } }.ToList() }, - new EmployeeEntity { Id = 9, Name = "Jean", Age = 32, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-10) }, new EventEntity { EventType = "Stop", EventDate = DateTime.Today.AddYears(-2) } }.ToList() }, - }; + List empEntity = + [ + new EmployeeEntity { Id = 1, Name = "Jean-Louis", Age = 39, Events = [new() { EventType = "Start", EventDate = DateTime.Today.AddYears(-1) }] }, + new EmployeeEntity { Id = 2, Name = "Jean-Paul", Age = 32, Events = [new() { EventType = "Start", EventDate = DateTime.Today.AddYears(-2) }] }, + new EmployeeEntity { Id = 3, Name = "Jean-Christophe", Age = 19, Events = [new() { EventType = "Start", EventDate = DateTime.Today.AddYears(-1) }] }, + new EmployeeEntity { Id = 4, Name = "Jean-Marie", Age = 27, Events = [new() { EventType = "Start", EventDate = DateTime.Today.AddYears(-3) }] }, + new EmployeeEntity { Id = 5, Name = "Jean-Marc", Age = 22, Events = [new() { EventType = "Start", EventDate = DateTime.Today.AddYears(-5) }] }, + new EmployeeEntity { Id = 5, Name = "Jean-Pierre", Age = 22, Events = [new() { EventType = "Start", EventDate = DateTime.Today.AddYears(-5) }] }, + new EmployeeEntity { Id = 6, Name = "Christophe", Age = 55, Events = [new() { EventType = "Start", EventDate = DateTime.Today.AddYears(-1) }] }, + new EmployeeEntity { Id = 7, Name = "Marc", Age = 23, Events = [new() { EventType = "Start", EventDate = DateTime.Today.AddYears(-2) }] }, + new EmployeeEntity { Id = 8, Name = "Paul", Age = 38, Events = [new() { EventType = "Start", EventDate = DateTime.Today.AddYears(-10) }, new() { EventType = "Stop", EventDate = DateTime.Today.AddYears(-1) }] }, + new EmployeeEntity { Id = 9, Name = "Jean", Age = 32, Events = [new() { EventType = "Start", EventDate = DateTime.Today.AddYears(-10) }, new() { EventType = "Stop", EventDate = DateTime.Today.AddYears(-2) }] }, + ]; Expression> filter = emp => emp.Events.Any(e => e.EventType.Equals("Stop")) && emp.Events.First(e => e.EventType.Equals("Stop")).EventDate < DateTime.Today.AddYears(-1); //Act Expression> mappedFilter = mapper.MapExpression>>(filter); - List res = empEntity.AsQueryable().Where(mappedFilter).ToList(); + List res = [.. empEntity.AsQueryable().Where(mappedFilter)]; //Assert - Assert.True(res.Count == 1); + Assert.Single(res); } [Fact] @@ -927,8 +927,7 @@ private static void SetupQueryableCollection() { Users = new User[] { - new User - { + new() { Account = new Account { Balance = 150000, @@ -937,14 +936,14 @@ private static void SetupQueryableCollection() Id = 12, Location = new Location { City = "Leeds", Latitude = 53.8008, Longitude = -1.5491 }, Number = "232232232", - Things = new Thing[] - { + Things = + [ new Thing { Bar = "Bar", Car = new Car { Color = "Black", Year = 2014 } }, new Thing { Bar = "Bar2", Car = new Car { Color = "White", Year = 2015 } } - }, + ], Type = "Personal", - Users = new User[] - { + Users = + [ new User { Active = true, @@ -954,7 +953,7 @@ private static void SetupQueryableCollection() IsLoggedOn = true, UserId = 11 } - } + ] }, Active = true, Age = 25, @@ -963,8 +962,7 @@ private static void SetupQueryableCollection() IsLoggedOn = true, UserId = 11 }, - new User - { + new() { Account = new Account { Balance = 200000, @@ -973,14 +971,14 @@ private static void SetupQueryableCollection() Id = 7, Location = new Location { City = "Leeds", Latitude = 53.8008, Longitude = -1.5491 }, Number = "444555444", - Things = new Thing[] - { + Things = + [ new Thing { Bar = "Bar3", Car = new Car { Color = "Black", Year = 2014 } }, new Thing { Bar = "Bar4", Car = new Car { Color = "White", Year = 2015 } } - }, + ], Type = "Business", - Users = new User[] - { + Users = + [ new User { Active = true, @@ -990,7 +988,7 @@ private static void SetupQueryableCollection() IsLoggedOn = false, UserId = 14 } - } + ] }, Active = true, Age = 25, @@ -1007,19 +1005,19 @@ private static void SetupQueryableCollection() public class OptionS { - public static OptionS GetNew() => new OptionS(); + public static OptionS GetNew() => new(); } public class OptionT { - public static OptionT GetNew() => new OptionT(); + public static OptionT GetNew() => new(); } public class Account { public Account() { - Things = new List(); + Things = []; } public int Id { get; set; } public double Balance { get; set; } @@ -1036,7 +1034,7 @@ public class AccountModel { public AccountModel() { - ThingModels = new List(); + ThingModels = []; } public int Id { get; set; } public double Bal { get; set; } @@ -1252,8 +1250,7 @@ public Parent Parent set { _parent = value; - if (GrandChild != null) - GrandChild.Parent = _parent; + GrandChild?.Parent = _parent; } } @@ -1403,7 +1400,7 @@ public class CheckDTO public class Part { - public List History { get; } = new List(); + public List History { get; } = []; public Guid ID { get; } = Guid.NewGuid(); } @@ -1431,7 +1428,7 @@ public OrganizationProfile() CreateMap() .ForMember(d => d.UserId, opt => opt.MapFrom(s => s.Id)) .ForMember(d => d.FirstName, opt => opt.MapFrom(s => s.FullName)) - .ForMember(d => d.IsLoggedOn, opt => opt.MapFrom(s => s.LoggedOn.ToUpper() == "Y")) + .ForMember(d => d.IsLoggedOn, opt => opt.MapFrom(s => s.LoggedOn.Equals("Y", StringComparison.CurrentCultureIgnoreCase))) .ForMember(d => d.Age, opt => opt.MapFrom(s => s.AgeInYears)) .ForMember(d => d.Active, opt => opt.MapFrom(s => s.IsActive)) .ForMember(d => d.Account, opt => opt.MapFrom(s => s.AccountModel)); @@ -1449,7 +1446,7 @@ public OrganizationProfile() CreateMap() .ForMember(d => d.UserId, opt => opt.MapFrom(s => s.Id)) .ForMember(d => d.FirstName, opt => opt.MapFrom(s => s.FullName)) - .ForMember(d => d.IsLoggedOn, opt => opt.MapFrom(s => s.LoggedOn.ToUpper() == "Y")) + .ForMember(d => d.IsLoggedOn, opt => opt.MapFrom(s => s.LoggedOn.Equals("Y", StringComparison.CurrentCultureIgnoreCase))) .ForMember(d => d.Age, opt => opt.MapFrom(s => s.AgeInYears)) .ForMember(d => d.Active, opt => opt.MapFrom(s => s.IsActive));