2016-03-29 12 views
5

Mit Web-API und OData habe ich einen Dienst, der Datentransferobjekte statt der Entity Framework-Entitäten verfügbar macht.Verwenden von DTOs mit OData & Web API

I verwenden AutoMapper EF Entities in ihre Gegenstücke DTO zu transformieren unter Verwendung von 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) konfiguriert ist wie folgt, siehe den ExplicitExpansion() die Serialisierung Autonavigations verhindert Eigenschaften erweitern, wenn sie werden nicht angefordert:

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() dann ein neues Problem schafft, wo die folgende Anforderung einen Fehler wirft:

/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

Die Navigationseigenschaft SalesOrderLines ist EF unbekannt, also ist dieser Fehler ziemlich genau das, was ich erwartet habe. Die Frage ist, wie gehe ich mit dieser Art von Anfrage um?

Die ProjectTo() Methode macht eine Überlast, die ich in einer Reihe von Eigenschaften passieren läßt, die Expansion erforderlich, fand ich & die Erweiterungsmethode modifizierte ToNavigationPropertyArray um zu versuchen, die Anfrage in ein String-Array zu analysieren:

[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; 
} 

Diese für erweitern funktioniert, jetzt, damit ich eine Anfrage wie folgt umgehen:

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

oder eine kompliziertere Anfrage wie:

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

jedoch kompliziertere Anforderung, die versucht, $ wählen zu kombinieren mit $ erweitern fehl:

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

Sequence contains no elements

So ist die Frage: bin ich dies den richtigen Weg nähern? Es fühlt sich sehr stinkend an, dass ich etwas schreiben müsste, um die ODataQueryOptions zu parsen und in etwas zu transformieren, das EF verstehen kann.

Es scheint dies ein ziemlich beliebtes Thema ist:

Während die meisten von ihnen empfehlen die Verwendung ProjectTo, scheinen keine Adresse Serialisierung automatisch erweitern ding Eigenschaften, oder wie mit der Erweiterung umzugehen, wenn ExplictExpansion konfiguriert wurde.

Klassen und Config unter:

Entity Framework (V6.1.3) Einheiten:

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) Datenübertragungs Objekte:

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"); 

Antwort

0

ich nie wirklich geschafft, diese zu trainieren. Die Erweiterungsmethode ToNavigationPropertyArray() hilft ein wenig, aber nicht mit unendlicher Tiefe Navigation.

Die wirkliche Lösung besteht darin, Aktionen oder Funktionen zu erstellen, damit Clients Daten anfordern können, die eine kompliziertere Abfrage erfordern.

Die andere Alternative besteht darin, mehrere kleinere/einfache Aufrufe zu machen und dann die Daten auf dem Client zu aggregieren, aber das ist nicht wirklich ideal.

0

Wenn Sie etwas für die explizite Erweiterung in AutoMapper markieren möchten, müssen Sie sich auch beim Aufruf von ProjectTo<>() zurückmelden.

// 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 
     ); 
} 

Während die AutoMapper wiki tut Zustand dies ist das Beispiel vielleicht ein wenig irreführend, indem nicht das gekoppelte ExplicitExpansion() Anruf einschließlich.

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

Hallo Dave zurückkehren, ich bin bereits explizit die Navigationseigenschaften erweitert. Der obige Code zeigt den Code, den ich benutze, um dies zu konfigurieren und die Odata zu parsen, um zu erhalten, welche Navigationseigenschaften eine Erweiterung erfordern. Dies ist der Zweck der 'ToNavigationPropertyArray()' Extension-Methode, deren Ergebnis dann an das 'ProjectTo()' übergeben wird. Meine Frage war/ist darüber, ob die Verwendung der Erweiterungsmethode zum Analysieren der ODATA-Abfragezeichenfolge der richtige Ansatz war, da es unordentlich wird, wenn mehrere Navigationseigenschaften erweitert werden müssen. Ich habe daran gearbeitet, indem ich Aktionen und Funktionen für kompliziertere Abfragen verwendet habe. – philreed

0

Ich habe eine AutoMapper explizite Navigation Erweiterung Nutzenfunktion erstellt, die mit N-deph erweitert funktionieren sollte. Es hier posten, da es jemandem helfen könnte.

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; 
} 

Sie können es nennen mit:

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

es wird eine Liste mit ["Parent" ,"Parent.Child"]

Verwandte Themen