Freigeben über


Containment in OData v4 using Web API 2.2

von Jinfu Tan

Traditionell konnte nur auf eine Entität zugegriffen werden, wenn sie innerhalb einer Entitätsgruppe gekapselt wurde. OData v4 bietet jedoch zwei zusätzliche Optionen, Singleton und Containment, die von WebAPI 2.2 unterstützt werden.

In diesem Thema wird gezeigt, wie Sie ein Containment in einem OData-Endpunkt in WebApi 2.2 definieren. Weitere Informationen zum Containment finden Sie unter Containment wird mit OData v4 bereitgestellt. Informationen zum Erstellen eines OData V4-Endpunkts in der Web-API finden Sie unter Erstellen eines OData v4-Endpunkts mit ASP.NET-Web-API 2.2.

Zunächst erstellen wir mit diesem Datenmodell ein Containmentdomänenmodell im OData-Dienst:

Datenmodell

Ein Konto enthält viele PaymentInstruments (PI), aber wir definieren keinen Entitätssatz für einen PI. Stattdessen kann nur über ein Konto auf die PIs zugegriffen werden.

Definieren des Datenmodells

  1. Definieren Sie die CLR-Typen.

    public class Account     
    {         
        public int AccountID { get; set; }         
        public string Name { get; set; }         
        [Contained]         
        public IList<PaymentInstrument> PayinPIs { get; set; }     
    }     
    
    public class PaymentInstrument     
    {         
        public int PaymentInstrumentID { get; set; }        
        public string FriendlyName { get; set; }     
    }
    

    Das Contained Attribut wird für Containment-Navigationseigenschaften verwendet.

  2. Generieren Sie das EDM-Modell basierend auf den CLR-Typen.

    public static IEdmModel GetModel()         
    {             
        ODataConventionModelBuilder builder = new ODataConventionModelBuilder();             
        builder.EntitySet<Account>("Accounts");             
        var paymentInstrumentType = builder.EntityType<PaymentInstrument>();             
        var functionConfiguration = 
            paymentInstrumentType.Collection.Function("GetCount");             
        functionConfiguration.Parameter<string>("NameContains");             
        functionConfiguration.Returns<int>();             
        builder.Namespace = typeof(Account).Namespace;             
        return builder.GetEdmModel();         
    }
    

    Der ODataConventionModelBuilder verarbeitet das Erstellen des EDM-Modells, wenn das Contained Attribut der entsprechenden Navigationseigenschaft hinzugefügt wird. Wenn es sich bei der Eigenschaft um einen Auflistungstyp handelt, wird auch eine GetCount(string NameContains) Funktion erstellt.

    Die generierten Metadaten sehen wie folgt aus:

    <EntityType Name="Account">   
      <Key>     
        <PropertyRef Name="AccountID" />   
      </Key>   
      <Property Name="AccountID" Type="Edm.Int32" Nullable="false" />   
      <Property Name="Name" Type="Edm.String" />   
      <NavigationProperty 
        Name="PayinPIs" 
        Type="Collection(ODataContrainmentSample.PaymentInstrument)" 
        ContainsTarget="true" /> 
    </EntityType>
    

    Das ContainsTarget Attribut gibt an, dass die Navigationseigenschaft ein Containment ist.

Definieren des enthaltenden Entitätssatzcontrollers

Eigenständige Entitäten verfügen nicht über einen eigenen Controller. die Aktion wird im enthaltenden Entitätssatzcontroller definiert. In diesem Beispiel gibt es einen AccountsController, aber keinen PaymentInstrumentsController.

public class AccountsController : ODataController     
{         
    private static IList<Account> _accounts = null;         
    public AccountsController()         
    {             
        if (_accounts == null)             
        {                 
            _accounts = InitAccounts();             
        }         
    }         
    // PUT ~/Accounts(100)/PayinPIs         
    [EnableQuery] 
    public IHttpActionResult GetPayinPIs(int key)         
    {             
        var payinPIs = _accounts.Single(a => a.AccountID == key).PayinPIs;             
        return Ok(payinPIs);         
    }         
    [EnableQuery]         
    [ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")]         
    public IHttpActionResult GetSinglePayinPI(int accountId, int paymentInstrumentId)         
    {             
        var payinPIs = _accounts.Single(a => a.AccountID == accountId).PayinPIs;             
        var payinPI = payinPIs.Single(pi => pi.PaymentInstrumentID == paymentInstrumentId);             
        return Ok(payinPI);         
    }         
    // PUT ~/Accounts(100)/PayinPIs(101)         
    [ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")]         
    public IHttpActionResult PutToPayinPI(int accountId, int paymentInstrumentId, [FromBody]PaymentInstrument paymentInstrument)         
    {             
        var account = _accounts.Single(a => a.AccountID == accountId);             
        var originalPi = account.PayinPIs.Single(p => p.PaymentInstrumentID == paymentInstrumentId);             
        originalPi.FriendlyName = paymentInstrument.FriendlyName;             
        return Ok(paymentInstrument);         
    }         
    // DELETE ~/Accounts(100)/PayinPIs(101)         
    [ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")]         
    public IHttpActionResult DeletePayinPIFromAccount(int accountId, int paymentInstrumentId)         
    {             
        var account = _accounts.Single(a => a.AccountID == accountId);             
        var originalPi = account.PayinPIs.Single(p => p.PaymentInstrumentID == paymentInstrumentId);             
        if (account.PayinPIs.Remove(originalPi))             
        {                 
            return StatusCode(HttpStatusCode.NoContent);             
        }             
        else             
        {                 
            return StatusCode(HttpStatusCode.InternalServerError);             
        }         
    }         
    // GET ~/Accounts(100)/PayinPIs/Namespace.GetCount() 
    [ODataRoute("Accounts({accountId})/PayinPIs/ODataContrainmentSample.GetCount(NameContains={name})")]         
    public IHttpActionResult GetPayinPIsCountWhoseNameContainsGivenValue(int accountId, [FromODataUri]string name)         
    {             
        var account = _accounts.Single(a => a.AccountID == accountId);             
        var count = account.PayinPIs.Where(pi => pi.FriendlyName.Contains(name)).Count();             
        return Ok(count);         
    }         
    private static IList<Account> InitAccounts()         
    {             
        var accounts = new List<Account>() 
        { 
            new Account()                 
            {                    
                AccountID = 100,                    
                Name="Name100",                    
                PayinPIs = new List<PaymentInstrument>()                     
                {                         
                    new PaymentInstrument()                         
                    {                             
                        PaymentInstrumentID = 101,                             
                        FriendlyName = "101 first PI",                         
                    },                         
                    new PaymentInstrument()                         
                    {                             
                        PaymentInstrumentID = 102,                             
                        FriendlyName = "102 second PI",                         
                    },                     
                },                 
            },             
        };            
        return accounts;         
    }     
}

Wenn der OData-Pfad 4 oder mehr Segmente aufweist, funktioniert nur das Attributrouting, z [ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")] . B. im obigen Controller. Andernfalls funktioniert sowohl das Attribut als auch das konventionelle Routing: Für instance GetPayInPIs(int key) entspricht GET ~/Accounts(1)/PayinPIs.

Vielen Dank an Leo Hu für den ursprünglichen Inhalt dieses Artikels.