Использование 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.
Кажется, это довольно популярная тема:
- odata-expand-dtos-and-entity-framework
- how-to-specify-the-shape-of-results-with-webapi2-odata-with-expand
- web-api-queryable-how-to-apply-automapper
- how-do-i-map-an-odata-query-against-a-dto-to-another-entity
Хотя большинство из них предлагают использовать 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");
Привет Дэйв, я уже явно расширяет навигационные свойства. В приведенном выше коде показан код, который я использую для настройки этого & parse odata, чтобы получить, какие свойства навигации требуют расширения. Это цель метода расширения ToNavigationPropertyArray(), результат которого затем передается в 'ProjectTo()'. Мой вопрос состоял в том, был ли использование метода расширения для синтаксического анализа строки запроса odata правильным правилом, потому что он становится беспорядочным, если требуется несколько свойств навигации. Я работал над этим, используя действия и функции для более сложных запросов. – philreed