Web API Queryable - как применить AutoMapper?

У меня есть простой метод WebApi, подобный этому, украшенный запрашиваемым атрибутом OData.

    [Queryable]
    public virtual IQueryable<PersonDto> Get()
    {
        return uow.Person().GetAll()); // Currently returns Person instead of PersonD
    }

Я хочу преобразовать результат запроса из типа Person в тип PersonD в AutoMapper, прежде чем WebAPI преобразует результат в JSON.

Кто-нибудь знает, как я могу это сделать? Я знаю, что я мог бы применить Mapper.Map после вызова GetAll() и затем преобразовать обратно в IQueryable, однако это привело бы к тому, что вся таблица была бы возвращена и отображена до применения фильтра OData (не хорошо!).

Казалось бы, этот вопрос ASP.NET Web API возвращает запрашиваемые DTO? охватывает ту же проблему (см. второй ответ для лучшего ответа), где предлагается использовать AutoMapper в конце цепочки, используя пользовательский MediaTypeFormatter, однако я не знаю, как это сделать, основываясь на примере, который я видел.

Любая помощь будет с благодарностью получена!

- Дополнительная информация

Я посмотрел на исходный код для IQueryable, но, к сожалению, там я не вижу никакого способа использования кода для этой цели. Мне удалось написать дополнительный фильтр, который, кажется, работает, но он не совсем элегантен.

public class PersonToPersonDtoConvertAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(System.Web.Http.Filters.HttpActionExecutedContext actionExecutedContext)
    {
        HttpResponseMessage response = actionExecutedContext.Response;

        if (response != null)
        {
            ObjectContent responseContent = response.Content as ObjectContent;
            var query = (responseContent.Value as IQueryable<Student>).ToList();
            response.Content = new ObjectContent<IEnumerable<StudentResource>>(query.ToList().Select(Mapper.Map<Person, PersonDto>), responseContent.Formatter);
        }
    }
}

Тогда я украсил действие как

    [Queryable]
    [PersonToPersonDtoConvert]
    public IQueryable<Person> Get()
    {
        return uow.GetRepo<IRepository<Person>>().GetAll();
    }

4 ответа

Решение

Есть лучшее решение. Попробуй это:

public virtual IQueryable<PersonDto> Get(ODataQueryOptions<Person> query)
{
    var people = query.ApplyTo(uow.Person().GetAll());
    return ConvertToDtos(people);
}

Это позволит убедиться, что запрос выполняется на Person вместо PersonDTO. Если вы хотите, чтобы преобразование происходило через атрибут, а не в коде, вы все равно захотите реализовать фильтр действий, аналогичный тому, который вы создали.

Используйте Queryable Extensions AutoMapper.

Сначала определим отображение.

Mapper.CreateMap<Person, PersonDto>();

Тогда вы можете использовать что-то вроде этого:

[EnableQuery]
public IQueryable<PersonDto> Get() {
    return this.dbContext.Persons.Project().To<PersonDto>();
}

ИМХО принятое решение не правильно. Вообще говоря, если ваш сервис использует DTO, вы не хотите показывать базовые сущности (человека) для сервиса. Почему бы вы запросить против Person модель и возврат PersonDTO объекты?

Поскольку вы уже используете его, Automapper имеет Queryable Extensions, который позволяет вам выставлять только ваши DTO и применять фильтрацию к базовому типу в источнике данных. Например:

public IQueryable<PersonDto> Get(ODataQueryOptions<PersonDto> options) {
    Mapper.CreateMap<Person, PersonDto>();
    var persons = _personRepository.GetPersonsAsQueryable();
    var personsDTOs = persons.Project().To<PersonDto>();  // magic happens here...

    return options.ApplyTo(personsDTOs);
}

Что касается активной загрузки свойств навигации...

@philreed: Я не смог дать достойный ответ в комментарии, поэтому добавил его сюда. Здесь был пост о том, как это сделать, но сегодня я получаю 403. Надеюсь, это временно.

По сути, вы проверяете предложения Select и Expand для вашего свойства навигации. Если он присутствует, вы указываете EF загружать через IQueryable<T> Include метод расширения.

контроллер

public IQueryable<MyDto> GetMyDtos(ODataQueryOptions<MyDto> options)
{   
  var eagerlyLoad = options.IsNavigationPropertyExpected(t => t.MyNavProperty);
  var queryable = _myDtoService.GetMyDtos(eagerlyLoad);

  // _myDtoService will eagerly load to prevent select N+1 problems
  // return (eagerlyLoad) ? efResults.Include(t => t.MyNavProperty) : efResults;

  return queryable;
}

Метод расширения

public static class ODataQueryOptionsExtensions
{
  public static bool IsNavigationPropertyExpected<TSource, TKey>(this ODataQueryOptions<TSource> source, Expression<Func<TSource, TKey>> keySelector)
  {
    if (source == null) { throw new ArgumentNullException("source"); }
    if (keySelector == null) { throw new ArgumentNullException("keySelector"); }

    var returnValue = false;
    var propertyName = (keySelector.Body as MemberExpression ?? ((UnaryExpression)keySelector.Body).Operand as MemberExpression).Member.Name;
    var expandProperties = source.SelectExpand == null || string.IsNullOrWhiteSpace(source.SelectExpand.RawExpand) ? new List<string>().ToArray() : source.SelectExpand.RawExpand.Split(',');
    var selectProperties = source.SelectExpand == null || string.IsNullOrWhiteSpace(source.SelectExpand.RawSelect) ? new List<string>().ToArray() : source.SelectExpand.RawSelect.Split(',');

    returnValue = returnValue ^ expandProperties.Contains<string>(propertyName);
    returnValue = returnValue ^ selectProperties.Contains<string>(propertyName);

    return returnValue;
  }
}

У меня есть пара замечаний / комментариев по предложенному решению. 1. Если Person используется в контексте запроса и возвращается PersonDTO, тогда вся концепция MVVM исчезает. Я думаю, это становится тесно связанным. Я бы предпочел просто использовать Person полностью или, если вы не хотите возвращать дочерние записи, то, по моему мнению, Automapper имеет расширение "Project", попробуйте использовать его. 2. При таком подходе опция запроса $inlinepagecount не будет работать должным образом, поскольку она получит счетчик PersonDTO вместо Person. (Ну, один из способов обойти эту проблему - использовать PageResult и установить свойства самостоятельно.) С уважением.

Другие вопросы по тегам