私は現在、既存のデータベースのいくつかの列の暗号化を含むプロジェクトに取り組んでいます。現在のスキーマに対して既にかなり多くのコードが作成されており、その多くはカスタム linq-to-sql クエリの形式になっています。クエリの数は 5 桁近くあるため、すべてのクエリを変更して再テストするにはコストがかかりすぎます。
私たちが見つけた別の方法は、DB スキーマを同じに保つことです -- 列の長さをわずかに変更するだけです。つまり、現在のエンティティ クラスの定義を変更する必要はありません -- 代わりに、式ツリーをオンザフライで変更します。 l2sql に到達する前に、IQueryProvider
必要な列に復号化関数を適用します。Table<TEntity>
my の関連プロパティをDataContext
カスタム実装でラップすることでこれを行います。これによりIQueryable<TEntity>
、システム内のすべてのクエリをプレビューできます。
私の現在の実装では、次のクエリがあるとします。
var mydate = new DateTime(2013, 1, 1);
var context = new DataContextFactory.GetClientsContext();
Expression<Func<string>> foo = context.MyClients.First(
c => c.BirthDay < mydate).EncryptedColumn;
しかし、クエリをキャッチすると、次のように変更します。
Expression<Func<string>> foo = context.Decrypt(
context.MyClients.First(c => c.BirthDay < mydate).EncryptedColumn);
ExpressionVisitor
クラスを使用してこれを行います。このメソッドでは、 currentが暗号化された列を参照してVisitMember
いるかどうかを確認します。MemberExpression
そうであれば、メソッド呼び出しの式を次のように置き換えます。
private const string FuncName = "Decrypt";
protected override Expression VisitMember(MemberExpression ma)
{
if (datactx != null && IsEncryptedColumnReference(ma))
return MakeCallExpression(ma);
}
return base.VisitMember(ma);
}
private static bool IsEncryptedColumnReference(MemberExpression ma)
{
return ma.Member.Name == "EncryptedColumn"
&& ma.Member.DeclaringType == typeof(MyClient);
}
private Expression MakeCallExpression(MemberExpression ma)
{
const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
var mi = typeof(MyDataContext).GetMethod(FuncName, flags);
return Expression.Call(datactx, mi, ma);
}
datactx
現在のデータコンテキストを指す式への参照を持つインスタンス変数です (これは前のパスで調べます)。
私の問題は、次のようなクエリがある場合です。
var qbeClient = new MyClient { EncryptedColumn = "FooBar" };
Expression<Func<MyClient>> dbquery = () => context.MyClients.First(
c => c.EncryptedColumn == qbeClient.EncryptedColumn);
私はそれを次のように変えたい:
Expression<Func<MyClient>> dbquery = () => context.MyClients.First(c =>
context.Decrypt(c.EncryptedColumn) == qbeClient.EncryptedColumn);
代わりに、私が得ているのはこれです:
Expression<Func<MyClient>> dbquery = () => context.MyClients.First(c =>
context.Decrypt(c.EncryptedColumn) == context.Decrypt(qbeClient.EncryptedColumn));
メモリ内オブジェクトを取得した場合、データはすでに暗号化されていないため、これは望ましくありません (さらに、オブジェクトに対して厄介な db 関数を呼び出したくありません!)
これが基本的な私の質問です。MemberExpression
インスタンスがある場合、それがメモリ内のオブジェクトを参照しているのか、データベース内の行を参照しているのかをどのように判断できますか?
前もって感謝します
編集:
@Shlomo のコードは実際に投稿したケースを解決しますが、以前のテストの 1 つが壊れました。
var context = new DataContextFactory.GetClientsContext(); Expression<Func<string>> expr = context.MyClients.First().EncryptedColumn; Expression<Func<string>> expected = context.Decrypt( context.MyClients.First().EncryptedColumn); var actual = MyVisitor.Visit(expr); Assert.AreEqual(expected.ToString(), actual.ToString());
この場合、 への参照
EncryptedColumn
はパラメータではありませんが、ビジターはこれを考慮に入れる必要があります。