DTO и прогнози в WebAPI

WebAPI поддържа OData, така че потребителят на API може да посочи полета, които изисква, и работи добре. Но има ли начин да се използват DTO обекти и проекции в WebAPI?

Аз например имам

public class WebSite
{
    public string Url {get;set;}
    public string Author {get;set;}
    public string Technology {get;set;}
    public DateTime CreatedAt {get;set;}
    // 20 more different properties
}

И аз също имам DTO обект:

public class WebSiteDTO
{
    public string Url {get;set;}
    public string Author {get;set;}

    public bool IsDotNet {get;set;} // it should be set during mapping as webSite.Technology == ".Net";
    public bool IsTrendThing {get;set;} // should be set as  webSite.Technology == ".Net" and webSite.CreatedAt > new DateTime(2014,0,0);
}

И някои типични крайни точки на WebAPI, които поддържат OData:

[HttpGet]
[Route("Test")]
public IQueryable Test(ODataQueryOptions<WebSiteDTO> options)
{
    var efDbContext = new MyDBContext();
    var query = efDbContext.WebSites;
    var odataQuery = options.ApplyTo(query, settings);
    return odataQuery;
}

В този случай обектът WebSite ще бъде върнат. Но как да върна WebSiteDTO обект и все пак да има поддръжка на OData? Възможно ли е да се направи картографиране по свойство, а не по самия клас? Например, ако Url е поискан чрез OData, тогава ще заредим само Url от DB и ще го съпоставим със свойство Url в DTO обект? Може да имам сложни случаи, когато свойствата в DTO трябва да бъдат зададени от някаква персонализирана логика, като в примера на IsDotNet, или може да зависи от повече от едно свойство.

Предполагам, че мога да напиша някакъв персонализиран междинен софтуер, който ще изпълни оригинална заявка с полета, посочени от OData, и след това да го запиша в речника и след това да направя нещо подобно:

MyMapper.Map<WebSite, WebSiteDTO>().
    MapProperty(o, dict => o.Url = (string)dict["Url"]).
    MapProperty(o, dict => o.IsDotNet = (string)dict["Technology"] == ".Net").
    MapProperty(o, dict => o.IsTrendThing = (string)dict["Technology"] == ".Net" && (DateTime)dict["CreatedAt"] > new DateTime(2014,0,0));

Но изглежда грозно и в този случай по някакъв начин трябва да уточня, че ако свойството IsTrendThing е поискано в заявка за OData, тогава трябва също да заредя полета Technology и CreatedAt от обект WebSite, което прави нещата сложни.

Има ли нещо, което може да бъде полезно в моя случай? Може би някой може да ми помогне с правилната посока?

Automapper има картографиране на проекции, но не работи в моя сценарий, тъй като изисква специфични тип, а в моя случай оригиналният тип може да е различен поради полета, посочени от OData.

Има друг подобен въпрос, но той беше за чисто DTO и в в моя случай искам да поддържам оператори 'select\expand' от OData, тъй като моите обекти могат да имат много свойства. И искам да не ги зареждам от DB, ако не са били поискани.


person Sergey Litvinov    schedule 26.11.2014    source източник
comment
Звучи като може би работа за Automapper? automapper.org   -  person sh1rts    schedule 27.11.2014
comment
Възможни дубликати на това и това   -  person qujck    schedule 27.11.2014
comment
Благодаря, момчета, но ако погледнахте дъното на въпроса ми, посочих за automapper и за първия от дублиращите се въпроси и това е, че те са различни. Въпросът ми е за DTO, базирани на прогнози. Когато потребителят на API може да посочи OData Select израз и данните трябва да бъдат преобразувани EF -› DTO -› анонимен тип въз основа на Select OData израз. и не искам да зареждам целия EF обект и да правя преобразуване от страна на .Net, ако мога да съпоставя полето на DTO към EF обект и към SQL   -  person Sergey Litvinov    schedule 27.11.2014


Отговори (1)


Трябва да направите проекцията, преди да приложите ODataQueryOptions. ODataQueryOptions могат да бъдат приложени към свойства, които съществуват в WebSiteDto.

var date = new DateTime(2014,0,0);
// this will not load your data
var query = efDbContext.WebSites.Select(w => new WebSiteDto() 
{
    /* projection code */
    IsDotNet = w.Technology == ".Net",
    IsTrendThing = w.Technology == ".Net" && w.CreatedAt > date,
    Url = w.Url,
    Author = w.Author
});

// this will still not load your data but will be applied on your projected object
var odataQuery = options.ApplyTo(query, settings);

Пълната заявка (с приложените опции) ще бъде изпълнена само когато действието на контролера се върне. Можете да направите всеки проекционен код, който работи с LinqToEntities вътре в метода за избор (Избягвайте извикване на методи като Equals и използвайте == вместо това или извикване на който и да е конструктор). Дори не зареждате Technology или CreatedAt, заявката се изпълнява на SQL сървър и получавате обратно само булеви стойности.

Ако клиентът ще извърши допълнителен $select, той ще бъде поставен отгоре на тази заявка, така че ненужните данни никога няма да достигнат вашия сървър на приложения от SQL Server (точно като прилагането на друг .Select() на вашия IQueryable)

Не съм сигурен как AutoMapper се справя с IQueryable (ако принуждава оценка на заявката или не), но методът Select няма да оцени вашата заявка, а просто ще надгражда върху нея, така че да може да бъде изпратена до SQL Server.

person Cosmin Vană    schedule 06.12.2014
comment
Хм, интересна идея. Мислех, че мога да направя картографиране на свойство, но в този случай мога да генерирам Select Expression по време на изпълнение и да го използвам за създаване на DTO, а OData ще го картографира по свой начин, така че няма да използва всички полета, а ще използва само полета, посочени в $select заявка. Благодаря! - person Sergey Litvinov; 07.12.2014