«Последовательность не содержит соответствующего элемента» при расширении дочернего свойства навигации с помощью OData & AutoMapper - PullRequest
0 голосов
/ 01 декабря 2018

Исключение "Последовательность не содержит соответствующий элемент" возникает при попытке расширить свойство навигации с помощью следующего запроса: http://localhost:5000/odata/users?$expand=roles. Запрос без ключевого слова $ expand работает.Объект DTO получен от AutoMapper с очень простой конфигурацией отображения.

Пожалуйста, посмотрите на соответствующие источники:


Entity Framework Entities:

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, пока онопытаясь найти WhereClause в выражении, сгенерированном AutoMapper.Эта строка:

var originalCorrelationPredicate = collectionQueryModel.BodyClauses
    .OfType<WhereClause>()
    .Single(c => c.Predicate is NullSafeEqualExpression);

Если я изменю конфигурацию AutoMapper на следующую ниже, исключение не выдается, но подзапрос Роли выполняется отдельно для каждой пользовательской записи:

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());

Я проверил еще одну идею: если я изменяю метод действия, чтобы вызвать метод ToList () для результата ProjectTo (), тогда только два запроса выполняются в БД, и никаких ошибок не происходит.Вот почему я думаю, что причина ошибки в использовании 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()?

...