Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
von Mike Wasson
Die meisten Datensätze definieren Beziehungen zwischen Entitäten: Kunden haben Bestellungen; Bücher haben Autoren; Produkte haben Lieferanten. Mithilfe von OData können Clients über Entitätsbeziehungen navigieren. Bei einem Produkt finden Sie den Lieferanten. Sie können auch Beziehungen erstellen oder entfernen. Sie können z. B. den Lieferanten für ein Produkt festlegen.
In diesem Lernprogramm wird gezeigt, wie Sie diese Vorgänge in OData v4 mithilfe ASP.NET Web-API unterstützen. Das Lernprogramm baut auf dem Lernprogramm "Erstellen eines OData v4-Endpunkts mit ASP.NET Web-API 2" auf.
Im Lernprogramm verwendete Softwareversionen
- Web-API 2.1
- OData v4
- Visual Studio 2017 (Laden Sie Visual Studio 2017 hier herunter)
- Entity Framework 6
- .NET 4.5
Tutorial-Versionen
Informationen zur OData-Version 3 finden Sie unter Unterstützen von Entitätsbeziehungen in OData v3.
Hinzufügen einer Lieferantenentität
Hinweis
Das Lernprogramm baut auf dem Lernprogramm "Erstellen eines OData v4-Endpunkts mit ASP.NET Web-API 2" auf.
Zunächst benötigen wir eine zugehörige Entität. Fügen Sie eine Klasse hinzu, die im Ordner "Models" benannt ist Supplier .
using System.Collections.Generic;
namespace ProductService.Models
{
public class Supplier
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Product> Products { get; set; }
}
}
Fügen Sie der Product Klasse eine Navigationseigenschaft hinzu:
using System.ComponentModel.DataAnnotations.Schema;
namespace ProductService.Models
{
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 int? SupplierId { get; set; }
public virtual Supplier Supplier { get; set; }
}
}
Fügen Sie der Klasse ein neues ProductsContext hinzu, sodass Entity Framework die Tabelle "Supplier" in der Datenbank enthält.
public class ProductsContext : DbContext
{
static ProductsContext()
{
Database.SetInitializer(new ProductInitializer());
}
public DbSet<Product> Products { get; set; }
// New code:
public DbSet<Supplier> Suppliers { get; set; }
}
Fügen Sie in WebApiConfig.cs dem Entitätsdatenmodell eine Entität "Lieferanten" hinzu:
public static void Register(HttpConfiguration config)
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");
// New code:
builder.EntitySet<Supplier>("Suppliers");
config.MapODataServiceRoute("ODataRoute", null, builder.GetEdmModel());
}
Hinzufügen eines Lieferantencontrollers
Fügen Sie dem Ordner "Controller" eine SuppliersController Klasse hinzu.
using ProductService.Models;
using System.Linq;
using System.Web.OData;
namespace ProductService.Controllers
{
public class SuppliersController : ODataController
{
ProductsContext db = new ProductsContext();
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
}
Ich zeige nicht, wie CRUD-Vorgänge für diesen Controller hinzugefügt werden. Die Schritte sind identisch mit dem Produktcontroller (siehe Erstellen eines OData v4-Endpunkts).
Abrufen verwandter Entitäten
Um den Lieferanten für ein Produkt zu erhalten, sendet der Kunde eine GET-Anforderung:
GET /Products(1)/Supplier
Um diese Anforderung zu unterstützen, fügen Sie der ProductsController Klasse die folgende Methode hinzu:
public class ProductsController : ODataController
{
// GET /Products(1)/Supplier
[EnableQuery]
public SingleResult<Supplier> GetSupplier([FromODataUri] int key)
{
var result = db.Products.Where(m => m.Id == key).Select(m => m.Supplier);
return SingleResult.Create(result);
}
// Other controller methods not shown.
}
Diese Methode verwendet eine Standardbenennungskonvention.
- Methodenname: GetX, wobei X die Navigationseigenschaft ist.
- Parametername: Schlüssel
Wenn Sie dieser Benennungskonvention folgen, ordnet die Web-API die HTTP-Anforderung automatisch der Controllermethode zu.
Beispiel-HTTP-Anforderung:
GET http://myproductservice.example.com/Products(1)/Supplier HTTP/1.1
User-Agent: Fiddler
Host: myproductservice.example.com
Beispiel-HTTP-Antwort:
HTTP/1.1 200 OK
Content-Length: 125
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
Server: Microsoft-IIS/8.0
OData-Version: 4.0
Date: Tue, 08 Jul 2014 00:44:27 GMT
{
"@odata.context":"http://myproductservice.example.com/$metadata#Suppliers/$entity","Id":2,"Name":"Wingtip Toys"
}
Abrufen einer verwandten Sammlung
Im vorherigen Beispiel hat ein Produkt einen Lieferanten. Eine Navigationseigenschaft kann auch eine Sammlung zurückgeben. Der folgende Code ruft die Produkte für einen Lieferanten ab:
public class SuppliersController : ODataController
{
// GET /Suppliers(1)/Products
[EnableQuery]
public IQueryable<Product> GetProducts([FromODataUri] int key)
{
return db.Suppliers.Where(m => m.Id.Equals(key)).SelectMany(m => m.Products);
}
// Other controller methods not shown.
}
In diesem Fall gibt die Methode eine IQueryable anstelle eines SingleResult<T> zurück.
Beispiel-HTTP-Anforderung:
GET http://myproductservice.example.com/Suppliers(2)/Products HTTP/1.1
User-Agent: Fiddler
Host: myproductservice.example.com
Beispiel-HTTP-Antwort:
HTTP/1.1 200 OK
Content-Length: 372
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
Server: Microsoft-IIS/8.0
OData-Version: 4.0
Date: Tue, 08 Jul 2014 01:06:54 GMT
{
"@odata.context":"http://myproductservice.example.com/$metadata#Products","value":[
{
"Id":1,"Name":"Hat","Price":14.95,"Category":"Clothing","SupplierId":2
},{
"Id":2,"Name":"Socks","Price":6.95,"Category":"Clothing","SupplierId":2
},{
"Id":4,"Name":"Pogo Stick","Price":29.99,"Category":"Toys","SupplierId":2
}
]
}
Erstellen einer Beziehung zwischen Entitäten
OData unterstützt das Erstellen oder Entfernen von Beziehungen zwischen zwei vorhandenen Entitäten. In der OData v4-Terminologie ist die Beziehung ein "Verweis". (In OData v3 wurde die Beziehung als Link bezeichnet. Die Protokollunterschiede sind für dieses Lernprogramm nicht wichtig.)
Ein Verweis verfügt über einen eigenen URI mit dem Format /Entity/NavigationProperty/$ref. Hier ist z. B. der URI, um den Verweis zwischen einem Produkt und seinem Lieferanten zu adressieren:
http:/host/Products(1)/Supplier/$ref
Um eine Beziehung hinzuzufügen, sendet der Client eine POST- oder PUT-Anforderung an diese Adresse.
- PUT, wenn die Navigationseigenschaft eine einzelne Entität ist, z. B.
Product.Supplier. - POST, wenn es sich bei der Navigationseigenschaft um eine Sammlung handelt, wie z. B.
Supplier.Products.
Der Textkörper der Anforderung enthält den URI der anderen Entität in der Beziehung. Hier ist eine Beispielanforderung:
PUT http://myproductservice.example.com/Products(6)/Supplier/$ref HTTP/1.1
OData-Version: 4.0;NetFx
OData-MaxVersion: 4.0;NetFx
Accept: application/json;odata.metadata=minimal
Accept-Charset: UTF-8
Content-Type: application/json;odata.metadata=minimal
User-Agent: Microsoft ADO.NET Data Services
Host: myproductservice.example.com
Content-Length: 70
Expect: 100-continue
{"@odata.id":"http://myproductservice.example.com/Suppliers(4)"}
In diesem Beispiel sendet der Client eine PUT-Anforderung an /Products(6)/Supplier/$ref, welche der $ref URI für das Supplier-Produkt mit der ID = 6 ist. Wenn die Anforderung erfolgreich ist, sendet der Server eine Antwort von 204 (Keine Inhalte):
HTTP/1.1 204 No Content
Server: Microsoft-IIS/8.0
Date: Tue, 08 Jul 2014 06:35:59 GMT
Hier ist die Controllermethode zum Hinzufügen einer Beziehung zu einem Product:
public class ProductsController : ODataController
{
[AcceptVerbs("POST", "PUT")]
public async Task<IHttpActionResult> CreateRef([FromODataUri] int key,
string navigationProperty, [FromBody] Uri link)
{
var product = await db.Products.SingleOrDefaultAsync(p => p.Id == key);
if (product == null)
{
return NotFound();
}
switch (navigationProperty)
{
case "Supplier":
// Note: The code for GetKeyFromUri is shown later in this topic.
var relatedKey = Helpers.GetKeyFromUri<int>(Request, link);
var supplier = await db.Suppliers.SingleOrDefaultAsync(f => f.Id == relatedKey);
if (supplier == null)
{
return NotFound();
}
product.Supplier = supplier;
break;
default:
return StatusCode(HttpStatusCode.NotImplemented);
}
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
}
// Other controller methods not shown.
}
Der Parameter "navigationProperty " gibt an, welche Beziehung festgelegt werden soll. (Wenn mehr als eine Navigationseigenschaft für die Entität vorhanden ist, können Sie weitere case Anweisungen hinzufügen.)
Der Linkparameter enthält den URI des Lieferanten. Die Web-API analysiert automatisch den Anforderungstext, um den Wert für diesen Parameter abzurufen.
Um den Lieferanten nachzuschlagen, benötigen wir die ID (oder den Schlüssel), die Teil des Linkparameters ist. Verwenden Sie hierzu die folgende Hilfsmethode:
using Microsoft.OData.Core;
using Microsoft.OData.Core.UriParser;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http.Routing;
using System.Web.OData.Extensions;
using System.Web.OData.Routing;
namespace ProductService
{
public static class Helpers
{
public static TKey GetKeyFromUri<TKey>(HttpRequestMessage request, Uri uri)
{
if (uri == null)
{
throw new ArgumentNullException("uri");
}
var urlHelper = request.GetUrlHelper() ?? new UrlHelper(request);
string serviceRoot = urlHelper.CreateODataLink(
request.ODataProperties().RouteName,
request.ODataProperties().PathHandler, new List<ODataPathSegment>());
var odataPath = request.ODataProperties().PathHandler.Parse(
request.ODataProperties().Model,
serviceRoot, uri.LocalPath);
var keySegment = odataPath.Segments.OfType<KeyValuePathSegment>().FirstOrDefault();
if (keySegment == null)
{
throw new InvalidOperationException("The link does not contain a key.");
}
var value = ODataUriUtils.ConvertFromUriLiteral(keySegment.Value, ODataVersion.V4);
return (TKey)value;
}
}
}
Im Grunde verwendet diese Methode die OData-Bibliothek, um den URI-Pfad in Segmente aufzuteilen, das Segment zu suchen, das den Schlüssel enthält, und den Schlüssel in den richtigen Typ zu konvertieren.
Löschen einer Beziehung zwischen Entitäten
Zum Löschen einer Beziehung sendet der Client eine HTTP DELETE-Anforderung an den $ref-URI:
DELETE http://host/Products(1)/Supplier/$ref
Hier ist die Controllermethode zum Löschen der Beziehung zwischen einem Produkt und einem Lieferanten:
public class ProductsController : ODataController
{
public async Task<IHttpActionResult> DeleteRef([FromODataUri] int key,
string navigationProperty, [FromBody] Uri link)
{
var product = db.Products.SingleOrDefault(p => p.Id == key);
if (product == null)
{
return NotFound();
}
switch (navigationProperty)
{
case "Supplier":
product.Supplier = null;
break;
default:
return StatusCode(HttpStatusCode.NotImplemented);
}
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
}
// Other controller methods not shown.
}
In diesem Fall stellt Product.Supplier das Ende "1" einer 1-zu-n-Beziehung dar, sodass Sie die Beziehung einfach entfernen können, indem Sie Product.Supplier auf null setzen.
Am "Many"-Ende einer Beziehung muss der Client angeben, welche zugehörige Entität entfernt werden soll. Dazu sendet der Client den URI der zugehörigen Entität in der Abfragezeichenfolge der Anforderung. So entfernen Sie z. B. "Produkt 1" aus "Lieferant 1":
DELETE http://host/Suppliers(1)/Products/$ref?$id=http://host/Products(1)
Um dies in der Web-API zu unterstützen, müssen wir einen zusätzlichen Parameter in die DeleteRef Methode einschließen. Hier ist die Controllermethode, um ein Produkt aus der Supplier.Products Beziehung zu entfernen.
public class SuppliersController : ODataController
{
public async Task<IHttpActionResult> DeleteRef([FromODataUri] int key,
[FromODataUri] string relatedKey, string navigationProperty)
{
var supplier = await db.Suppliers.SingleOrDefaultAsync(p => p.Id == key);
if (supplier == null)
{
return StatusCode(HttpStatusCode.NotFound);
}
switch (navigationProperty)
{
case "Products":
var productId = Convert.ToInt32(relatedKey);
var product = await db.Products.SingleOrDefaultAsync(p => p.Id == productId);
if (product == null)
{
return NotFound();
}
product.Supplier = null;
break;
default:
return StatusCode(HttpStatusCode.NotImplemented);
}
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
}
// Other controller methods not shown.
}
Der Schlüsselparameter ist der Schlüssel für den Lieferanten, und der relatedKey-Parameter ist der Schlüssel für das Produkt, das aus der Products Beziehung entfernt werden soll. Beachten Sie, dass die Web-API automatisch den Schlüssel aus der Abfragezeichenfolge abruft.