OK、ジェイソン・フレイタスが提案したことを試してみましたが、結果が複雑すぎてコメントで対処できないため、回答を追加しています.
tl;dr: 解決策はIDataServiceUpdateProvider
とIUpdatable
. (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 に存在する唯一の句であるためPartitionKey
andのみを設定することになりますが、特定のプロパティを検索しないコードを記述する方が簡単でした。RowKey
Where
これは、各プロパティが独自のWhere
句で処理されることを前提としているという点で、少し不安定です。理論的には、 と の両方をテストする単一の式を含むDataService<T>
1 つの句を使用することをやめることはできません。したがって、理論的には次のいずれかを使用できます。Where
PartitionKey
RowKey
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
実装し、単一のメンバーを追加できます。IDataServiceUpdateProvider
IUpdatable
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 レベルで偽造することだと思います。それが、私が偽造しようとしているセマンティクスをサポートする唯一の方法だからです。