2

私が理解しているように、OData ベースのサービスが 'upsert' (つまり、行を挿入するか、このキーを持つ行が既に存在する場合は更新する) をサポートする通常の方法は、行のフィルターを含む PUT 要求を使用することです。およびパーティション キー。

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

私の知る限り、これが Azure テーブル ストレージがアップサートをサポートする方法です。しかし、私が知る限り、.NET Framework のビルトイン で実装された独自の OData サービスで同じことを試みるとDataService<T>、行が既に存在する場合にのみ成功します。行が存在しない場合、404 エラーが発生します。

つまり、これは更新に対してのみ機能し、挿入に対しては機能しません。

アップサートは単にサポートされていないと思われますが、決定的な答えを見つけることができませんでした. これを行う方法を誰かに教えてもらえますか、それとも絶対にできないことを確認できますか?

4

2 に答える 2

2

独自のカスタム クエリ プロバイダー (IDataServiceQueryProvider の実装) を作成してみることができます。ユーザーが存在しない単一のオブジェクトを要求しており、現在の HTTP 要求のメソッドが PUT である場合、指定された ID を持つ新しいオブジェクトを返します。組み込みの更新プロバイダーは、そこから処理してレコードを更新できるはずだと思います。そうしないと、独自の更新プロバイダーも必要になる場合があります。

WCF Data Services Toolkitを使用すると、作業が簡単になる場合があります。それ以外の場合は、独自の linq プロバイダーを作成する必要がありますが、これは統合テストにはやり過ぎのように思えます。

これは、カスタム ds プロバイダーを構築する方法を説明する、msdn に関する優れた一連のブログ投稿です。

于 2013-02-28T10:27:30.623 に答える
2

OK、ジェイソン・フレイタスが提案したことを試してみましたが、結果が複雑すぎてコメントで対処できないため、回答を追加しています.

tl;dr: 解決策はIDataServiceUpdateProviderIUpdatable. (Jason は を提案しましたがIDataServiceQueryProvider、これは役に立たないようです。) ただし、問題は、それDataService<T>が upsert をサポートするように実際には設計されておらず、更新に使用するインターフェイスでもないことです。したがって、動作させることはできますが、解決策はハッキング (そして良い意味ではありません) であり、将来問題を引き起こす可能性があると思われるものです。

長いバージョン:

これはIUpdatable、更新、挿入、および削除をサポートするために必要なものです。IDataServiceQueryProvider更新サポートに関連するものは何も追加しないのでIUpdatable、重要です。私はもともとIUpdatable.GetResource、要求されたアイテムが存在しなくてもアイテムを返すように手配すると、クエリの動作が台無しになると考えていました。しかし、もちろん、 に対するクエリDataService<T>は通過しない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);
            }
        }
    }
}

このGetResourceメソッドはIUpdatableインターフェースの一部でありDataService<T>、PUT を受け取ったときに既存のリソースを見つけようとするために使用します。ご覧のとおり、オブジェクトがまだ存在しない場合は、新しいインスタンスを構築するだけです。ただし、新しいオブジェクトをデフォルトの状態のままにしておくだけでは十分ではありません。PartitionKeyとはRowKey、着信 PUT 要求の値と一致する必要があります。また、これらの値は直接通知されません。クエリに埋め込まれています。

そこでPopulatePutStandin、これらの値をクエリから引き出すメソッドを作成しました。呼び出しを探して、クエリを表す呼び出し式のチェーンをたどりWhereます。(これは他の LINQ 演算子を処理しませんが、更新/挿入の場合、これ以上複雑なものは表示されません。)Where特定のプロパティに特定の値があるかどうかをテストする各句について、私のコードはそのプロパティをその値に設定します。新しいオブジェクトの値。実際には、これはupsert に存在する唯一の句であるためPartitionKeyandのみを設定することになりますが、特定のプロパティを検索しないコードを記述する方が簡単でした。RowKeyWhere

これは、各プロパティが独自のWhere句で処理されることを前提としているという点で、少し不安定です。理論的には、 と の両方をテストする単一の式を含むDataService<T>1 つの句を使用することをやめることはできません。したがって、理論的には次のいずれかを使用できます。WherePartitionKeyRowKey

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次のエラーが表示されます。

エンティティ タイプ 'Mm.Web.Tests.Fakes.AzureTableStorage.FakeUserPermission' には 1 つ以上の etag プロパティがあるため、このタイプの DELETE/PUT 操作には If-Match HTTP ヘッダーを指定する必要があります。

の内部でDataService<T>は、意味をなすために PUT リクエストに ETag を含める必要があると判断されました。それ以外の方法で、編集しようとしていたエンティティを編集していることを確認できますか? これは更新には意味がありますが、明らかに挿入には意味がありません。したがって、これはアップサートに適していません。

クライアント側にetagを含めてみました:

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

ただし、Azure Table Storage クライアントは、etags が upsert に対して意味をなさないことを知っているほど賢いようです。(etag を知っていれば、これは間違いなく更新であることを知っているので、upsert を使用すべきではありません。) したがって、実際にはその ETag は渡されません。

ただし、これを回避することはできます。を実装するだけでなく、から派生した をIUpdatable実装し、単一のメンバーを追加できます。IDataServiceUpdateProviderIUpdatable

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

このインターフェイスを実装する場合、基本的にはDataService<T>ETag を自分で処理する必要があることを示しています。そして、ETag がないことはまったく問題ないので、何もしなくても問題ありません。このインターフェイスの空の実装を提供するだけで、デフォルトの ETag 処理が無効になり、例外が発生しなくなります。だから、それはうまくいくようです。

1 つの問題は、偽の代役を生成している時点で、PUT と DELETE を区別する明確な方法がないことです。GetResource操作が何であるかはわかりません。または、少なくとも、直接ではありません。fullTypeNameたまたま、引数がたまたまDELETE であることがわかりましたnullが、ドキュメントはそれを約束していません。だから、文書化されていない偶然に頼っているように感じます。

そして、これは根本的な問題の兆候のように感じます。ここに含まれるインターフェイスは、アップサートをサポートするように設計されていません。したがって、「機能する」ものに似たものにすることは可能ですが、それは常にやや不十分なハックになります。

したがって、これに対する唯一の満足できる解決策は、HTTP レベルで偽造することだと思います。それが、私が偽造しようとしているセマンティクスをサポートする唯一の方法だからです。

于 2013-03-08T10:53:53.023 に答える