-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Description
ProjectTo fails with to an unhandled exception when trying to project to a destination containing an aggregate function (e.g. Count) where the matching source is an IEnumerable<T>. This works as expected when the aggregate function is Count and the source is an ICollection<T> (e.g. List<T>) because the name matches a property on the destination object. Other aggregate functions cause the same error with ICollection<T> (but not Sum which is dicussed below).
The documentation suggests this should work.
This error is due to the correct extension method being located but then treated as a field/property accessor in MemberGetterExpressionResultConverter.cs:
private static ExpressionResolutionResult ExpressionResolutionResult(
ExpressionResolutionResult expressionResolutionResult, MemberInfo getter)
{
var member = Expression.MakeMemberAccess(expressionResolutionResult.ResolutionExpression, getter);
return new ExpressionResolutionResult(member, member.Type);
}I replaced the method above with the code below while investigating and it passes existing tests. This also allows other aggregate functions (e.g. Sum, Max, Min, etc.) to work with IEnumerable<T> but not all aggregate functions work with ICollection<T> (e.g. Sum) and I haven't found the cause. This second behaviour can be seen by changing the Points property to an ICollection<T> and then back to an IEnumerable<T>.
private static ExpressionResolutionResult ExpressionResolutionResult(
ExpressionResolutionResult expressionResolutionResult, MemberInfo getter)
{
var member = (getter is MethodInfo method && method.IsStatic)
? Expression.Call(method, expressionResolutionResult.ResolutionExpression) as Expression
: Expression.MakeMemberAccess(expressionResolutionResult.ResolutionExpression, getter);
return new ExpressionResolutionResult(member, member.Type);
}I have created a gist with a full example.
I discovered this while trying to use AutoMapper with Linq2DB/EFCore and immutable models where the collections and associations are exposed as IEnumerable<T>. Although EF6 requires the many side of a one-to-many association to be an ICollection<T>, this problem may still apply due to the lack of fields/properties that match the name of aggregate functions (e.g. not Count).
Source/destination types
class TeamHistory
{
public int TeamId { get; set; }
public IEnumerable<int> Points { get; set; }
}
class ViewModel
{
public int TeamId { get; private set; }
public int PointsCount { get; private set; }
public int PointsSum { get; private set; }
public int PointsMin { get; private set; }
public int PointsMax { get; private set; }
}Mapping configuration
var config = new MapperConfiguration(_ => { });Version: 6.2.2
Expected behavior
Output:
Team: 4
- 3 total game(s)
- 10 total point(s)
- 1 min point(s)
- 5 max point(s)
Actual behavior
Receive an unhandled exception:
System.ArgumentException: 'Member 'Int32 Count[Int32](System.Collections.Generic.IEnumerable`1[System.Int32])' not field or property'
Steps to reproduce
var teams = new List<TeamHistory>()
{
new TeamHistory()
{
TeamId = 4,
Points = new List<int>() { 1, 4, 5 }
}
}.AsQueryable();
var config = new MapperConfiguration(_ => { });
var model = teams.ProjectTo<ViewModel>(config).Single();
Console.WriteLine($@"Team: {model.TeamId}
- {model.PointsCount} total game(s)
- {model.PointsSum} total point(s)
- {model.PointsMin} min point(s)
- {model.PointsMax} max point(s)");
Console.Write("Press enter to exit...");
Console.ReadLine();