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();
}
より良い解決策があります。これを試して:
public virtual IQueryable<PersonDto> Get(ODataQueryOptions<Person> query)
{
var people = query.ApplyTo(uow.Person().GetAll());
return ConvertToDtos(people);
}
これにより、クエリがPersonDTOではなくPersonで実行されるようになります。コードではなく属性を介して変換を実行する場合でも、設定したものと同様のアクションフィルターを実装する必要があります。
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を反映するように更新されました。
私見では、受け入れられた解決策は正しくありません。一般的に、サービスが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;
}
}