Web API 2.2를 사용하여 OData v4의 포함

작성자: Jinfu Tan

일반적으로 엔터티는 엔터티 집합 내에 캡슐화된 경우에만 액세스할 수 있습니다. 그러나 OData v4는 WebAPI 2.2가 지원하는 두 가지 추가 옵션인 Singleton 및 Containment를 제공합니다.

이 항목에서는 WebApi 2.2의 OData 엔드포인트에서 포함을 정의하는 방법을 보여줍니다. 포함에 대한 자세한 내용은 포함이 OData v4와 함께 제공됨을 참조하세요. Web API에서 OData V4 엔드포인트를 만들려면 ASP.NET Web API 2.2를 사용하여 OData v4 엔드포인트 만들기를 참조하세요.

먼저 이 데이터 모델을 사용하여 OData 서비스에 포함 도메인 모델을 만듭니다.

데이터 모델

계정에 많은 PI(PaymentInstruments)가 포함되어 있지만 PI에 대한 엔터티 집합을 정의하지는 않습니다. 대신, PIS는 계정을 통해서만 액세스할 수 있습니다.

데이터 모델 정의

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

    ODataConventionModelBuilder 특성이 해당 탐색 속성에 추가된 경우 Contained 는 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})")] 특성 라우팅만 작동합니다. 그렇지 않으면 특성 및 기존 라우팅이 모두 작동합니다. instance 경우 는 GetPayInPIs(int key) 와 일치합니다GET ~/Accounts(1)/PayinPIs.

이 문서의 원래 콘텐츠에 대한 레오 후 감사합니다.