Подтвердить что ты не робот

Веб-API Queryable - как применять AutoMapper?

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

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

То, что я хочу сделать, - это преобразовать результат запроса из типа Person для ввода PersonDto с использованием 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();
    }
4b9b3361

Ответ 1

Существует лучшее решение. Попробуйте следующее:

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

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

Ответ 2

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

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

Mapper.CreateMap<Person, PersonDto>();

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

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

Ответ 3

IMHO принятое решение неверно. Вообще говоря, если ваша служба использует DTO, вы не хотите раскрывать базовые объекты (Person) для этой службы. Почему вы запрашиваете модель 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: я не смог добавить достойный ответ в комментарии, поэтому добавил его здесь. Был пост о том, как это сделать здесь, но сегодня я получаю 403s. Надеюсь, это временное.

В основном вы изучаете предложения 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;
  }
}

Ответ 4

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