web-dev-qa-db-ja.com

Web APIクエリ可能-AutoMapperを適用する方法は?

ODataのqueryable属性で装飾されたこのような単純なWebApiメソッドがあります。

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

私がやりたいのは、WebAPIが結果をJSONに変換する前に、AutoMapperを使用してクエリの結果をPerson型からPersonDto型に変換することです。

誰かが私がこれを行う方法を知っていますか? GetAll()呼び出しの後にMapper.Mapを適用してから、IQueryableに変換し直すことはできますが、これにより、ODataフィルターが適用される前にテーブル全体が返され、マップされます(良くありません!)。

この質問 ASP.NET Web APIはクエリ可能なDTOを返しますか? は同じ問題をカバーしているように見えます(より良い答えについては2番目の応答を参照してください)。チェーンの最後で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();
    }
27
user460667

より良い解決策があります。これを試して:

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

これにより、クエリがPersonDTOではなくPersonで実行されるようになります。コードではなく属性を介して変換を実行する場合でも、設定したものと同様のアクションフィルターを実装する必要があります。

27

AutoMapperの Queryable Extensions を使用します。

まず、マッピングを定義します。

// Old AutoMapper API
// Mapper.CreateMap<Person, PersonDto>();

// Current AutoMapper API
Mapper.Initialize(cfg => 
   cfg.CreateMap<Person, PersonDto>()
);

次に、次のようなものを使用できます。

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

    // New AutoMapper API
    return this.dbContext.Persons.ProjectTo<PersonDto>();
}

2019年4月編集:現在のAutoMapperAPIを反映するように更新されました。

25
alik

私見では、受け入れられた解決策は正しくありません。一般的に、サービスが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:コメントにまともな返答を入れることができなかったので、ここに追加しました。これを行う方法についての投稿がありました ここ しかし、私は今日403を取得しています。うまくいけば、それは一時的なものです。

基本的に、ナビゲーションプロパティのSelect句とExpand句を調べます。存在する場合は、IQueryable<T> Include拡張メソッドを介して熱心にロードするようにEFに指示します。

コントローラー

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;
  }
}
17
Ben Ripley