diff --git a/src/AutoMapper.Extensions.ExpressionMapping/AutoMapper.Extensions.ExpressionMapping.csproj b/src/AutoMapper.Extensions.ExpressionMapping/AutoMapper.Extensions.ExpressionMapping.csproj
index ef53d09..0ac4660 100644
--- a/src/AutoMapper.Extensions.ExpressionMapping/AutoMapper.Extensions.ExpressionMapping.csproj
+++ b/src/AutoMapper.Extensions.ExpressionMapping/AutoMapper.Extensions.ExpressionMapping.csproj
@@ -36,4 +36,19 @@
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
diff --git a/src/AutoMapper.Extensions.ExpressionMapping/FindMemberExpressionsVisitor.cs b/src/AutoMapper.Extensions.ExpressionMapping/FindMemberExpressionsVisitor.cs
index 24234bc..fe1543e 100644
--- a/src/AutoMapper.Extensions.ExpressionMapping/FindMemberExpressionsVisitor.cs
+++ b/src/AutoMapper.Extensions.ExpressionMapping/FindMemberExpressionsVisitor.cs
@@ -30,7 +30,7 @@ public MemberExpression Result
if (string.IsNullOrEmpty(result) || next.Contains(result))
result = next;
else throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
- Resource.includeExpressionTooComplex,
+ Properties.Resources.includeExpressionTooComplex,
string.Concat(_newParentExpression.Type.Name, period, result),
string.Concat(_newParentExpression.Type.Name, period, next)));
@@ -50,7 +50,7 @@ protected override Expression VisitMember(MemberExpression node)
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, Resource.mappedMemberIsChildOfTheParameterFormat, node.GetPropertyFullName(), node.Type.FullName, sType.FullName));
+ throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Properties.Resources.mappedMemberIsChildOfTheParameterFormat, node.GetPropertyFullName(), node.Type.FullName, sType.FullName));
else
_memberExpressions.Add(node);
}
diff --git a/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs b/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs
index c8df785..5051cf8 100644
--- a/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs
+++ b/src/AutoMapper.Extensions.ExpressionMapping/MapperExtensions.cs
@@ -109,7 +109,7 @@ TDestDelegate MapBody(Dictionary typeMappings, XpressionMapperVisito
TDestDelegate GetLambda(Dictionary typeMappings, XpressionMapperVisitor visitor, Expression mappedBody)
{
if (mappedBody == null)
- throw new InvalidOperationException(Resource.cantRemapExpression);
+ throw new InvalidOperationException(Properties.Resources.cantRemapExpression);
return (TDestDelegate)Lambda
(
@@ -255,7 +255,7 @@ public static List GetDestinationParameterExpressions(this
///
public static Dictionary AddTypeMapping(this Dictionary typeMappings, IConfigurationProvider configurationProvider)
=> typeMappings == null
- ? throw new ArgumentException(Resource.typeMappingsDictionaryIsNull)
+ ? throw new ArgumentException(Properties.Resources.typeMappingsDictionaryIsNull)
: typeMappings.AddTypeMapping(configurationProvider, typeof(TSource), typeof(TDest));
private static bool HasUnderlyingType(this Type type)
@@ -284,7 +284,7 @@ private static void AddUnderlyingTypes(this Dictionary typeMappings,
public static Dictionary AddTypeMapping(this Dictionary typeMappings, IConfigurationProvider configurationProvider, Type sourceType, Type destType)
{
if (typeMappings == null)
- throw new ArgumentException(Resource.typeMappingsDictionaryIsNull);
+ throw new ArgumentException(Properties.Resources.typeMappingsDictionaryIsNull);
if (sourceType.GetTypeInfo().IsGenericType && sourceType.GetGenericTypeDefinition() == typeof(Expression<>))
{
@@ -357,7 +357,7 @@ public static Type ReplaceType(this Dictionary typeMappings, Type so
private static Dictionary AddTypeMappingsFromDelegates(this Dictionary typeMappings, IConfigurationProvider configurationProvider, Type sourceType, Type destType)
{
if (typeMappings == null)
- throw new ArgumentException(Resource.typeMappingsDictionaryIsNull);
+ throw new ArgumentException(Properties.Resources.typeMappingsDictionaryIsNull);
typeMappings.DoAddTypeMappingsFromDelegates
(
@@ -372,7 +372,7 @@ private static Dictionary AddTypeMappingsFromDelegates(this Dictiona
private static void DoAddTypeMappingsFromDelegates(this Dictionary typeMappings, IConfigurationProvider configurationProvider, List sourceArguments, List destArguments)
{
if (sourceArguments.Count != destArguments.Count)
- throw new ArgumentException(Resource.invalidArgumentCount);
+ throw new ArgumentException(Properties.Resources.invalidArgumentCount);
for (int i = 0; i < sourceArguments.Count; i++)
{
diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Resource.Designer.cs b/src/AutoMapper.Extensions.ExpressionMapping/Properties/Resources.Designer.cs
similarity index 88%
rename from src/AutoMapper.Extensions.ExpressionMapping/Resource.Designer.cs
rename to src/AutoMapper.Extensions.ExpressionMapping/Properties/Resources.Designer.cs
index 8eddfcd..461bc9c 100644
--- a/src/AutoMapper.Extensions.ExpressionMapping/Resource.Designer.cs
+++ b/src/AutoMapper.Extensions.ExpressionMapping/Properties/Resources.Designer.cs
@@ -8,9 +8,8 @@
//
//------------------------------------------------------------------------------
-namespace AutoMapper.Extensions.ExpressionMapping {
+namespace AutoMapper.Extensions.ExpressionMapping.Properties {
using System;
- using System.Reflection;
///
@@ -20,17 +19,17 @@ namespace AutoMapper.Extensions.ExpressionMapping {
// 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", "4.0.0.0")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class Resource {
+ internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- internal Resource() {
+ internal Resources() {
}
///
@@ -40,7 +39,7 @@ internal Resource() {
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoMapper.Extensions.ExpressionMapping.Resource", typeof(Resource).GetTypeInfo().Assembly);
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AutoMapper.Extensions.ExpressionMapping.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
@@ -89,7 +88,7 @@ internal static string customResolversNotSupported {
}
///
- /// Looks up a localized string similar to The source and destination types must be the same for expression mapping between value types. Source Type: {0}, Source Description: {1}, Destination Type: {2}, Destination Property: {3}..
+ /// Looks up a localized string similar to The source and destination types must be the same for expression mapping between literal types. Source Type: {0}, Source Description: {1}, Destination Type: {2}, Destination Property: {3}..
///
internal static string expressionMapValueTypeMustMatchFormat {
get {
@@ -124,6 +123,15 @@ internal static string invalidExpErr {
}
}
+ ///
+ /// Looks up a localized string similar to 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}"..
+ ///
+ internal static string makeParentTypesMatchForMembersOfLiteralsFormat {
+ get {
+ return ResourceManager.GetString("makeParentTypesMatchForMembersOfLiteralsFormat", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to The mapped member {0} is of type {1} and a child of the parameter type {2}. No reference type (parent of) {0} is available to map as an include..
///
diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Properties/Resources.resx b/src/AutoMapper.Extensions.ExpressionMapping/Properties/Resources.resx
new file mode 100644
index 0000000..da39c6c
--- /dev/null
+++ b/src/AutoMapper.Extensions.ExpressionMapping/Properties/Resources.resx
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 1.3
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Cannot create a binary expression for the following pair. Node: {0}, Type: {1} and Node: {2}, Type: {3}.
+ 0=leftNode; 1=leftNodeType; 2=rightNode; 3=rightNodeType
+
+
+ Can't rempa expression
+
+
+ The source and destination types must be the same for expression mapping between literal types. Source Type: {0}, Source Description: {1}, Destination Type: {2}, Destination Property: {3}.
+ 0=Source Type; 1=SourceDescription; 2=Destination Type; 3=Destination Property.
+
+
+ The Include value-type expression uses multiple sibling navigation objects "{0}", "{1}" and is too complex to translate.
+ 0=FirstNavigationProperty, 1=SecondNavigationProperty
+
+
+ Source and destination must have the same number of arguments.
+
+
+ Invalid expression type for this operation.
+
+
+ Mapper Info dictionary cannot be null.
+
+
+ SourceMember cannot be null. Source Type: {0}, Destination Type: {1}, Property: {2}.
+ 0=SorceType; 1=DestinationType; 2=Name of the source property
+
+
+ Type Mappings dictionary cannot be null.
+
+
+ Custom resolvers are not supported for expression mapping.
+
+
+ Arguments must be expressions.
+
+
+ The mapped member {0} is of type {1} and a child of the parameter type {2}. No reference type (parent of) {0} is available to map as an include.
+ 0=memberName, 1=memberType; 2=parameterType
+
+
+ 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
+
+
\ No newline at end of file
diff --git a/src/AutoMapper.Extensions.ExpressionMapping/Resource.resx b/src/AutoMapper.Extensions.ExpressionMapping/Resource.resx
deleted file mode 100644
index ddc224b..0000000
--- a/src/AutoMapper.Extensions.ExpressionMapping/Resource.resx
+++ /dev/null
@@ -1,161 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- Cannot create a binary expression for the following pair. Node: {0}, Type: {1} and Node: {2}, Type: {3}.
- 0=leftNode; 1=leftNodeType; 2=rightNode; 3=rightNodeType
-
-
- Can't rempa expression
-
-
- The source and destination types must be the same for expression mapping between literal types. Source Type: {0}, Source Description: {1}, Destination Type: {2}, Destination Property: {3}.
- 0=Source Type; 1=SourceDescription; 2=Destination Type; 3=Destination Property.
-
-
- The Include value-type expression uses multiple sibling navigation objects "{0}", "{1}" and is too complex to translate.
- 0=FirstNavigationProperty, 1=SecondNavigationProperty
-
-
- Source and destination must have the same number of arguments.
-
-
- Invalid expression type for this operation.
-
-
- Mapper Info dictionary cannot be null.
-
-
- SourceMember cannot be null. Source Type: {0}, Destination Type: {1}, Property: {2}.
- 0=SorceType; 1=DestinationType; 2=Name of the source property
-
-
- Type Mappings dictionary cannot be null.
-
-
- Custom resolvers are not supported for expression mapping.
-
-
- Arguments must be expressions.
-
-
- The mapped member {0} is of type {1} and a child of the parameter type {2}. No reference type (parent of) {0} is available to map as an include.
- 0=memberName, 1=memberType; 2=parameterType
-
-
\ No newline at end of file
diff --git a/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs b/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs
index f1b9201..f8986b1 100644
--- a/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs
+++ b/src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs
@@ -95,11 +95,17 @@ Expression GetMappedMemberExpression(Expression parentExpression, List propertyMapInfoList)
{
+ if (typeSource.IsLiteralType()
+ && typeDestination.IsLiteralType()
+ && typeSource != typeDestination)
+ {
+ throw new InvalidOperationException
+ (
+ string.Format
+ (
+ CultureInfo.CurrentCulture,
+ Properties.Resources.makeParentTypesMatchForMembersOfLiteralsFormat,
+ typeSource,
+ typeDestination,
+ sourceFullName
+ )
+ );
+ }
+
const string period = ".";
bool BothTypesAreAnonymous()
=> IsAnonymousType(typeSource) && IsAnonymousType(typeDestination);
@@ -696,25 +719,11 @@ TypeMap GetTypeMap() => BothTypesAreAnonymous()
var sourceMemberInfo = typeSource.GetFieldOrProperty(propertyMap.GetDestinationName());
if (propertyMap.ValueResolverConfig != null)
{
- throw new InvalidOperationException(Resource.customResolversNotSupported);
+ throw new InvalidOperationException(Properties.Resources.customResolversNotSupported);
}
if (propertyMap.CustomMapExpression == null && !propertyMap.SourceMembers.Any())
- throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.srcMemberCannotBeNullFormat, typeSource.Name, typeDestination.Name, sourceFullName));
-
- CompareSourceAndDestLiterals
- (
- propertyMap.CustomMapExpression != null ? propertyMap.CustomMapExpression.ReturnType : propertyMap.SourceMember.GetMemberType(),
- propertyMap.CustomMapExpression != null ? propertyMap.CustomMapExpression.ToString() : propertyMap.SourceMember.Name,
- sourceMemberInfo.GetMemberType()
- );
-
- void CompareSourceAndDestLiterals(Type mappedPropertyType, string mappedPropertyDescription, Type sourceMemberType)
- {
- //switch from IsValueType to IsLiteralType because we do not want to throw an exception for all structs
- if ((mappedPropertyType.IsLiteralType() || sourceMemberType.IsLiteralType()) && sourceMemberType != mappedPropertyType)
- throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.expressionMapValueTypeMustMatchFormat, mappedPropertyType.Name, mappedPropertyDescription, sourceMemberType.Name, propertyMap.GetDestinationName()));
- }
+ 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()));
@@ -728,7 +737,7 @@ void CompareSourceAndDestLiterals(Type mappedPropertyType, string mappedProperty
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.
- throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.srcMemberCannotBeNullFormat, typeSource.Name, typeDestination.Name, propertyName));
+ 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()));
diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapMismatchedLiteralMemberExpressionsWithoutCustomExpressions.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapMismatchedLiteralMemberExpressionsWithoutCustomExpressions.cs
new file mode 100644
index 0000000..cfd5472
--- /dev/null
+++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/CanMapMismatchedLiteralMemberExpressionsWithoutCustomExpressions.cs
@@ -0,0 +1,269 @@
+using Microsoft.OData.Edm;
+using System;
+using System.Linq.Expressions;
+using Xunit;
+
+namespace AutoMapper.Extensions.ExpressionMapping.UnitTests
+{
+ public class CanMapMismatchedLiteralMemberExpressionsWithoutCustomExpressions
+ {
+ [Theory]
+ [InlineData(nameof(ProductModel.Bool), typeof(bool))]
+ [InlineData(nameof(ProductModel.DateTime), typeof(DateTime))]
+ [InlineData(nameof(ProductModel.DateTimeOffset), typeof(DateTimeOffset))]
+ [InlineData(nameof(ProductModel.Date), typeof(Date))]
+ [InlineData(nameof(ProductModel.DateOnly), typeof(DateOnly))]
+ [InlineData(nameof(ProductModel.TimeSpan), typeof(TimeSpan))]
+ [InlineData(nameof(ProductModel.TimeOfDay), typeof(TimeOfDay))]
+ [InlineData(nameof(ProductModel.TimeOnly), typeof(TimeOnly))]
+ [InlineData(nameof(ProductModel.Guid), typeof(Guid))]
+ [InlineData(nameof(ProductModel.Decimal), typeof(decimal))]
+ [InlineData(nameof(ProductModel.Byte), typeof(byte))]
+ [InlineData(nameof(ProductModel.Short), typeof(short))]
+ [InlineData(nameof(ProductModel.Int), typeof(int))]
+ [InlineData(nameof(ProductModel.Long), typeof(long))]
+ [InlineData(nameof(ProductModel.Float), typeof(float))]
+ [InlineData(nameof(ProductModel.Double), typeof(double))]
+ [InlineData(nameof(ProductModel.Char), typeof(char))]
+ [InlineData(nameof(ProductModel.SByte), typeof(sbyte))]
+ [InlineData(nameof(ProductModel.UShort), typeof(ushort))]
+ [InlineData(nameof(ProductModel.ULong), typeof(ulong))]
+ public void CanMapNonNullableToNullableWithoutCustomExpression(string memberName, Type constantType)
+ {
+ //arrange
+ var mapper = GetDataToModelMapper();
+ ParameterExpression productParam = Expression.Parameter(typeof(ProductModel), "x");
+ MemberExpression property = Expression.MakeMemberAccess(productParam, AutoMapper.Internal.TypeExtensions.GetFieldOrProperty(typeof(ProductModel), memberName));
+ object constantValue = Activator.CreateInstance(constantType);
+ Expression> expression = Expression.Lambda>
+ (
+ Expression.Equal
+ (
+ property,
+ Expression.Constant(constantValue, constantType)
+ ),
+ productParam
+ );
+ Product product = new();
+ typeof(Product).GetProperty(memberName).SetValue(product, constantValue);
+
+ //act
+ var mappedExpression = mapper.MapExpression>>(expression);
+
+ //assert
+ Assert.True(mappedExpression.Compile()(product));
+ }
+
+ [Theory]
+ [InlineData(nameof(Product.Bool), typeof(bool?))]
+ [InlineData(nameof(Product.DateTime), typeof(DateTime?))]
+ [InlineData(nameof(Product.DateTimeOffset), typeof(DateTimeOffset?))]
+ [InlineData(nameof(Product.Date), typeof(Date?))]
+ [InlineData(nameof(Product.DateOnly), typeof(DateOnly?))]
+ [InlineData(nameof(Product.TimeSpan), typeof(TimeSpan?))]
+ [InlineData(nameof(Product.TimeOfDay), typeof(TimeOfDay?))]
+ [InlineData(nameof(Product.TimeOnly), typeof(TimeOnly?))]
+ [InlineData(nameof(Product.Guid), typeof(Guid?))]
+ [InlineData(nameof(Product.Decimal), typeof(decimal?))]
+ [InlineData(nameof(Product.Byte), typeof(byte?))]
+ [InlineData(nameof(Product.Short), typeof(short?))]
+ [InlineData(nameof(Product.Int), typeof(int?))]
+ [InlineData(nameof(Product.Long), typeof(long?))]
+ [InlineData(nameof(Product.Float), typeof(float?))]
+ [InlineData(nameof(Product.Double), typeof(double?))]
+ [InlineData(nameof(Product.Char), typeof(char?))]
+ [InlineData(nameof(Product.SByte), typeof(sbyte?))]
+ [InlineData(nameof(Product.UShort), typeof(ushort?))]
+ [InlineData(nameof(Product.ULong), typeof(ulong?))]
+ public void CanMapNullableToNonNullableWithoutCustomExpression(string memberName, Type constantType)
+ {
+ //arrange
+ var mapper = GetModelToDataMapper();
+ ParameterExpression productParam = Expression.Parameter(typeof(Product), "x");
+ MemberExpression property = Expression.MakeMemberAccess(productParam, AutoMapper.Internal.TypeExtensions.GetFieldOrProperty(typeof(Product), memberName));
+ object constantValue = Activator.CreateInstance(Nullable.GetUnderlyingType(constantType));
+ Expression> expression = Expression.Lambda>
+ (
+ Expression.Equal
+ (
+ property,
+ Expression.Constant(constantValue, constantType)
+ ),
+ productParam
+ );
+ ProductModel product = new();
+ typeof(ProductModel).GetProperty(memberName).SetValue(product, constantValue);
+
+ //act
+ var mappedExpression = mapper.MapExpression>>(expression);
+
+ //assert
+ Assert.True(mappedExpression.Compile()(product));
+ }
+
+ [Theory]
+ [InlineData(nameof(ProductModel.Bool), typeof(bool))]
+ [InlineData(nameof(ProductModel.DateTime), typeof(DateTime))]
+ [InlineData(nameof(ProductModel.DateTimeOffset), typeof(DateTimeOffset))]
+ [InlineData(nameof(ProductModel.Date), typeof(Date))]
+ [InlineData(nameof(ProductModel.DateOnly), typeof(DateOnly))]
+ [InlineData(nameof(ProductModel.TimeSpan), typeof(TimeSpan))]
+ [InlineData(nameof(ProductModel.TimeOfDay), typeof(TimeOfDay))]
+ [InlineData(nameof(ProductModel.TimeOnly), typeof(TimeOnly))]
+ [InlineData(nameof(ProductModel.Guid), typeof(Guid))]
+ [InlineData(nameof(ProductModel.Decimal), typeof(decimal))]
+ [InlineData(nameof(ProductModel.Byte), typeof(byte))]
+ [InlineData(nameof(ProductModel.Short), typeof(short))]
+ [InlineData(nameof(ProductModel.Int), typeof(int))]
+ [InlineData(nameof(ProductModel.Long), typeof(long))]
+ [InlineData(nameof(ProductModel.Float), typeof(float))]
+ [InlineData(nameof(ProductModel.Double), typeof(double))]
+ [InlineData(nameof(ProductModel.Char), typeof(char))]
+ [InlineData(nameof(ProductModel.SByte), typeof(sbyte))]
+ [InlineData(nameof(ProductModel.UShort), typeof(ushort))]
+ [InlineData(nameof(ProductModel.ULong), typeof(ulong))]
+ public void CanMapNonNullableSelectorToNullableelectorWithoutCustomExpression(string memberName, Type memberType)
+ {
+ var mapper = GetDataToModelMapper();
+ ParameterExpression productParam = Expression.Parameter(typeof(ProductModel), "x");
+ MemberExpression property = Expression.MakeMemberAccess(productParam, AutoMapper.Internal.TypeExtensions.GetFieldOrProperty(typeof(ProductModel), memberName));
+ Type sourceType = typeof(Func<,>).MakeGenericType(typeof(ProductModel), memberType);
+ Type destType = typeof(Func<,>).MakeGenericType(typeof(Product), memberType);
+ Type sourceExpressionype = typeof(Expression<>).MakeGenericType(sourceType);
+ Type destExpressionType = typeof(Expression<>).MakeGenericType(destType);
+ var expression = Expression.Lambda
+ (
+ sourceType,
+ property,
+ productParam
+ );
+ object constantValue = Activator.CreateInstance(memberType);
+ Product product = new();
+ typeof(Product).GetProperty(memberName).SetValue(product, constantValue);
+
+ //act
+ var mappedExpression = mapper.MapExpression(expression, sourceExpressionype, destExpressionType);
+
+ //assert
+ Assert.Equal(constantValue, mappedExpression.Compile().DynamicInvoke(product));
+ }
+
+ [Theory]
+ [InlineData(nameof(Product.Bool), typeof(bool?))]
+ [InlineData(nameof(Product.DateTime), typeof(DateTime?))]
+ [InlineData(nameof(Product.DateTimeOffset), typeof(DateTimeOffset?))]
+ [InlineData(nameof(Product.Date), typeof(Date?))]
+ [InlineData(nameof(Product.DateOnly), typeof(DateOnly?))]
+ [InlineData(nameof(Product.TimeSpan), typeof(TimeSpan?))]
+ [InlineData(nameof(Product.TimeOfDay), typeof(TimeOfDay?))]
+ [InlineData(nameof(Product.TimeOnly), typeof(TimeOnly?))]
+ [InlineData(nameof(Product.Guid), typeof(Guid?))]
+ [InlineData(nameof(Product.Decimal), typeof(decimal?))]
+ [InlineData(nameof(Product.Byte), typeof(byte?))]
+ [InlineData(nameof(Product.Short), typeof(short?))]
+ [InlineData(nameof(Product.Int), typeof(int?))]
+ [InlineData(nameof(Product.Long), typeof(long?))]
+ [InlineData(nameof(Product.Float), typeof(float?))]
+ [InlineData(nameof(Product.Double), typeof(double?))]
+ [InlineData(nameof(Product.Char), typeof(char?))]
+ [InlineData(nameof(Product.SByte), typeof(sbyte?))]
+ [InlineData(nameof(Product.UShort), typeof(ushort?))]
+ [InlineData(nameof(Product.ULong), typeof(ulong?))]
+ public void CanMapNullableSelectorToNonNullableelectorWithoutCustomExpression(string memberName, Type memberType)
+ {
+ var mapper = GetModelToDataMapper();
+ ParameterExpression productParam = Expression.Parameter(typeof(Product), "x");
+ MemberExpression property = Expression.MakeMemberAccess(productParam, AutoMapper.Internal.TypeExtensions.GetFieldOrProperty(typeof(Product), memberName));
+ Type sourceType = typeof(Func<,>).MakeGenericType(typeof(Product), memberType);
+ Type destType = typeof(Func<,>).MakeGenericType(typeof(ProductModel), memberType);
+ Type sourceExpressionype = typeof(Expression<>).MakeGenericType(sourceType);
+ Type destExpressionType = typeof(Expression<>).MakeGenericType(destType);
+ var expression = Expression.Lambda
+ (
+ sourceType,
+ property,
+ productParam
+ );
+
+ object constantValue = Activator.CreateInstance(Nullable.GetUnderlyingType(memberType));
+ ProductModel product = new();
+ typeof(ProductModel).GetProperty(memberName).SetValue(product, constantValue);
+
+ //act
+ var mappedExpression = mapper.MapExpression(expression, sourceExpressionype, destExpressionType);
+
+ //assert
+ Assert.Equal(constantValue, mappedExpression.Compile().DynamicInvoke(product));
+ }
+
+ private static IMapper GetModelToDataMapper()
+ {
+ var config = new MapperConfiguration(c =>
+ {
+ c.CreateMap();
+ });
+ config.AssertConfigurationIsValid();
+ return config.CreateMapper();
+ }
+
+ private static IMapper GetDataToModelMapper()
+ {
+ var config = new MapperConfiguration(c =>
+ {
+ c.CreateMap();
+ });
+ config.AssertConfigurationIsValid();
+ return config.CreateMapper();
+ }
+
+ class Product
+ {
+ public bool? Bool { get; set; }
+ public DateTimeOffset? DateTimeOffset { get; set; }
+ public DateTime? DateTime { get; set; }
+ public Date? Date { get; set; }
+ public DateOnly? DateOnly { get; set; }
+ public TimeSpan? TimeSpan { get; set; }
+ public TimeOfDay? TimeOfDay { get; set; }
+ public TimeOnly? TimeOnly { get; set; }
+ public Guid? Guid { get; set; }
+ public decimal? Decimal { get; set; }
+ public byte? Byte { get; set; }
+ public short? Short { get; set; }
+ public int? Int { get; set; }
+ public long? Long { get; set; }
+ public float? Float { get; set; }
+ public double? Double { get; set; }
+ public char? Char { get; set; }
+ public sbyte? SByte { get; set; }
+ public ushort? UShort { get; set; }
+ public uint? UInt { get; set; }
+ public ulong? ULong { get; set; }
+ }
+
+ class ProductModel
+ {
+ public bool Bool { get; set; }
+ public DateTimeOffset DateTimeOffset { get; set; }
+ public DateTime DateTime { get; set; }
+ public Date Date { get; set; }
+ public DateOnly DateOnly { get; set; }
+ public TimeSpan TimeSpan { get; set; }
+ public TimeOfDay TimeOfDay { get; set; }
+ public TimeOnly TimeOnly { get; set; }
+ public Guid Guid { get; set; }
+ public decimal Decimal { get; set; }
+ public byte Byte { get; set; }
+ public short Short { get; set; }
+ public int Int { get; set; }
+ public long Long { get; set; }
+ public float Float { get; set; }
+ public double Double { get; set; }
+ public char Char { get; set; }
+ public sbyte SByte { get; set; }
+ public ushort UShort { get; set; }
+ public uint UInt { get; set; }
+ public ulong ULong { get; set; }
+ }
+ }
+}
diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MemberMappingsOfLiteralParentTypesMustMatch.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MemberMappingsOfLiteralParentTypesMustMatch.cs
new file mode 100644
index 0000000..27b7a4d
--- /dev/null
+++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/MemberMappingsOfLiteralParentTypesMustMatch.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Linq.Expressions;
+using Xunit;
+
+namespace AutoMapper.Extensions.ExpressionMapping.UnitTests
+{
+ public class MemberMappingsOfLiteralParentTypesMustMatch
+ {
+ [Fact]
+ public void MappingMemberOfNullableParentToMemberOfNonNullableParentWithoutCustomExpressionsThrowsException()
+ {
+ //arrange
+ var mapper = GetMapper();
+ Expression> expression = x => x.DateTimeOffset.Value.Day.ToString() == "2";
+
+ //act
+ var exception = Assert.Throws(() => mapper.MapExpression>>(expression));
+
+ //assert
+ Assert.StartsWith
+ (
+ "For members of literal types, use IMappingExpression.ForMember() to make the parent property types an exact match.",
+ exception.Message
+ );
+ }
+
+ [Fact]
+ public void MappingMemberOfNonNullableParentToMemberOfNullableParentWithoutCustomExpressionsThrowsException()
+ {
+ //arrange
+ var mapper = GetMapper();
+ Expression> expression = x => x.DateTime.Day.ToString() == "2";
+
+ //act
+ var exception = Assert.Throws(() => mapper.MapExpression>>(expression));
+
+ //assert
+ Assert.StartsWith
+ (
+ "For members of literal types, use IMappingExpression.ForMember() to make the parent property types an exact match.",
+ exception.Message
+ );
+ }
+
+ [Fact]
+ public void MappingMemberOfNullableParentToMemberOfNonNullableParentWorksUsingCustomExpressions()
+ {
+ //arrange
+ var mapper = GetMapperWithCustomExpressions();
+ Expression> expression = x => x.DateTimeOffset.Value.Day.ToString() == "2";
+
+ //act
+ var mappedExpression = mapper.MapExpression>>(expression);
+
+ //assert
+ Assert.NotNull(mappedExpression);
+ Func func = mappedExpression.Compile();
+ Assert.False(func(new Product { DateTimeOffset = new DateTimeOffset(new DateTime(2000, 3, 3), TimeSpan.Zero) }));
+ Assert.True(func(new Product { DateTimeOffset = new DateTimeOffset(new DateTime(2000, 2, 2), TimeSpan.Zero) }));
+ }
+
+ [Fact]
+ public void MappingMemberOfNonNullableParentToMemberOfNullableParentWorksUsingCustomExpressions()
+ {
+ //arrange
+ var mapper = GetMapperWithCustomExpressions();
+ Expression> expression = x => x.DateTime.Day.ToString() == "2";
+
+ //act
+ var mappedExpression = mapper.MapExpression>>(expression);
+
+ //assert
+ Assert.NotNull(mappedExpression);
+ Func func = mappedExpression.Compile();
+ Assert.False(func(new Product { DateTime = new DateTime(2000, 3, 3) }));
+ Assert.True(func(new Product { DateTime = new DateTime(2000, 2, 2) }));
+ }
+
+
+ private static IMapper GetMapper()
+ {
+ var config = new MapperConfiguration(c =>
+ {
+ c.CreateMap();
+ });
+ config.AssertConfigurationIsValid();
+ return config.CreateMapper();
+ }
+
+ private static IMapper GetMapperWithCustomExpressions()
+ {
+ var config = new MapperConfiguration(c =>
+ {
+ c.CreateMap()
+ .ForMember(d => d.DateTime, o => o.MapFrom(s => s.DateTime.Value))
+ .ForMember(d => d.DateTimeOffset, o => o.MapFrom(s => (DateTimeOffset?)s.DateTimeOffset));
+ });
+ config.AssertConfigurationIsValid();
+ return config.CreateMapper();
+ }
+
+ class Product
+ {
+ public DateTime? DateTime { get; set; }
+ public DateTimeOffset DateTimeOffset { get; set; }
+ }
+
+ class ProductModel
+ {
+ public DateTime DateTime { get; set; }
+ public DateTimeOffset? DateTimeOffset { get; set; }
+ }
+ }
+}
diff --git a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ShouldThrowInvalidOperationExceptionForUnmatchedLiterals.cs b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ShouldThrowInvalidOperationExceptionForUnmatchedLiterals.cs
index f2454c6..6fb88f9 100644
--- a/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ShouldThrowInvalidOperationExceptionForUnmatchedLiterals.cs
+++ b/tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/ShouldThrowInvalidOperationExceptionForUnmatchedLiterals.cs
@@ -30,7 +30,6 @@ public class ShouldThrowInvalidOperationExceptionForUnmatchedLiterals
[InlineData(nameof(ProductModel.ULong), typeof(ulong?))]
public void ThrowsCreatingBinaryExpressionCombiningNonNullableParameterWithNullableConstant(string memberName, Type constantType)
{
- var mapper = GetMapper();
ParameterExpression productParam = Expression.Parameter(typeof(ProductModel), "x");
MemberExpression property = Expression.MakeMemberAccess(productParam, AutoMapper.Internal.TypeExtensions.GetFieldOrProperty(typeof(ProductModel), memberName));
@@ -71,7 +70,6 @@ public void ThrowsCreatingBinaryExpressionCombiningNonNullableParameterWithNulla
[InlineData(nameof(Product.ULong), typeof(ulong))]
public void ThrowsCreatingBinaryExpressionCombiningNullableParameterWithNonNullableConstant(string memberName, Type constantType)
{
- var mapper = GetMapper();
ParameterExpression productParam = Expression.Parameter(typeof(Product), "x");
MemberExpression property = Expression.MakeMemberAccess(productParam, AutoMapper.Internal.TypeExtensions.GetFieldOrProperty(typeof(Product), memberName));
@@ -89,138 +87,6 @@ public void ThrowsCreatingBinaryExpressionCombiningNullableParameterWithNonNulla
);
}
- [Theory]
- [InlineData(nameof(ProductModel.Bool), typeof(bool))]
- [InlineData(nameof(ProductModel.DateTime), typeof(DateTime))]
- [InlineData(nameof(ProductModel.DateTimeOffset), typeof(DateTimeOffset))]
- [InlineData(nameof(ProductModel.Date), typeof(Date))]
- [InlineData(nameof(ProductModel.DateOnly), typeof(DateOnly))]
- [InlineData(nameof(ProductModel.TimeSpan), typeof(TimeSpan))]
- [InlineData(nameof(ProductModel.TimeOfDay), typeof(TimeOfDay))]
- [InlineData(nameof(ProductModel.TimeOnly), typeof(TimeOnly))]
- [InlineData(nameof(ProductModel.Guid), typeof(Guid))]
- [InlineData(nameof(ProductModel.Decimal), typeof(decimal))]
- [InlineData(nameof(ProductModel.Byte), typeof(byte))]
- [InlineData(nameof(ProductModel.Short), typeof(short))]
- [InlineData(nameof(ProductModel.Int), typeof(int))]
- [InlineData(nameof(ProductModel.Long), typeof(long))]
- [InlineData(nameof(ProductModel.Float), typeof(float))]
- [InlineData(nameof(ProductModel.Double), typeof(double))]
- [InlineData(nameof(ProductModel.Char), typeof(char))]
- [InlineData(nameof(ProductModel.SByte), typeof(sbyte))]
- [InlineData(nameof(ProductModel.UShort), typeof(ushort))]
- [InlineData(nameof(ProductModel.ULong), typeof(ulong))]
- public void ThrowsmappingExpressionWithMismatchedOperands(string memberName, Type constantType)
- {
- var mapper = GetMapper();
- ParameterExpression productParam = Expression.Parameter(typeof(ProductModel), "x");
- MemberExpression property = Expression.MakeMemberAccess(productParam, AutoMapper.Internal.TypeExtensions.GetFieldOrProperty(typeof(ProductModel), memberName));
-
- Expression> expression = Expression.Lambda>
- (
- Expression.Equal
- (
- property,
- Expression.Constant(Activator.CreateInstance(constantType), constantType)
- ),
- productParam
- );
-
- var exception = Assert.Throws
- (
- () => mapper.MapExpression>>(expression)
- );
-
- Assert.StartsWith
- (
- "The source and destination types must be the same for expression mapping between literal types.",
- exception.Message
- );
- }
-
- [Theory]
- [InlineData(nameof(ProductModel.Bool), typeof(bool))]
- [InlineData(nameof(ProductModel.DateTime), typeof(DateTime))]
- [InlineData(nameof(ProductModel.DateTimeOffset), typeof(DateTimeOffset))]
- [InlineData(nameof(ProductModel.Date), typeof(Date))]
- [InlineData(nameof(ProductModel.DateOnly), typeof(DateOnly))]
- [InlineData(nameof(ProductModel.TimeSpan), typeof(TimeSpan))]
- [InlineData(nameof(ProductModel.TimeOfDay), typeof(TimeOfDay))]
- [InlineData(nameof(ProductModel.TimeOnly), typeof(TimeOnly))]
- [InlineData(nameof(ProductModel.Guid), typeof(Guid))]
- [InlineData(nameof(ProductModel.Decimal), typeof(decimal))]
- [InlineData(nameof(ProductModel.Byte), typeof(byte))]
- [InlineData(nameof(ProductModel.Short), typeof(short))]
- [InlineData(nameof(ProductModel.Int), typeof(int))]
- [InlineData(nameof(ProductModel.Long), typeof(long))]
- [InlineData(nameof(ProductModel.Float), typeof(float))]
- [InlineData(nameof(ProductModel.Double), typeof(double))]
- [InlineData(nameof(ProductModel.Char), typeof(char))]
- [InlineData(nameof(ProductModel.SByte), typeof(sbyte))]
- [InlineData(nameof(ProductModel.UShort), typeof(ushort))]
- [InlineData(nameof(ProductModel.ULong), typeof(ulong))]
- public void MappingExpressionWorksUsingCustomExpressionToResolveBinaryOperators(string memberName, Type constantType)
- {
- var mapper = GetMapperWithCustomExpressions();
- ParameterExpression productParam = Expression.Parameter(typeof(ProductModel), "x");
- MemberExpression property = Expression.MakeMemberAccess(productParam, AutoMapper.Internal.TypeExtensions.GetFieldOrProperty(typeof(ProductModel), memberName));
-
- Expression> expression = Expression.Lambda>
- (
- Expression.Equal
- (
- property,
- Expression.Constant(Activator.CreateInstance(constantType), constantType)
- ),
- productParam
- );
-
- var mappedExpression = mapper.MapExpression>>(expression);
- Assert.NotNull(mappedExpression);
- }
-
- private static IMapper GetMapper()
- {
- var config = new MapperConfiguration(c =>
- {
- c.CreateMap();
- });
- config.AssertConfigurationIsValid();
- return config.CreateMapper();
- }
-
- private static IMapper GetMapperWithCustomExpressions()
- {
- var config = new MapperConfiguration(c =>
- {
- c.CreateMap()
- .ForMember(d => d.Bool, o => o.MapFrom(s => s.Bool.Value))
- .ForMember(d => d.DateTime, o => o.MapFrom(s => s.DateTime.Value))
- .ForMember(d => d.DateTimeOffset, o => o.MapFrom(s => s.DateTimeOffset.Value))
- .ForMember(d => d.Date, o => o.MapFrom(s => s.Date.Value))
- .ForMember(d => d.DateOnly, o => o.MapFrom(s => s.DateOnly.Value))
- .ForMember(d => d.TimeSpan, o => o.MapFrom(s => s.TimeSpan.Value))
- .ForMember(d => d.TimeOfDay, o => o.MapFrom(s => s.TimeOfDay.Value))
- .ForMember(d => d.TimeOnly, o => o.MapFrom(s => s.TimeOnly.Value))
- .ForMember(d => d.Guid, o => o.MapFrom(s => s.Guid.Value))
- .ForMember(d => d.Decimal, o => o.MapFrom(s => s.Decimal.Value))
- .ForMember(d => d.Byte, o => o.MapFrom(s => s.Byte.Value))
- .ForMember(d => d.Short, o => o.MapFrom(s => s.Short.Value))
- .ForMember(d => d.Int, o => o.MapFrom(s => s.Int.Value))
- .ForMember(d => d.Long, o => o.MapFrom(s => s.Long.Value))
- .ForMember(d => d.Float, o => o.MapFrom(s => s.Float.Value))
- .ForMember(d => d.Double, o => o.MapFrom(s => s.Double.Value))
- .ForMember(d => d.Char, o => o.MapFrom(s => s.Char.Value))
- .ForMember(d => d.SByte, o => o.MapFrom(s => s.SByte.Value))
- .ForMember(d => d.UShort, o => o.MapFrom(s => s.UShort.Value))
- .ForMember(d => d.UInt, o => o.MapFrom(s => s.UInt.Value))
- .ForMember(d => d.ULong, o => o.MapFrom(s => s.ULong.Value));
- });
-
- config.AssertConfigurationIsValid();
- return config.CreateMapper();
- }
-
class Product
{
public bool? Bool { get; set; }