Freigeben über


Eindämmung in OData v4 mit Web-API 2.2

von Jinfu Tan

Traditionell konnte nur auf eine Entität zugegriffen werden, wenn sie in einem Entitätssatz gekapselt wurde. OData v4 bietet jedoch zwei zusätzliche Optionen, Singleton und Containment, die beide WebAPI 2.2 unterstützen.

In diesem Thema wird gezeigt, wie Sie eine Eindämmung in einem OData-Endpunkt in WebApi 2.2 definieren. Weitere Informationen zum Containment finden Sie unter Containment is coming with OData v4. 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 mithilfe dieses Datenmodells ein Eindämmungsdomänenmodell im OData-Dienst:

Datenmodell

Ein Konto enthält viele PaymentInstruments (PI), aber wir definieren keinen Entitätssatz für eine PI. Stattdessen können auf die PIs nur über ein Konto 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 Eindämmungsnavigationseigenschaften 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();         
    }
    

    Das ODataConventionModelBuilder kümmert sich um den Aufbau des EDM-Modells, wenn das Contained Attribut zur entsprechenden Navigationseigenschaft hinzugefügt wird. Wenn es sich bei der Eigenschaft um einen Sammlungstyp 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 eine Einschließung ist.

Den enthaltenden Entitätssatzcontroller definieren

Enthaltene Entitäten besitzen keinen eigenen Controller; die Aktion wird im enthaltenden Entitätssatz-Controller definiert. In diesem Beispiel gibt es einen AccountsController, aber kein 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 vier oder mehr Segmente sind, funktioniert nur das Attribut-Routing, wie [ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")] im obigen Controller. Andernfalls funktionieren sowohl Attribut- als auch herkömmliches Routing: Zum Beispiel passt GetPayInPIs(int key) zu GET ~/Accounts(1)/PayinPIs.

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