Relações de entidade de suporte no OData v3 com a API Web 2
por Mike Wasson
A maioria dos conjuntos de dados define relações entre entidades: os clientes têm pedidos; livros têm autores; os produtos têm fornecedores. Usando o OData, os clientes podem navegar por relações de entidade. Dado um produto, você pode encontrar o fornecedor. Você também pode criar ou remover relações. Por exemplo, você pode definir o fornecedor para um produto.
Este tutorial mostra como dar suporte a essas operações no ASP.NET Web API. O tutorial se baseia no tutorial Criando um ponto de extremidade OData v3 com a API Web 2.
Versões de software usadas no tutorial
- API Web 2
- OData Versão 3
- Entity Framework 6
Adicionar uma entidade de fornecedor
Primeiro, precisamos adicionar um novo tipo de entidade ao nosso feed OData. Adicionaremos uma Supplier
classe.
using System.ComponentModel.DataAnnotations;
namespace ProductService.Models
{
public class Supplier
{
[Key]
public string Key { get; set; }
public string Name { get; set; }
}
}
Essa classe usa uma cadeia de caracteres para a chave de entidade. Na prática, isso pode ser menos comum do que usar uma chave de inteiro. Mas vale a pena ver como o OData lida com outros tipos de chave além de inteiros.
Em seguida, criaremos uma relação adicionando uma Supplier
propriedade à Product
classe :
public class Product
{
public int ID { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
// New code
[ForeignKey("Supplier")]
public string SupplierId { get; set; }
public virtual Supplier Supplier { get; set; }
}
Adicione um novo DbSet à classe , para que o ProductServiceContext
Entity Framework inclua a Supplier
tabela no banco de dados.
public class ProductServiceContext : DbContext
{
public ProductServiceContext() : base("name=ProductServiceContext")
{
}
public System.Data.Entity.DbSet<ProductService.Models.Product> Products { get; set; }
// New code:
public System.Data.Entity.DbSet<ProductService.Models.Supplier> Suppliers { get; set; }
}
Em WebApiConfig.cs, adicione uma entidade "Fornecedores" ao modelo EDM:
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");
// New code:
builder.EntitySet<Supplier>("Suppliers");
Propriedades de navegação
Para obter o fornecedor de um produto, o cliente envia uma solicitação GET:
GET /Products(1)/Supplier
Aqui "Fornecedor" é uma propriedade de navegação no Product
tipo . Nesse caso, Supplier
refere-se a um único item, mas uma propriedade de navegação também pode retornar uma coleção (relação um para muitos ou muitos para muitos).
Para dar suporte a essa solicitação, adicione o seguinte método à ProductsController
classe :
// GET /Products(1)/Supplier
public Supplier GetSupplier([FromODataUri] int key)
{
Product product = _context.Products.FirstOrDefault(p => p.ID == key);
if (product == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return product.Supplier;
}
O parâmetro de chave é a chave do produto. O método retorna a entidade relacionada , nesse caso, uma Supplier
instância. O nome do método e o nome do parâmetro são importantes. Em geral, se a propriedade de navegação for chamada "X", você precisará adicionar um método chamado "GetX". O método deve usar um parâmetro chamado "key" que corresponda ao tipo de dados da chave pai.
Também é importante incluir o atributo [FromOdataUri] no parâmetro de chave . Esse atributo informa à API Web para usar regras de sintaxe OData quando analisa a chave do URI de solicitação.
Criando e excluindo links
O OData dá suporte à criação ou remoção de relações entre duas entidades. Na terminologia OData, a relação é um "link". Cada link tem um URI com a entidade/entidade de formulário/$links/entidade. Por exemplo, o link de produto para fornecedor tem esta aparência:
/Products(1)/$links/Supplier
Para criar um novo link, o cliente envia uma solicitação POST para o URI do link. O corpo da solicitação é o URI da entidade de destino. Por exemplo, suponha que haja um fornecedor com a chave "CTSO". Para criar um link de "Product(1)" para "Supplier('CTSO')", o cliente envia uma solicitação como a seguinte:
POST http://localhost/odata/Products(1)/$links/Supplier
Content-Type: application/json
Content-Length: 50
{"url":"http://localhost/odata/Suppliers('CTSO')"}
Para excluir um link, o cliente envia uma solicitação DELETE para o URI do link.
Criar Links
Para permitir que um cliente crie links de fornecedor de produtos, adicione o seguinte código à ProductsController
classe :
[AcceptVerbs("POST", "PUT")]
public async Task<IHttpActionResult> CreateLink([FromODataUri] int key, string navigationProperty, [FromBody] Uri link)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Product product = await db.Products.FindAsync(key);
if (product == null)
{
return NotFound();
}
switch (navigationProperty)
{
case "Supplier":
string supplierKey = GetKeyFromLinkUri<string>(link);
Supplier supplier = await db.Suppliers.FindAsync(supplierKey);
if (supplier == null)
{
return NotFound();
}
product.Supplier = supplier;
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
default:
return NotFound();
}
}
Esse método usa três parâmetros:
- key: a chave para a entidade pai (o produto)
- navigationProperty: o nome da propriedade de navegação. Neste exemplo, a única propriedade de navegação válida é "Supplier".
- link: o URI OData da entidade relacionada. Esse valor é obtido do corpo da solicitação. Por exemplo, o URI do link pode ser "
http://localhost/odata/Suppliers('CTSO')
, o que significa que o fornecedor com ID = 'CTSO'.
O método usa o link para procurar o fornecedor. Se o fornecedor correspondente for encontrado, o método definirá a Product.Supplier
propriedade e salvará o resultado no banco de dados.
A parte mais difícil é analisar o URI do link. Basicamente, você precisa simular o resultado do envio de uma solicitação GET para esse URI. O método auxiliar a seguir mostra como fazer isso. O método invoca o processo de roteamento da API Web e obtém de volta uma instância do ODataPath que representa o caminho OData analisado. Para um URI de link, um dos segmentos deve ser a chave de entidade. (Caso contrário, o cliente enviou um URI inválido.)
// Helper method to extract the key from an OData link URI.
private TKey GetKeyFromLinkUri<TKey>(Uri link)
{
TKey key = default(TKey);
// Get the route that was used for this request.
IHttpRoute route = Request.GetRouteData().Route;
// Create an equivalent self-hosted route.
IHttpRoute newRoute = new HttpRoute(route.RouteTemplate,
new HttpRouteValueDictionary(route.Defaults),
new HttpRouteValueDictionary(route.Constraints),
new HttpRouteValueDictionary(route.DataTokens), route.Handler);
// Create a fake GET request for the link URI.
var tmpRequest = new HttpRequestMessage(HttpMethod.Get, link);
// Send this request through the routing process.
var routeData = newRoute.GetRouteData(
Request.GetConfiguration().VirtualPathRoot, tmpRequest);
// If the GET request matches the route, use the path segments to find the key.
if (routeData != null)
{
ODataPath path = tmpRequest.GetODataPath();
var segment = path.Segments.OfType<KeyValuePathSegment>().FirstOrDefault();
if (segment != null)
{
// Convert the segment into the key type.
key = (TKey)ODataUriUtils.ConvertFromUriLiteral(
segment.Value, ODataVersion.V3);
}
}
return key;
}
Excluindo links
Para excluir um link, adicione o seguinte código à ProductsController
classe :
public async Task<IHttpActionResult> DeleteLink([FromODataUri] int key, string navigationProperty)
{
Product product = await db.Products.FindAsync(key);
if (product == null)
{
return NotFound();
}
switch (navigationProperty)
{
case "Supplier":
product.Supplier = null;
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
default:
return NotFound();
}
}
Neste exemplo, a propriedade de navegação é uma única Supplier
entidade. Se a propriedade de navegação for uma coleção, o URI para excluir um link deverá incluir uma chave para a entidade relacionada. Por exemplo:
DELETE /odata/Customers(1)/$links/Orders(1)
Essa solicitação remove o pedido 1 do cliente 1. Nesse caso, o método DeleteLink terá a seguinte assinatura:
void DeleteLink([FromODataUri] int key, string relatedKey, string navigationProperty);
O parâmetro relatedKey fornece a chave para a entidade relacionada. Portanto, em seu DeleteLink
método, procure a entidade primária pelo parâmetro de chave , localize a entidade relacionada pelo parâmetro relatedKey e remova a associação. Dependendo do modelo de dados, talvez seja necessário implementar ambas as versões do DeleteLink
. A API Web chamará a versão correta com base no URI da solicitação.