2016-03-29 12 views
5

Utilizzando Web API e OData, dispongo di un servizio che espone oggetti Data Transfer invece delle entità Entity Framework.Utilizzo di DTO con OData e API Web

che uso Automapper trasformare le Entità EF nelle loro controparti DTO usando ProjectTo():

public class SalesOrdersController : ODataController 
{ 
    private DbContext _DbContext; 

    public SalesOrdersController(DbContext context) 
    { 
     _DbContext = context; 
    } 

    [EnableQuery] 
    public IQueryable<SalesOrderDto> Get(ODataQueryOptions<SalesOrderDto> queryOptions) 
    { 
     return _DbContext.SalesOrders.ProjectTo<SalesOrderDto>(AutoMapperConfig.Config); 
    } 

    [EnableQuery] 
    public IQueryable<SalesOrderDto> Get([FromODataUri] string key, ODataQueryOptions<SalesOrderDto> queryOptions) 
    { 
     return _DbContext.SalesOrders.Where(so => so.SalesOrderNumber == key) 
          .ProjectTo<SalesOrderDto>(AutoMapperConfig.Config); 
    } 
} 

Automapper (V4.2.1) è configurato come segue, notare il ExplicitExpansion() che impedisce serializzazione automatica espansione proprietà di navigazione quando non sono richieste:

cfg.CreateMap<SalesOrderHeader, SalesOrderDto>()     
      .ForMember(dest => dest.SalesOrderLines, opt => opt.ExplicitExpansion()); 

cfg.CreateMap<SalesOrderLine, SalesOrderLineDto>() 
      .ForMember(dest => dest.MasterStockRecord, opt => opt.ExplicitExpansion()) 
      .ForMember(dest => dest.SalesOrderHeader, opt => opt.ExplicitExpansion()); 

ExplicitExpansion() quindi crea un nuovo problema in cui la seguente richiesta genera un errore:

/odatademo/SalesOrders('123456')?$expand=SalesOrderLines

The query specified in the URI is not valid. The specified type member 'SalesOrderLines' is not supported in LINQ to Entities

La proprietà di spostamento SalesOrderLines è sconosciuta a EF, quindi questo errore è più o meno quello che mi aspettavo. La domanda è: come gestisco questo tipo di richiesta?

Procedimento ProjectTo() ha un sovraccarico che mi permette di passare in una serie di proprietà che richiedono l'espansione, ho trovato & modifica il metodo di estensione ToNavigationPropertyArray per cercare di analizzare la richiesta in una matrice di stringhe:

[EnableQuery] 
public IQueryable<SalesOrderDto> Get([FromODataUri] string key, ODataQueryOptions<SalesOrderDto> queryOptions) 
{ 
    return _DbContext.SalesOrders.Where(so => so.SalesOrderNumber == key) 
      .ProjectTo<SalesOrderDto>(AutoMapperConfig.Config, null, queryOptions.ToNavigationPropertyArray()); 
} 

public static string[] ToNavigationPropertyArray(this ODataQueryOptions source) 
{ 
    if (source == null) { return new string[]{}; } 

    var expandProperties = string.IsNullOrWhiteSpace(source.SelectExpand?.RawExpand) ? new List<string>().ToArray() : source.SelectExpand.RawExpand.Split(','); 

    for (var expandIndex = 0; expandIndex < expandProperties.Length; expandIndex++) 
    { 
     // Need to transform the odata syntax for expanding properties to something EF will understand: 

     // OData may pass something in this form: "SalesOrderLines($expand=MasterStockRecord)";     
     // But EF wants it like this: "SalesOrderLines.MasterStockRecord"; 

     expandProperties[expandIndex] = expandProperties[expandIndex].Replace(" ", ""); 
     expandProperties[expandIndex] = expandProperties[expandIndex].Replace("($expand=", "."); 
     expandProperties[expandIndex] = expandProperties[expandIndex].Replace(")", ""); 
    } 

    var selectProperties = source.SelectExpand == null || string.IsNullOrWhiteSpace(source.SelectExpand.RawSelect) ? new List<string>().ToArray() : source.SelectExpand.RawSelect.Split(','); 

    //Now do the same for Select (incomplete)   
    var propertiesToExpand = expandProperties.Union(selectProperties).ToArray(); 

    return propertiesToExpand; 
} 

Questo funziona per espandersi, così ora posso gestire una richiesta simile alla seguente:

/odatademo/SalesOrders('123456')?$expand=SalesOrderLines

o una richiesta più complessa come:

/odatademo/SalesOrders('123456')?$expand=SalesOrderLines($expand=MasterStockRecord)

Tuttavia, la richiesta più complicato che cercano di coniugare $ selezionare con $ espandere fallirà:

/odatademo/SalesOrders('123456')?$expand=SalesOrderLines($select=OrderQuantity)

Sequence contains no elements

Quindi, la domanda è: sto avvicinando questo il modo giusto? Sembra molto maleodorante dover scrivere qualcosa da analizzare e trasformare ODataQueryOptions in qualcosa che EF possa capire.

Sembra che questo è un argomento piuttosto popolare:

Mentre la maggior parte di questi suggeriamo di utilizzare ProjectTo, nessuno sembra affrontare serializzazione auto expan ding properties, o come gestire l'espansione se è stato configurato ExplictExpansion.

Classi e configurazione di seguito:

Entity Framework entità (V6.1.3):

public class SalesOrderHeader 
{ 
    public string SalesOrderNumber { get; set; } 
    public string Alpha { get; set; } 
    public string Customer { get; set; } 
    public string Status { get; set; } 
    public virtual ICollection<SalesOrderLine> SalesOrderLines { get; set; } 
} 

public class SalesOrderLine 
{ 
    public string SalesOrderNumber { get; set; } 
    public string OrderLineNumber { get; set; }   
    public string Product { get; set; } 
    public string Description { get; set; } 
    public decimal OrderQuantity { get; set; } 

    public virtual SalesOrderHeader SalesOrderHeader { get; set; } 
    public virtual MasterStockRecord MasterStockRecord { get; set; } 
} 

public class MasterStockRecord 
{   
    public string ProductCode { get; set; }  
    public string Description { get; set; } 
    public decimal Quantity { get; set; } 
} 

OData (V6.13.0) di trasferimento dati oggetti:

public class SalesOrderDto 
{ 
    [Key] 
    public string SalesOrderNumber { get; set; } 
    public string Customer { get; set; } 
    public string Status { get; set; } 
    public virtual ICollection<SalesOrderLineDto> SalesOrderLines { get; set; } 
} 

public class SalesOrderLineDto 
{ 
    [Key] 
    [ForeignKey("SalesOrderHeader")] 
    public string SalesOrderNumber { get; set; } 

    [Key] 
    public string OrderLineNumber { get; set; } 
    public string LineType { get; set; } 
    public string Product { get; set; } 
    public string Description { get; set; } 
    public decimal OrderQuantity { get; set; } 

    public virtual SalesOrderDto SalesOrderHeader { get; set; } 
    public virtual StockDto MasterStockRecord { get; set; } 
} 

public class StockDto 
{ 
    [Key] 
    public string StockCode { get; set; }   
    public string Description { get; set; }   
    public decimal Quantity { get; set; } 
} 

OData Config:

var builder = new ODataConventionModelBuilder(); 

builder.EntitySet<StockDto>("Stock"); 
builder.EntitySet<SalesOrderDto>("SalesOrders"); 
builder.EntitySet<SalesOrderLineDto>("SalesOrderLines"); 

risposta

0

ho mai veramente riuscito a lavorare questo fuori. Il metodo di estensione ToNavigationPropertyArray() aiuta un po ', ma non gestisce la navigazione di profondità infinita.

La vera soluzione è creare azioni o funzioni per consentire ai client di richiedere dati che richiedono una query più complessa.

L'altra alternativa consiste nel fare più chiamate più piccole/semplici e quindi aggregare i dati sul client, ma questo non è proprio l'ideale.

0

Quando si desidera contrassegnare qualcosa per l'espansione esplicita in AutoMapper, è necessario anche opt-back-in quando si chiama ProjectTo<>().

// map 
cfg.CreateMap<SalesOrderHeader, SalesOrderDto>()     
    .ForMember(dest => dest.SalesOrderLines, opt => opt.ExplicitExpansion()); 

// updated controller 
[EnableQuery] 
public IQueryable<SalesOrderDto> Get() 
{ 
    return _dbContext.SalesOrders 
     .ProjectTo<SalesOrderDto>(
      AutoMapperConfig.Config, 
      so => so.SalesOrderLines, 
      // ... additional opt-ins 
     ); 
} 

Mentre il AutoMapper wiki fa stato di questo, l'esempio è forse un po 'fuorviante, non includendo l'accoppiato ExplicitExpansion() chiamata.

To control which members are expanded during projection, set ExplicitExpansion in the configuration and then pass in the members you want to explicitly expand:

+0

Ciao Dave, sto già espandendo in modo esplicito le proprietà di navigazione. Il codice sopra mostra il codice che uso per configurarlo e analizzare l'odata per ottenere quali proprietà di navigazione richiedono l'espansione. Questo è lo scopo del metodo di estensione 'ToNavigationPropertyArray()', il cui risultato viene poi passato a 'ProjectTo()'. La mia domanda era/riguarda se utilizzare il metodo di estensione per analizzare la stringa di query odata fosse l'approccio giusto perché diventa complicato se è necessario espandere più proprietà di navigazione. Ci ho lavorato attorno usando azioni e funzioni per query più complicate. – philreed

0

Ho creato una funzione di utilità di espansione di esplorazione esplicita di Automapper che dovrebbe funzionare con l'espansione di N-deph. Pubblicarlo qui poiché potrebbe aiutare qualcuno.

public List<string> ProcessExpands(IEnumerable<SelectItem> items, string parentNavPath="") 
{ 
    var expandedPropsList = new List<String>(); 
    if (items == null) return expandedPropsList; 

    foreach (var selectItem in items) 
    { 
     if (selectItem is ExpandedNavigationSelectItem) 
     { 
      var expandItem = selectItem as ExpandedNavigationSelectItem; 
      var navProperty = expandItem.PathToNavigationProperty?.FirstSegment?.Identifier; 

      expandedPropsList.Add($"{parentNavPath}{navProperty}");      
      //go recursively to subproperties 
      var subExpandList = ProcessExpands(expandItem?.SelectAndExpand?.SelectedItems, $"{navProperty}."); 
      expandedPropsList = expandedPropsList.Concat(subExpandList).ToList(); 
     } 
    } 
    return expandedPropsList; 
} 

Si può chiamare con:

var navExp = ProcessExpands(options?.SelectExpand?.SelectExpandClause?.SelectedItems) 

si restituirà un elenco con ["Parent" ,"Parent.Child"]

Problemi correlati