5

Использование Web API и OData У меня есть служба, которая предоставляет объекты передачи данных вместо объектов Entity Framework.Использование DTO с API OData & Web

Я использую AutoMapper преобразовать EF Entities в свои встречные части DTO с помощью 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) настраивается следующим образом, обратите внимание на ExplicitExpansion(), который предотвращает сериализации автоматического расширения навигационных свойств, когда они не запрашиваются:

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() затем создает новую проблему, когда следующий запрос выдает ошибку:

/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

Навигационное свойство SalesOrderLines неизвестно EF, поэтому эта ошибка в значительной степени соответствует тому, что я ожидал. Вопрос в том, как мне обрабатывать этот тип запроса?

Метод ProjectTo() имеет перегрузку, которая позволяет мне передать массив свойств, требующих расширения, я нашел & модифицированный метод расширения ToNavigationPropertyArray, чтобы попытаться разобрать запрос в массив строк:

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

Это работает для расширения, так что теперь я могу обрабатывать запрос, как следующее:

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

или более сложный запрос, как:

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

Однако, более сложный запрос, которые пытаются объединить $ выбрать с $ развернуть не получится:

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

Sequence contains no elements

Итак, вопрос: я приближаюсь это правильный путь? Мне кажется, что мне нужно было что-то написать, чтобы разобрать и преобразовать ODataQueryOptions в нечто, что может понять EF.

Кажется, это довольно популярная тема:

Хотя большинство из них предлагают использовать ProjectTo, никто, кажется, для решения автоматическое расширение сериализации ding, или как обрабатывать расширение, если настроено ExplictExpansion.

Классы и конфигурации ниже:

Entity Framework (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) Передача данных объектов:

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

ответ

0

Я никогда не удавалось работать этот. Метод расширения ToNavigationPropertyArray() помогает немного, но не обрабатывает бесконечную навигацию по глубине.

Настоящим решением является создание действий или функций, позволяющих клиентам запрашивать данные, требующие более сложного запроса.

Другой альтернативой является создание нескольких небольших/простых вызовов, а затем объединение данных на клиенте, но это не идеальный вариант.

0

Если вы хотите пометить что-то для явного расширения в AutoMapper, вам необходимо также выбрать отказ при вызове 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 
     ); 
} 

Хотя AutoMapper wiki делает состояние это, например, возможно, немного вводит в заблуждение, не включая парный ExplicitExpansion() вызов.

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

Привет Дэйв, я уже явно расширяет навигационные свойства. В приведенном выше коде показан код, который я использую для настройки этого & parse odata, чтобы получить, какие свойства навигации требуют расширения. Это цель метода расширения ToNavigationPropertyArray(), результат которого затем передается в 'ProjectTo()'. Мой вопрос состоял в том, был ли использование метода расширения для синтаксического анализа строки запроса odata правильным правилом, потому что он становится беспорядочным, если требуется несколько свойств навигации. Я работал над этим, используя действия и функции для более сложных запросов. – philreed

0

Я создал функцию утилиты расширения расширения Automapper, которая должна работать с расширением N-deph. Проводя его здесь, поскольку он может помочь кому-то.

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

Вы можете назвать его:

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

он будет возвращать список с ["Parent" ,"Parent.Child"]

Смежные вопросы