Исключение "Последовательность не содержит соответствующий элемент" возникает при попытке расширить свойство навигации с помощью следующего запроса: 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()
?