使用OData和AutoMapper扩展子导航属性时,“序列不包含匹配元素”

时间:2018-12-01 15:27:48

标签: .net-core odata automapper entity-framework-core-2.1

当我尝试使用以下查询扩展导航属性时,发生异常“序列不包含任何匹配元素”: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()发出呼叫吗?

0 个答案:

没有答案