Skip to content

Commit 1e8672b

Browse files
Merge pull request #104 from Tasteful/feature/null-should-not-throw-NRE
Use 0 instead of GetHashCode() when member is null
2 parents 1a122a4 + 98d9268 commit 1e8672b

File tree

3 files changed

+104
-17
lines changed

3 files changed

+104
-17
lines changed

src/AutoMapper.Collection.EntityFramework.Tests/AutoMapper.Collection.EntityFramework.Tests.csproj

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@
66
<IsPackable>false</IsPackable>
77
</PropertyGroup>
88

9-
<ItemGroup>
10-
<PackageReference Include="System.ValueTuple" Version="4.3.0" />
11-
</ItemGroup>
12-
139
<ItemGroup>
1410
<ProjectReference Include="..\AutoMapper.Collection.EntityFramework\AutoMapper.Collection.EntityFramework.csproj" />
1511
</ItemGroup>
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System.Collections.Generic;
2+
using AutoMapper.EquivalencyExpression;
3+
using FluentAssertions;
4+
using Xunit;
5+
6+
namespace AutoMapper.Collection
7+
{
8+
public class NullableIdTests
9+
{
10+
[Fact]
11+
public void Should_Work_With_Null_Id()
12+
{
13+
Mapper.Reset();
14+
Mapper.Initialize(x =>
15+
{
16+
x.AddCollectionMappers();
17+
x.CreateMap<ThingWithStringIdDto, ThingWithStringId>().EqualityComparison((dto, entity) => dto.ID == entity.ID);
18+
});
19+
20+
var original = new List<ThingWithStringId>
21+
{
22+
new ThingWithStringId { ID = "1", Title = "test0" },
23+
new ThingWithStringId { ID = "2", Title = "test2" },
24+
};
25+
26+
var dtos = new List<ThingWithStringIdDto>
27+
{
28+
new ThingWithStringIdDto { ID = "1", Title = "test0" },
29+
new ThingWithStringIdDto { ID = "2", Title = "test2" },
30+
new ThingWithStringIdDto { Title = "test3" }
31+
};
32+
33+
Mapper.Map(dtos, original);
34+
35+
original.Should().HaveSameCount(dtos);
36+
}
37+
38+
39+
[Fact]
40+
public void Should_Work_With_Multiple_Null_Id()
41+
{
42+
Mapper.Reset();
43+
Mapper.Initialize(x =>
44+
{
45+
x.AddCollectionMappers();
46+
x.CreateMap<ThingWithStringIdDto, ThingWithStringId>().EqualityComparison((dto, entity) => dto.ID == entity.ID);
47+
});
48+
49+
var original = new List<ThingWithStringId>
50+
{
51+
new ThingWithStringId { ID = "1", Title = "test0" },
52+
new ThingWithStringId { ID = "2", Title = "test2" },
53+
new ThingWithStringId { ID = "3", Title = "test3" },
54+
};
55+
56+
var dtos = new List<ThingWithStringIdDto>
57+
{
58+
new ThingWithStringIdDto { ID = "1", Title = "test0" },
59+
new ThingWithStringIdDto { ID = "2", Title = "test2" },
60+
new ThingWithStringIdDto { Title = "test3" },
61+
new ThingWithStringIdDto { Title = "test4" },
62+
};
63+
64+
Mapper.Map(dtos, original);
65+
66+
original.Should().HaveSameCount(dtos);
67+
}
68+
69+
public class ThingWithStringId
70+
{
71+
public string ID { get; set; }
72+
public string Title { get; set; }
73+
public override string ToString() { return Title; }
74+
}
75+
76+
public class ThingWithStringIdDto
77+
{
78+
public string ID { get; set; }
79+
public string Title { get; set; }
80+
}
81+
}
82+
}

src/AutoMapper.Collection/EquivalencyExpression/ExpressionExtentions.cs

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Collections.Concurrent;
33
using System.Collections.Generic;
4-
using System.Linq;
54
using System.Linq.Expressions;
65
using System.Reflection;
76
using AutoMapper.Collection;
@@ -16,30 +15,30 @@ public static Type GetSinglePredicateExpressionArgumentType(this Type type)
1615
{
1716
return _singleParameterTypeDictionary.GetOrAdd(type, t =>
1817
{
19-
var isExpression = typeof (Expression).GetTypeInfo().IsAssignableFrom(t.GetTypeInfo());
18+
var isExpression = typeof(Expression).GetTypeInfo().IsAssignableFrom(t.GetTypeInfo());
2019
if (!isExpression)
2120
return null;
2221

23-
var expressionOf = t.GetTypeInfo().GenericTypeArguments.First();
24-
var isFunction = expressionOf.GetGenericTypeDefinition() == typeof (Func<,>);
22+
var expressionOf = t.GetTypeInfo().GenericTypeArguments[0];
23+
var isFunction = expressionOf.GetGenericTypeDefinition() == typeof(Func<,>);
2524
if (!isFunction)
2625
return null;
2726

28-
var isPredicate = expressionOf.GetTypeInfo().GenericTypeArguments[1] == typeof (bool);
27+
var isPredicate = expressionOf.GetTypeInfo().GenericTypeArguments[1] == typeof(bool);
2928
if (!isPredicate)
3029
return null;
3130

32-
var objType = expressionOf.GetTypeInfo().GenericTypeArguments.First();
31+
var objType = expressionOf.GetTypeInfo().GenericTypeArguments[0];
3332
return CacheAndReturnType(type, objType);
3433
});
3534
}
3635

3736
private static Type CacheAndReturnType(Type type, Type objType)
3837
{
39-
_singleParameterTypeDictionary.AddOrUpdate(type, objType, (t,t2) => objType);
38+
_singleParameterTypeDictionary.AddOrUpdate(type, objType, (_, __) => objType);
4039
return objType;
4140
}
42-
41+
4342
public static Expression<Func<T, int>> GetHashCodeExpression<T>(this List<Expression> members, ParameterExpression sourceParam)
4443
{
4544
var hashMultiply = Expression.Constant(397L);
@@ -48,20 +47,30 @@ public static Expression<Func<T, int>> GetHashCodeExpression<T>(this List<Expres
4847
var returnTarget = Expression.Label(typeof(int));
4948
var returnExpression = Expression.Return(returnTarget, Expression.Convert(hashVariable, typeof(int)), typeof(int));
5049
var returnLabel = Expression.Label(returnTarget, Expression.Constant(-1));
51-
50+
5251
var expressions = new List<Expression>();
5352
foreach (var member in members)
5453
{
55-
var callGetHashCode = Expression.Call(member, member.Type.GetDeclaredMethod(nameof(GetHashCode)));
56-
var convertHashCodeToInt64 = Expression.Convert(callGetHashCode, typeof(long));
54+
// Call the GetHashCode method
55+
var hasCodeExpression = Expression.Convert(Expression.Call(member, member.Type.GetDeclaredMethod(nameof(GetHashCode))), typeof(long));
56+
57+
// return (((object)x) == null ? 0 : x.GetHashCode())
58+
var hashCodeReturnTarget = Expression.Label(typeof(long));
59+
var hashCode = Expression.Block(
60+
Expression.IfThenElse(
61+
Expression.ReferenceEqual(Expression.Convert(member, typeof(object)), Expression.Constant(null)),
62+
Expression.Return(hashCodeReturnTarget, Expression.Constant(0L, typeof(long))),
63+
Expression.Return(hashCodeReturnTarget, hasCodeExpression)),
64+
Expression.Label(hashCodeReturnTarget, Expression.Constant(0L, typeof(long))));
65+
5766
if (expressions.Count == 0)
5867
{
59-
expressions.Add(Expression.Assign(hashVariable, convertHashCodeToInt64));
68+
expressions.Add(Expression.Assign(hashVariable, hashCode));
6069
}
6170
else
6271
{
6372
var oldHashMultiplied = Expression.Multiply(hashVariable, hashMultiply);
64-
var xOrHash = Expression.ExclusiveOr(oldHashMultiplied, convertHashCodeToInt64);
73+
var xOrHash = Expression.ExclusiveOr(oldHashMultiplied, hashCode);
6574
expressions.Add(Expression.Assign(hashVariable, xOrHash));
6675
}
6776
}

0 commit comments

Comments
 (0)