当我尝试使用以下查询扩展导航属性时,发生异常“序列不包含任何匹配元素”:http://localhost:5000/odata/users?$ expand = roles。没有$ expand关键字的请求有效。通过非常简单的映射配置从AutoMapper
接收DTO对象。
请查看相关来源:
实体框架实体:
public class User {
public string Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public virtual ICollection<UserRole> UserRoles { get; set; }
}
public class Role {
public string Id { get; set; }
public string Name { get; set; }
}
public class UserRole {
public string UserId { get; set; }
public string RoleId { get; set; }
}
DTO型号:
public class UserModel {
public string Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public IEnumerable<RoleModel> Roles { get; set; }
}
public class RoleModel
{
public string Id { get; set; }
public string Name { get; set; }
}
AutoMapper
配置:
CreateMap<UserRole, RoleModel>()
.ForMember(x => x.Id, x => x.MapFrom(m => m.UserId))
.ForMember(x => x.Name, x => x.MapFrom(m => m.Role.Name));
CreateMap<User, UserModel>()
.ForMember(dest => dest.Roles, opts => opts.MapFrom(src => src.UserRoles))
.ReverseMap()
.ForMember(dest => dest.UserRoles, o => o.Ignore());
控制器操作方法:
[HttpGet]
[EnableQuery]
public IQueryable<UserModel> Get() =>
_userManager.Users.ProjectTo<UserModel>(_mapper.ConfigurationProvider);
实际上,Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.CorrelatedCollectionOptimizingVisitor
抛出异常,试图在AutoMapper
生成的表达式中查找WhereClause。这行:
var originalCorrelationPredicate = collectionQueryModel.BodyClauses
.OfType<WhereClause>()
.Single(c => c.Predicate is NullSafeEqualExpression);
如果将AutoMapper
的配置更改为以下内容,则不会引发异常,但是将对每个用户记录分别执行Roles子查询:
CreateMap<User, UserModel>()
.ForMember(dest => dest.Roles, opts =>
opts.MapFrom(src =>
src.UserRoles.Select(ur => new RoleModel {
Id = ur.UserId,
Name = ur.Role.Name
})))
.ReverseMap()
.ForMember(dest => dest.UserRoles, o => o.Ignore());
我已经检查了另一个想法:如果我要更改操作方法以在ProjectTo()结果上调用ToList()方法,则仅对DB执行两个查询,并且不会发生任何错误。这就是为什么我认为同时使用OData和AutoMapper会出错的原因。
我在做什么错了?
添加:
我检查了@Lucian提出的执行计划,发现AutoMapper生成的表达式与手动编写的另一个表达式之间的唯一区别是子查询调用。
手写变体(应正常工作):
.......
Roles = .Call System.Linq.Enumerable.Select(
$x.UserRoles,
.Lambda
#Lambda2<System.Func`2[Test.Data.UserRole,Test.Models.RoleModel]>),
.......
由AutoMapper生成:
.......
Roles = .Call System.Linq.Enumerable.ToList(.Call
System.Linq.Enumerable.Select(
$x.UserRoles,
.Lambda
#Lambda2<System.Func`2[Test.Data.UserRole,Test.Models.RoleModel]>)),
.......
有人知道如何强制AutoMapper
不向ToList()
发出呼叫吗?