0

私は現在、既存のデータベースのいくつかの列の暗号化を含むプロジェクトに取り組んでいます。現在のスキーマに対して既にかなり多くのコードが作成されており、その多くはカスタム 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はパラメータではありませんが、ビジターはこれを考慮に入れる必要があります。

4

1 に答える 1

1

DB 行を表すMemberExpressionは、 の子孫になりParameterExpressionます。インメモリ オブジェクトはそうではありません。おそらく何らかの形式のFieldExpression.

あなたの場合、このようなものはほとんどの場合に機能します (コードに 1 つのメソッドを追加し、VisitMember メソッドを修正します:

private bool IsFromParameter(MemberExpression ma)
{
    if(ma.Expression.NodeType == ExpressionType.Parameter)
        return true;

    if(ma.Expression is MemberExpression)
        return IsFromParameter(ma.Expression as MemberExpression);

    return false;
}


protected override Expression VisitMember(MemberExpression ma)
{
    if (datactx != null && IsEncryptedColumnReference(ma) && IsFromParameter(ma))
        return MakeCallExpression(ma);
    }

    return base.VisitMember(ma);
}
于 2013-05-31T20:21:08.987 に答える