Web API 2.2 を使用した OData v4 の包含

作成者: Jinfu Tan

従来、エンティティはエンティティ セット内にカプセル化されている場合のみアクセスできました。 ただし、OData v4 には、シングルトンと包含という 2 つの追加オプションがあり、どちらも WebAPI 2.2 でサポートされています。

このトピックでは、WebApi 2.2 の OData エンドポイントで包含を定義する方法について説明します。 包含の詳細については、「OData v4 に付属する包含」を参照してください。 Web API で OData V4 エンドポイントを作成するには、「ASP.NET Web API 2.2 を使用して OData v4 エンドポイントを作成する」を参照してください。

まず、次のデータ モデルを使用して、OData サービスに包含ドメイン モデルを作成します。

Data model

アカウントには多数の PaymentInstrument (PI) が含まれていますが、PI のエンティティ セットは定義しません。 代わりに、PI にはアカウント経由でのみアクセスできます。

データ モデルの定義

  1. CLR 型を定義します。

    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; }     
    }
    

    Contained 属性は包含ナビゲーション プロパティに使用されます。

  2. CLR 型に基づいて EDM モデルを生成します。

    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();         
    }
    

    Contained 属性が対応するナビゲーション プロパティに追加された場合、ODataConventionModelBuilder は EDM モデルの構築を処理します。 プロパティがコレクション型の場合は、GetCount(string NameContains) 関数も作成されます。

    生成されたメタデータは次のようになります。

    <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>
    

    ContainsTarget 属性は、ナビゲーション プロパティが包含であることを示します。

包含エンティティ セット コントローラーを定義する

包含されるエンティティには独自のコントローラーがありません。アクションは、包含エンティティ セット コントローラーで定義されます。 このサンプルには AccountsController がありますが、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;         
    }     
}

OData パスが 4 つ以上のセグメントの場合、上記のコントローラーの [ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")] など、属性ルーティングのみが機能します。 それ以外の場合は、属性と従来のルーティングの両方が機能します。たとえば、GetPayInPIs(int key)GET ~/Accounts(1)/PayinPIs に一致します。

この記事のオリジナル コンテンツの作成者である Leo Hu に感謝します。