2

エンティティ フレームワークで論理的な削除機能を実装する際に問題が発生しています。アイデアは、EF コンテキストを認識しているリポジトリを使用することです。プラグイン システムを実装したレポジトリのレベルでは、レポジトリでアクションが実行されるたびに、これらのプラグインが実行されます。たとえばRepository.GetQuery<Relation>()、プラグインを呼び出すと実行されます。プラグインの 1 つは です。このプラグインは、select にある各テーブルにステートメントをLogicalDeletePlugin追加する必要があります。Where(x => x.IsDeleted)アイデアは、linq 式にアクセスし、すべての「テーブル」選択ステートメントを見つけて条件を追加するIsDeletedを使用してこのプラグインを実装することでした。ExpressionVisitorIsDeleted

質問/問題を明確にするために、いくつかのコード例を使用して問題を説明します。

void Main()
{
var options = new ReadonlyRepositoryOptions() { ConnectionStringDelegate = () => Connection.ConnectionString };
using (var context = new ReadonlyObjectContextRepository<PFishEntities>(options))
{
var query = context.GetQuery<Relation>()
.Select(x => new {
Test = x.Bonus,
TestWorks = x.Bonus.Where(y => y.bonID == 100)
});

query.InterceptWith(new TestVisitor()).ToList();
}
}

public class TestVisitor : ExpressionVisitor {
private ParameterExpression Parameter { get; set; }

protected override Expression VisitBinary(BinaryExpression node) {
"VisitBinary".Dump();
Expression left = this.Visit(node.Left);
Expression right = this.Visit(node.Right);

var newParams = new[] { Parameter };
var condition = (LambdaExpression)new LogicalDeletePlugin().QueryConditionals.First().Conditional;
var paramMap = condition.Parameters.Select((original, i) => new { original, replacement = newParams[i] }).ToDictionary(p => p.original, p => p.replacement);

var fixedBody = ParameterRebinder.ReplaceParameters(paramMap, condition.Body);
return Expression.MakeBinary(ExpressionType.AndAlso, node, fixedBody, node.IsLiftedToNull, node.Method);
    }

protected override Expression VisitParameter(ParameterExpression expr)
{
Parameter = expr;
return base.VisitParameter(expr);
}
}
void Main()
{
    var options = new ReadonlyRepositoryOptions() { ConnectionStringDelegate = () => Connection.ConnectionString };
    using (var context = new ReadonlyObjectContextRepository<PFishEntities>(options))
    {
        var query = context.GetQuery<Relation>()
        .Select(x => new {
            Test = x.Bonus,
            TestWorks = x.Bonus.Where(y => y.bonID == 100)
        });

        query.InterceptWith(new TestVisitor()).ToList();
    }
}

public class TestVisitor : ExpressionVisitor {
    private ParameterExpression Parameter { get; set; }

    protected override Expression VisitBinary(BinaryExpression node) {
        "VisitBinary".Dump();
        Expression left = this.Visit(node.Left);
        Expression right = this.Visit(node.Right);

        var newParams = new[] { Parameter };
        var condition = (LambdaExpression)new LogicalDeletePlugin().QueryConditionals.First().Conditional;
        var paramMap = condition.Parameters.Select((original, i) => new { original, replacement = newParams[i] }).ToDictionary(p => p.original, p => p.replacement);
        var fixedBody = ParameterRebinder.ReplaceParameters(paramMap, condition.Body);
        return Expression.MakeBinary(ExpressionType.AndAlso, node, fixedBody, node.IsLiftedToNull, node.Method);
    }

    protected override Expression VisitParameter(ParameterExpression expr)
    {
        Parameter = expr;
        return base.VisitParameter(expr);
    }
}

上記の c# コードは、次の SQL コードになります。

SELECT 
[UnionAll1].[relID] AS [C1], 
[UnionAll1].[C2] AS [C2], 
[UnionAll1].[C1] AS [C3], 
[UnionAll1].[bonID] AS [C4], 
[UnionAll1].[bonCUSTOMERID] AS [C5], 
[UnionAll1].[bonRELATIONARTICLEBONUSID] AS [C6], 
[UnionAll1].[bonINVOICEID] AS [C7], 
[UnionAll1].[bonSALEROWID] AS [C8], 
[UnionAll1].[bonVALUE] AS [C9], 
[UnionAll1].[bonPERCENTAGE] AS [C10], 
[UnionAll1].[bonMANUAL] AS [C11], 
[UnionAll1].[bonPAID] AS [C12], 
[UnionAll1].[IsDeleted] AS [C13], 
[UnionAll1].[InternalReference] AS [C14], 
[UnionAll1].[ConcurrencyToken] AS [C15], 
[UnionAll1].[Created] AS [C16], 
[UnionAll1].[CreatedBy] AS [C17], 
[UnionAll1].[Updated] AS [C18], 
[UnionAll1].[UpdatedBy] AS [C19], 
[UnionAll1].[DisplayMember] AS [C20], 
[UnionAll1].[ValueMember] AS [C21], 
[UnionAll1].[SearchField] AS [C22], 
[UnionAll1].[CreateDate] AS [C23], 
[UnionAll1].[C3] AS [C24], 
[UnionAll1].[C4] AS [C25], 
[UnionAll1].[C5] AS [C26], 
[UnionAll1].[C6] AS [C27], 
[UnionAll1].[C7] AS [C28], 
[UnionAll1].[C8] AS [C29], 
[UnionAll1].[C9] AS [C30], 
[UnionAll1].[C10] AS [C31], 
[UnionAll1].[C11] AS [C32], 
[UnionAll1].[C12] AS [C33], 
[UnionAll1].[C13] AS [C34], 
[UnionAll1].[C14] AS [C35], 
[UnionAll1].[C15] AS [C36], 
[UnionAll1].[C16] AS [C37], 
[UnionAll1].[C17] AS [C38], 
[UnionAll1].[C18] AS [C39], 
[UnionAll1].[C19] AS [C40], 
[UnionAll1].[C20] AS [C41], 
[UnionAll1].[C21] AS [C42], 
[UnionAll1].[C22] AS [C43]
FROM  (SELECT 
    CASE WHEN ([Extent2].[bonID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1], 
    [Extent1].[relID] AS [relID], 
    1 AS [C2], 
    [Extent2].[bonID] AS [bonID], 
    [Extent2].[bonCUSTOMERID] AS [bonCUSTOMERID], 
    [Extent2].[bonRELATIONARTICLEBONUSID] AS [bonRELATIONARTICLEBONUSID], 
    [Extent2].[bonINVOICEID] AS [bonINVOICEID], 
    [Extent2].[bonSALEROWID] AS [bonSALEROWID], 
    [Extent2].[bonVALUE] AS [bonVALUE], 
    [Extent2].[bonPERCENTAGE] AS [bonPERCENTAGE], 
    [Extent2].[bonMANUAL] AS [bonMANUAL], 
    [Extent2].[bonPAID] AS [bonPAID], 
    [Extent2].[IsDeleted] AS [IsDeleted], 
    [Extent2].[InternalReference] AS [InternalReference], 
    [Extent2].[ConcurrencyToken] AS [ConcurrencyToken], 
    [Extent2].[Created] AS [Created], 
    [Extent2].[CreatedBy] AS [CreatedBy], 
    [Extent2].[Updated] AS [Updated], 
    [Extent2].[UpdatedBy] AS [UpdatedBy], 
    [Extent2].[DisplayMember] AS [DisplayMember], 
    [Extent2].[ValueMember] AS [ValueMember], 
    [Extent2].[SearchField] AS [SearchField], 
    [Extent2].[CreateDate] AS [CreateDate], 
    CAST(NULL AS bigint) AS [C3], 
    CAST(NULL AS bigint) AS [C4], 
    CAST(NULL AS bigint) AS [C5], 
    CAST(NULL AS bigint) AS [C6], 
    CAST(NULL AS bigint) AS [C7], 
    CAST(NULL AS decimal(20,4)) AS [C8], 
    CAST(NULL AS decimal(20,4)) AS [C9], 
    CAST(NULL AS bit) AS [C10], 
    CAST(NULL AS decimal(20,4)) AS [C11], 
    CAST(NULL AS bit) AS [C12], 
    CAST(NULL AS varchar(1)) AS [C13], 
    CAST(NULL AS varbinary(1)) AS [C14], 
    CAST(NULL AS datetimeoffset) AS [C15], 
    CAST(NULL AS varchar(1)) AS [C16], 
    CAST(NULL AS datetimeoffset) AS [C17], 
    CAST(NULL AS varchar(1)) AS [C18], 
    CAST(NULL AS varchar(1)) AS [C19], 
    CAST(NULL AS varchar(1)) AS [C20], 
    CAST(NULL AS varchar(1)) AS [C21], 
    CAST(NULL AS datetime2) AS [C22]
    FROM  [dbo].[Relation] AS [Extent1]
    LEFT OUTER JOIN [dbo].[Bonus] AS [Extent2] ON [Extent1].[relID] = [Extent2].[bonCUSTOMERID]
UNION ALL
    SELECT 
    2 AS [C1], 
    [Extent3].[relID] AS [relID], 
    2 AS [C2], 
    CAST(NULL AS bigint) AS [C3], 
    CAST(NULL AS bigint) AS [C4], 
    CAST(NULL AS bigint) AS [C5], 
    CAST(NULL AS bigint) AS [C6], 
    CAST(NULL AS bigint) AS [C7], 
    CAST(NULL AS decimal(20,4)) AS [C8], 
    CAST(NULL AS decimal(20,4)) AS [C9], 
    CAST(NULL AS bit) AS [C10], 
    CAST(NULL AS decimal(20,4)) AS [C11], 
    CAST(NULL AS bit) AS [C12], 
    CAST(NULL AS varchar(1)) AS [C13], 
    CAST(NULL AS varbinary(1)) AS [C14], 
    CAST(NULL AS datetimeoffset) AS [C15], 
    CAST(NULL AS varchar(1)) AS [C16], 
    CAST(NULL AS datetimeoffset) AS [C17], 
    CAST(NULL AS varchar(1)) AS [C18], 
    CAST(NULL AS varchar(1)) AS [C19], 
    CAST(NULL AS varchar(1)) AS [C20], 
    CAST(NULL AS varchar(1)) AS [C21], 
    CAST(NULL AS datetime2) AS [C22], 
    [Extent4].[bonID] AS [bonID], 
    [Extent4].[bonCUSTOMERID] AS [bonCUSTOMERID], 
    [Extent4].[bonRELATIONARTICLEBONUSID] AS [bonRELATIONARTICLEBONUSID], 
    [Extent4].[bonINVOICEID] AS [bonINVOICEID], 
    [Extent4].[bonSALEROWID] AS [bonSALEROWID], 
    [Extent4].[bonVALUE] AS [bonVALUE], 
    [Extent4].[bonPERCENTAGE] AS [bonPERCENTAGE], 
    [Extent4].[bonMANUAL] AS [bonMANUAL], 
    [Extent4].[bonPAID] AS [bonPAID], 
    [Extent4].[IsDeleted] AS [IsDeleted], 
    [Extent4].[InternalReference] AS [InternalReference], 
    [Extent4].[ConcurrencyToken] AS [ConcurrencyToken], 
    [Extent4].[Created] AS [Created], 
    [Extent4].[CreatedBy] AS [CreatedBy], 
    [Extent4].[Updated] AS [Updated], 
    [Extent4].[UpdatedBy] AS [UpdatedBy], 
    [Extent4].[DisplayMember] AS [DisplayMember], 
    [Extent4].[ValueMember] AS [ValueMember], 
    [Extent4].[SearchField] AS [SearchField], 
    [Extent4].[CreateDate] AS [CreateDate]
    FROM  [dbo].[Relation] AS [Extent3]
    INNER JOIN [dbo].[Bonus] AS [Extent4] ON ([Extent3].[relID] = [Extent4].[bonCUSTOMERID]) AND (100 = [Extent4].[bonID]) AND ([Extent4].[IsDeleted] <> cast(1 as bit))) AS [UnionAll1]
ORDER BY [UnionAll1].[relID] ASC, [UnionAll1].[C1] ASC

結果の SQL クエリでわかるように、IsDeletedステートメントはTestWorks = x.Bonus.Where(y => !y.IsDeleted)「select」コードに追加されます。TestVisitorが現在行っていることです。しかし、問題は、これを他の選択にもどのように実装できるかということです。これは一部x => !x.IsDeletedに追加されません。Test = x.Bonus

ExpressionVisitor はこれを行うための正しいアプローチですか、それとも別のソリューションを使用する必要がありますか? すべての助けに感謝します!説明が十分に明確でない場合はお知らせください。追加情報を提供します。

編集:

protected override Expression VisitMember(MemberExpression node)
    {
        var test = typeof(bool);
        if (node.Type != test && node.Type != typeof(string))
        {
            var type = typeof(ArticleVat);
            var condition = (LambdaExpression)Condition;
            var newParams = new[] { Expression.Parameter(type, "x") };
            var paramMap = condition.Parameters.Select((original, i) => new { original, replacement = newParams[i] }).ToDictionary(p => p.original, p => p.replacement);
            var fixedBody = ParameterRebinder.ReplaceParameters(paramMap, condition.Body);
            condition = Expression.Lambda(fixedBody, newParams);
            var whereM = whereMethod.MakeGenericMethod(new [] { type });
            var expr = Expression.Property(node.Expression, "ArticleVat");
            var whereExpr = Expression.Call(whereM, expr, condition);
//          whereExpr.Dump();
node.Dump();
//          return Expression.MakeMemberAccess(whereExpr, node.Expression.Type.GetMember(node.Member.Name).Single());
//           return Expression.MakeMemberAccess(
//                whereExpr,
//                node.Expression.Type.GetMember(node.Member.Name).Single());
        }

        return base.VisitMember(node);
    }

上記は、私が ExpressionVisitor に追加したものです。return Expression.MamkeMemberaccess コードのコメントを外すと、MemberExpression などを想定していないため、例外がスローされます。

以下は私が思いついた解決策です:

/// <summary>
/// This visitor will append a .Where(QueryCondition) clause for a given Condition to each Navigation property
/// </summary>
public class InjectConditionVisitor : ExpressionVisitor
{
    private QueryConditional QueryCondition { get; set; }

    public InjectConditionVisitor(QueryConditional condition)
    {
        QueryCondition = condition;
    }

    protected override Expression VisitMember(MemberExpression ex)
    {
        // Only change generic types = Navigation Properties
        // else just execute the normal code.
        return !ex.Type.IsGenericType ? base.VisitMember(ex) : CreateWhereExpression(QueryCondition, ex) ?? base.VisitMember(ex);
    }

    /// <summary>
    /// Create the where expression with the adapted QueryConditional
    /// </summary>
    /// <param name="condition">The condition to use</param>
    /// <param name="ex">The MemberExpression we're visiting</param>
    /// <returns></returns>
    private Expression CreateWhereExpression(QueryConditional condition, Expression ex)
    {
        var type = ex.Type.GetGenericArguments().First();
        var test = CreateExpression(condition, type);
        if (test == null)
            return null;
        var listType = typeof(IQueryable<>).MakeGenericType(type);
        return Expression.Convert(Expression.Call(typeof(Enumerable), "Where", new Type[] { type }, (Expression)ex, test), listType);
    }

    /// <summary>
    /// Adapt a QueryConditional to the member we're currently visiting.
    /// </summary>
    /// <param name="condition">The condition to adapt</param>
    /// <param name="type">The type of the current member (=Navigation property)</param>
    /// <returns>The adapted QueryConditional</returns>
    private LambdaExpression CreateExpression(QueryConditional condition, Type type)
    {
        var lambda = (LambdaExpression)condition.Conditional;
        var conditionType = condition.GetType().GetGenericArguments().FirstOrDefault();
        // Only continue when the condition is applicable to the Type of the member
        if (conditionType == null)
            return null;
        if (!conditionType.IsAssignableFrom(type))
            return null;

        var newParams = new[] { Expression.Parameter(type, "bo") };
        var paramMap = lambda.Parameters.Select((original, i) => new { original, replacement = newParams[i] }).ToDictionary(p => p.original, p => p.replacement);
        var fixedBody = ParameterRebinder.ReplaceParameters(paramMap, lambda.Body);
        lambda = Expression.Lambda(fixedBody, newParams);

        return lambda;
    }
}

QueryConditional は、 type の Expression を保持するクラスExpression<Func<T, bool>>です。

InjectconditionVisitor は、 のように InterceptWith (QueryInterceptor NuGet パッケージ) と組み合わせて使用​​できますquery.InterceptWith(new InjectConditionVisitor(new QueryConditional(x => x.Deleted == true))

4

2 に答える 2

0

はい、使用するExpressionVisitorのが正しいアプローチです。

に変身する必要がありx.Bonusますx.Bonus.Where(x => !x.IsDeleted)。に対しても同じことを行うことをお勧めしますx.Bonus.Where(y => y.bonID == 100)。に変換しますx.Bonus.Where(x => !x.IsDeleted).Where(y => y.bonID == 100)

IQueryable<Bonus>これは、 type の任意の Expression を typeの別の式に変換する必要があることを意味しますがIQueryable<Bonus>、where 句が追加されています。

バイナリ式だけでなく、すべてのExpressionVisitor.Visit式にアクセスするには、おそらく非常に一般的なメソッドをオーバーライドする必要があります。

ここで、まだ考えたことのない特殊なケースに遭遇する可能性が非常に高くなります。これは難しいでしょうが、楽しいです:)

于 2012-10-15T22:37:08.597 に答える
-1

勝利!今日、ナビゲーション プロパティであっても、IsDeleted where 句を各選択に追加する ExpressionVisitor を作成しました。

于 2013-03-14T10:32:40.583 に答える