2013-02-27 1 views
2

OData 기반 서비스가 'upsert'를 지원하는 일반적인 방법 (즉, 행 삽입 또는이 키가있는 행이 이미 존재하는 경우 업데이트)는 PUT 요청을 통해 행과 파티션 키용 필터를 포함합니다.DataService는 <T>을 지원합니다. Upsert (삽입 또는 바꾸기)

http://myaccount.table.core.windows.net/mytable(PartitionKey='myPartitionKey', RowKey='myRowKey1') 

내가 아는 한, 이것은 Azure 테이블 스토리지가 업서 트를 지원하는 방법입니다. 그러나 내가 알 수있는 한 .NET Framework에 내장 된 DataService<T>으로 구현 된 OData 서비스에서 동일한 작업을 시도하면이 행이 이미 존재하는 경우에만 성공합니다. 행이 존재하지 않으면 404 오류가 발생합니다.

즉, 이는 삽입이 아닌 업데이트를 위해서만 작동합니다.

나는 upsert가 단순히 지원되지는 않지만 확실한 답을 찾을 수 없었다고 생각합니다. 아무도 나에게이 일을하는 방법을 말해 주거나 내가 확실히 할 수 없다는 것을 확인할 수 있습니까?

+0

GET을 수행하여 행이 있는지 확인하고 결과에 따라 행/파티션 키를 PUT에 포함하거나 제외합니다. –

+0

본질적인 지원이 없으면 upsert를 구현하는 방법을 알고 있습니다. (제안은 완전하지 않습니다. 다른 클라이언트가 GET과 PUT 사이에 삽입을 수행 할 수있는 경쟁 조건이 있습니다. 실패에 대비하고 업데이트로 돌아갈 준비를해야합니다.) 내가 알고 싶은 것은 업서 트를 지원할 방법이 있는지 여부입니다. (내가 DataService 을 사용하여 통합 테스트 목적으로 Azure 테이블 스토리지의 가짜 버전을 제공하기 때문에 Azure는 upsert를 지원하므로 가짜가 지원되기를 원한다.) –

+0

그게 내가 이미했던거야. 그러나 홈 - 브루어 업 (home-brew-upsert)의 문제점은 하나의 요청이 될 수있는 것을 2 개 이상, 아마도 3 개 이상의 예외로 바꿀 수 있다는 것이다. 정확하지는 않습니다. (그리고 테이블 스토리지 서버의 부하가 문제가되는 시나리오에 따라 달라질 수 있습니다.) 그래서 위의 질문에 "내가 지원하지 않는 서비스로 어떻게 업그레이드 할 수 있습니까?"라는 질문을하지 않았습니다. –

답변

2

사용자 지정 쿼리 공급자 (IDataServiceQueryProvider 구현)를 만들 수 있습니다. 사용자가 존재하지 않는 단일 객체를 요청하고 현재 HTTP 요청의 메서드가 PUT 인 경우 지정된 id로 새 객체를 반환합니다. I 내장 된 업데이트 공급자가 거기에서 처리하고 레코드를 업데이트 할 수 있어야한다고 생각합니다. 그렇지 않으면 자체 업데이트 공급자도 필요할 수 있습니다.

WCF Data Services Toolkit을 사용하면 작업을 쉽게 할 수 있습니다. 그렇지 않으면 통합 테스트를 위해 과도한 소리가 나는 자신의 linq 공급자를 작성해야합니다.

This은 맞춤 ds 공급자를 만드는 방법을 설명하는 msdn에 대한 블로그 게시물 시리즈입니다.

+0

섹션 7에서는 업데이트에 대해 다룹니다. 기본 제공 리플렉션 기반 공급자를 사용하는 것과 달리 사용자 지정 쿼리 구현을 제공하는지 여부에 관계없이 업데이트 의미를 변경하지 않는 것으로 보입니다. 'IDataServiceUpdateProvider'와'IUpdatable'은 이미 사용하고 있습니다. (그리고 PUT이 사용 중임을 알려주지는 않습니다.)하지만, IUpdatable.GetResource를 오해했을 수도 있다는 것을 깨닫게되었습니다. 쿼리가 엉망이 될 수 있다고 생각했지만 물론 IUpdatable을 사용하지 않습니다. 나는 이것을 시도하고 볼 것이다 ... –

+0

나는 이것이 이것을 해결할 수있게 해주는 직접적으로 나를 지적했기 때문에 이것을 답으로 표시했다. 그러나 그 해결책은 결국 실천의 차이를 보게되었습니다. 그 이유는 너무 복잡하여 의견에 맞지 않으므로 게시 한 답변을 참조하십시오. –

2

제이슨 프레리 타스 (Jason Freitas)가 제안한 내용을 시도해 보았습니다. 결과가 너무 복잡하여 의견을 제시 할 수 없으므로 답변을 추가하고 있습니다.

tl; dr : 솔루션은 IDataServiceUpdateProviderIUpdatable 주위를 돌고 있지만 일종의 정렬이 가능합니다. (Jason은 IDataServiceQueryProvider을 제안했지만 도움이되지는 않는다.) 문제는 비록 DataService<T>이 실제로 upsert를 지원하도록 설계되지 않았고 인터페이스가 업데이트를 위해 사용되는 것이 아니기 때문에 작동시킬 수는 있지만 솔루션 해킹 (좋은 방식이 아님)이며, 앞으로 문제가 발생할 수 있다고 생각하는 사람이 있습니다.

긴 버전 : 이미 무엇을 업데이트, 삽입을 지원하는 데 필요한 및 삭제 것입니다 IUpdatable를 구현했다

. IDataServiceQueryProvider은 업데이트 지원과 관련된 내용을 추가하지 않으므로 IUpdatable이 중요합니다. 나는 원래 요청한 항목이 존재하지 않아도 IUpdatable.GetResource이 항목을 반환하도록 조정하면 쿼리 동작을 엉망으로 만들 것이라고 생각한 실수를 범했습니다. 물론 DataService<T>에 대한 쿼리는 IUpdatable을 거치지 않으므로 요청 된 것과 상관없이 해당 메서드가 개체를 반환하도록 할 수 있습니다.

놀랍게도 그렇게하는 것이 복잡하며, 충분하지 않을 수도 있습니다. GetResource 방법은 IUpdatable 인터페이스의 일부

public object GetResource(IQueryable query, string fullTypeName) 
{ 
    var item = query.Cast<object>().SingleOrDefault(); 
    if (item == null && fullTypeName != null) 
    { 
     var ctor = Type.GetType(fullTypeName).GetConstructor(Type.EmptyTypes); 
     if (ctor != null) 
     { 
      item = ctor.Invoke(null); 
      PopulatePutStandin(query.Expression, item); 
     } 
    } 

    return item; 
} 

private void PopulatePutStandin(Expression expression, object item) 
{ 
    var call = expression as MethodCallExpression; 
    if (call != null && call.Method.Name == "Where" && call.Method.DeclaringType == typeof(Queryable)) 
    { 
     foreach (Expression arg in call.Arguments) 
     { 
      var ux = arg as UnaryExpression; 
      if (ux != null) 
      { 
       var op = ux.Operand as LambdaExpression; 
       if (op != null) 
       { 
        var bx = op.Body as BinaryExpression; 
        if (bx != null && bx.Method.Name == "op_Equality") 
        { 
         var left = bx.Left as MemberExpression; 
         var right = bx.Right as ConstantExpression; 
         if (left != null && right != null) 
         { 
          var prop = left.Member as PropertyInfo; 
          if (prop != null) 
          { 
           prop.SetValue(item, right.Value); 
          } 
         } 
        } 
       } 
      } 
      else 
      { 
       PopulatePutStandin(arg, item); 
      } 
     } 
    } 
} 

그건, 그리고 DataService<T> 그것이 PUT를받을 때 기존 자원을 찾으려고하기 위해 사용하는 하나 : 여기에 코드입니다.보시다시피 오브젝트가 아직 존재하지 않으면 새로운 인스턴스가 생성됩니다. 그러나 새 객체를 기본 상태로 두는 것만으로는 충분하지 않습니다. PartitionKeyRowKey은 들어오는 PUT 요청의 값과 일치해야합니다. 그리고이 값들은 직접적으로 알려주지 않습니다 - 쿼리에 포함됩니다.

그래서 PopulatePutStandin 메서드를 사용하여 해당 값을 쿼리에서 가져 오게했습니다. Where 호출을 찾고 쿼리를 나타내는 호출 식 체인을 따라 이동합니다. (다른 LINQ 연산자는 처리하지 않지만 업데이트/삽입의 경우 더 복잡한 것은 표시되지 않아야합니다.) 각 Where 절에 대해 특정 속성에 특정 값이 있는지 확인하기 위해 테스트 할 때 코드에서 해당 속성을 그 새로운 객체의 값. 실제로는 PartitionKeyRowKey 만 설정하고 결국 업서 트는 Where 절이 있기 때문에 특정 속성을 찾지 않는 코드를 작성하는 것이 더 쉽습니다.

각 속성이 자체 Where 절을 통해 처리된다고 가정 할 때 약간 희미합니다. 이론적으로 DataService<T>Where 절을 사용하는 것을 막을 수는 없습니다. PartitionKeyRowKey에 대한 단일 식 테스트가 포함되어 있습니다.

src.Where(e => e.PartitionKey == "123").Where(e => e.RowKey == "456") 

또는

src.Where(e => e.PartitionKey == "123" && e.RowKey == "456") 

사람들은 모두 같은 효과가 있어야합니다 그래서 이론적으로는 중 하나를 사용할 수 있습니다. 이전 버전을 사용했는데 내 코드가 이에 의존하고 있지만 DataService<T>이 특정 형식으로 쿼리를 제공하겠다고 약속 한 문서를 찾지 못했습니다. 따라서 구현 세부 사항의 변경 사항은 DataService<T>에 민감합니다. 더 견고한 구현은 문제가 있다고 느끼더라도 양 양식을 처리하기를 원할 것입니다. 이론 상으로는 여러 가지 방법으로 질문 할 수 있습니다. 여기에 표시 될 수있는 검색어와 일치하는 새 객체를 완전히 일반적이고 안전하게 탐색 할 수있는 방법은 확실하지 않습니다.

그러나이 코드는 테스트에서 실행되는 코드이므로 개발 당시 문제의 종류를 감지 할 수 있으므로 받아 들일 만합니다.

하지만 PUT에 적합한 타겟을 생성 할 수는 있지만 충분하지는 않습니다. 우리는 다음과 같은 오류와 DataServiceException를 얻을 : DataService<T>

Since entity type 'Mm.Web.Tests.Fakes.AzureTableStorage.FakeUserPermission' has one or more etag properties, If-Match HTTP header must be specified for DELETE/PUT operations on this type.

내장은 어떤 의미를 만들기 위해이라면 어떻게 다른 당신이 개체를 편집하고 확인 것이기 때문에 PUT 요청,의 ETag를 포함해야한다고 결정했다 너 편집하려고 했어? 업데이트에 대해서는 의미가 있지만 삽입물에는 의미가 없습니다. 그래서 이것은 업서 친화적이지 않습니다.

나는 클라이언트 측의 ETAG을 포함하여 시도 :

var permission = new TableEntity(userId, claimId) { ETag = "*" }; 
await _myTable.ExecuteAsync(TableOperation.InsertOrReplace(permission)); 

그러나, 푸른 표 스토리지 클라이언트가 ETag를가 upsert에 대한 이해가 없다는 것을 알고 충분히 분명히 똑똑하다. (만약 당신이 etag을 안다면, 이것은 확실히 업데이트라는 것을 알기 때문에 upsert를 사용해서는 안된다.) 그래서 실제로 그 ETag를 전달하지는 않는다.

그러나이 문제를 해결할 수 있습니다.이 인터페이스를 구현하는 경우

public void SetConcurrencyValues(
    object resourceCookie, 
    bool? checkForEquality, 
    IEnumerable<KeyValuePair<string, object>> concurrencyValues) 
{ 
} 

, 당신은 기본적으로 당신이 ETag를 직접 처리 할 DataService<T>을 말하는 것 : 오히려 단지 IUpdatable를 구현하는 것보다, 당신은 IUpdatable에서 파생 및 단일 구성원을 추가하는 IDataServiceUpdateProvider을 구현할 수 있습니다. ETag가 없어지면 정말 기쁩니다. 아무 것도하지 않아도됩니다. 이 인터페이스의 빈 구현을 제공하기 만하면 기본 ETag 처리가 비활성화되므로 더 이상 예외가 발생하지 않습니다. 그래서 그것은 일종의 것 같습니다.

가짜 스탠드 인을 생성하는 지점에서 PUT 또는 DELETE를 구별하는 명확한 방법이 없다는 것이 하나의 문제입니다. GetResource에는 작업 내용을 알 수 없습니다. 적어도 직접적으로는 아닙니다. 결과적으로 DELAYE에 대한 fullTypeName 인수가 null 인 것으로 밝혀졌지만 문서에서 보장하지 않습니다. 그래서 나는 문서화되지 않은 우연의 일치에 의지하고 있다고 생각합니다.

그리고이 문제는 근본적인 문제의 증상처럼 느껴집니다. 여기에 관련된 인터페이스는 upsert를 지원하도록 설계되지 않았습니다. 따라서 '일하는'것과 흡사 한 것으로 만들 수는 있지만 항상 불만족스러운 해킹이 될 수 있습니다.

그래서 이것이 유일한 만족스러운 해결책은 HTTP 수준에서 위조하는 것입니다. 이것이 내가 가짜를 시도하는 의미를 지원하는 유일한 방법이기 때문입니다.

+1

기꺼이 도와 드리겠습니다. 내가 IDataServiceQueryProvider와 관련하여 언급 한 것은 IQueryable이 IUpdateProvider.GetResource에 전달 된 것이 IDataServiceQueryProvider.GetQueryRootForResourceSet의 반환 값이라는 것입니다. 귀하의 방법은 linq 공급자를 만들 필요가 없기 때문에 훨씬 쉽습니다. 그러나 존재하지 않는 것에 대해 PUT하려고한다면 404를 얻는 지 확실하지 않습니다. PUT과 DELETE를 구분하는 데 System.Web.HttpContext.Current.Request.HttpMethod를 사용할 수 있습니까? –

+0

'DataService '이 존재하는지 여부를 결정하는 방법은'GetResource'를 호출하는 것입니다.따라서 "존재하지 않는 것에 대한 PUT"은 실제로 발생하지 않습니다. 리소스가 존재하는지 (요구 한 것에 관계없이) 말하고 있기 때문입니다. 귀하의 HttpMethod 제안은 아마 작동 할 것이지만, 지금, 내 코드는 HttpContext에 의존하지 않으므로 테스트하기가 더 쉽습니다. (예, 코드는 테스트의 일부입니다. 그러나 자체 테스트가 필요하다고 생각하기에 충분히 복잡합니다 ...) 그래서 이제는 이후의'IUpdatable' 호출에서 연산을 추론 할 수 있을지 궁금합니다. . –