2012-03-27 10 views
9

Ho creato una piccola API con l'API Web ASP.NET, ma suppongo che non sia giusto restituire le entità dal mio contesto (framework di entità) AsQueryable, quindi sto mappando tutto sugli oggetti DTO.L'API Web ASP.NET restituisce DTO interrogabili?

Ciò che non capisco comunque: come posso mantenere il mio contesto interrogabile, ma restituisco solo DTO invece di entità? O questo non è possibile?

Questo è il mio codice:

public IQueryable<ItemDto> Get() 
{ 
    using (EfContext context = new EfContext()) 
    { 
     Mapper.CreateMap<Item, ItemDto>() 
      .ForMember(itemDto => itemDto.Category, mce => mce.MapFrom(item => item.Category.Name)); 

     IEnumerable<ItemDto> data = Mapper.Map<IEnumerable<Item>, IEnumerable<ItemDto>>(context.Items 
      .OrderByDescending(x => x.PubDate) 
      .Take(20)); 

     return data.AsQueryable(); 
    } 
} 

Come potete vedere ho caricare i dati, e fare quel piccolo raccolta queryable IEnumerable. Il problema è che la query generata per questo pezzo di codice è probabilmente abbastanza inefficiente perché carica tutti gli elementi prima (o almeno i primi 20 elementi) e quindi filtra l'output.

Spero di aver descritto il mio problema nel miglior modo possibile, è un po 'difficile da spiegare. Non ho trovato nulla su di esso su Google.

+0

Non esporre gli endpoint dell'API Web come IQueryable a tutti ... Se proprio ciò è necessario, utilizzare OData Web API. Altrimenti, limitati a semplici vecchi endpoint REST e esponi qualsiasi tipo di filtro possibile come parametri sulle azioni del controller. – mare

risposta

7

Non selezionare prima tutto in memoria. Fare qualcosa di simile:

public IQueryable<ItemDto> Get() 
{ 
    using (EfContext context = new EfContext()) 
    { 
     var query = from item in context.Items 
        select Mapper.Map<Item, ItemDto>(item) 

     return query.OrderByDescending(x => x.PubDate).Take(20)); 
    } 
} 

BTW Il seguente codice è qualcosa che si vuole fare una volta, ad esempio in un costruttore statico o nel file WebApiConfig.cs.

Mapper.CreateMap<Item, ItemDto>() 
    .ForMember(itemDto => itemDto.Category, mce => mce.MapFrom(item => item.Category.Name)); 
+0

Funziona in EF4? Come meglio ricordo, EF non ti ha permesso di mappare a tipi che non erano definiti in EF. –

+0

Quindi devo solo definire il mio mapping una volta all'avvio dell'applicazione? Non lo sapevo. Grazie per avermelo fatto notare :) –

+0

@Ryan. Nella query EF vengono richiamate le entità EF, solo quelle caricate vengono trasformate in DTO utilizzando AutoMapper. In questo modo si ottiene un caricamento lento, quindi l'ordine/filtro viene eseguito nel database e solo i 20 record vengono mappati da entità EF a DTO. – Maurice

3

Se l'interrogazione avviene solo nel codice che vediamo (cioè l'ordine e Take) il codice va bene. Mapperà solo il risultato (max 20). Tuttavia, dal momento che stai restituendo IQueryable, presumo che desideri eseguire ulteriori query sul risultato. Possono essere i parametri di stile OData?

Con un massimo di 20 articoli, è meglio non scrivere alcun codice. Le restanti query verranno eseguite come query di oggetti. Tuttavia, se si decide di rimuovere quel vincolo (massimo 20) o di metterlo dopo che sono state effettuate ulteriori query, in questo modo sarà inefficiente.

Fondamentalmente, è necessario spostare la mappatura alla fine della catena di query se si desidera che tutte le query vengano eseguite nel database EF.

Che cosa si può fare è in realtà tornare l'entità effettiva oggetti

public IQueryable<ItemDto> Get() 
    { 
     using (EfContext context = new EfContext()) 
     { 
      return context.items 
         .OrderByDescending(x => x.PubDate) 
         .Take(20)); 
     } 
    } 

E dire MVC come serializzare questo in un MediaTypeFormatter. Qui puoi usare AutoMapper. .