我們遇到了一些實體軟實體框架的軟刪除功能問題。這個想法是使用知道EF上下文的存儲庫。在存儲庫的層次上,我們實現了一個插件系統,只要在存儲庫上完成一個操作,就會執行這些插件。例如,當我們調用Repository.GetQuery<Relation>()
時,插件會被執行。其中一個插件是LogicalDeletePlugin
,這個插件應該爲select中的每個表添加一個Where(x => x.IsDeleted)
語句。我們的想法是使用ExpressionVisitor
實現這個IsDeleted
插件,該插件訪問linq表達式並查找所有「table」select語句並添加IsDeleted
條件。ExpressionVisitor軟刪除
爲了澄清問題/問題,我將使用一些代碼示例來解釋問題。
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)
「中選擇」代碼見。這就是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。現在,當我取消註釋返回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是持有Expression<Func<T, bool>>
類型的表達式的類。
InjectconditionVisitor可以與InterceptWith(QueryInterceptor NuGet包)結合使用,如query.InterceptWith(new InjectConditionVisitor(new QueryConditional(x => x.Deleted == true))
。
我不明白你的解決方案。你說'QueryConditional'是一個類,我假設,'Conditional'屬性是'Expression>'。但是'T'從哪裏來?當你說'QueryConditional(x => x.Deleted == true))'定義了哪裏'x''的類型?看起來'T'必須是特定的基類或接口,或者'QueryConditional'必須是一個泛型類'QueryConditional '。另外什麼是'ParameterRebinder'? –
xr280xr