正文表达式的复杂编辑<Func<T,bool>>



摘要:我想知道如何从表达式的正文中检测特定的定义,然后按照我想要的方式进行更改,例如

e.Entity.ListA.Union(e.ListB).Any(...)...

e.Entity != null && 
((e.Entity.ListA != null && e.Entity.ListA.Any(...)) 
|| (e.Entity.ListB != null && e.Entity.ListB.Any(...)))

在我看来,只有使用LinqExpression技术才是理想的解决方案。

作为编写干净C#代码的一部分,我已经编写了一组预定义的表达式,并使用LinqKit扩展将它们组合在一起,因此它将轻松扩展编写复杂表达式的动态性,直到一切正常为止。此外,我想使用它们来过滤IQueryableIEnumerable两种情况。

然而,正如你所知,在某些情况下,定义的表达式在前者或后者中都不起作用,我成功地避免了很多这样的问题。直到我找到了一个解决方案,但我仍然觉得不理想。

我将首先展示问题,然后解释希望的解决方案,最后,我将分享我的尝试。

//---
public class AssignmentsEx : BaseEx
{ 
//.........
/// <summary>
/// (e.FreeRoles AND e.RoleClass.Roles) ⊆ ass.AllRoles
/// </summary>
public static Expression<Func<T, bool>> RolesInclosedBy<T>(IAssignedInstitution assignedInstitution) where T : class, IAssignedInstitution
{
var allStaticRoles = AppRolesStaticData.AdminRolesStr.GetAll();
var assAllRoles = assignedInstitution.AllRoles.Select(s => s.Name).ToList();
var hasAllRoles = allStaticRoles.All(assR => assAllRoles.Any(sR => sR == assR));
if (hasAllRoles)
return e => true;
// for LINQ to SQL the expression works perfectly as you know 
// the expression will be translated to an SQL code
// for IEnumerable case the nested object Roles with throw null obj ref 
// exception if the RoleClass is null (and this is a healthy case from code execution
// 
return Expression<Func<T, bool>> whenToEntity = e => e.FreeRoles.Union(e.RoleClass.Roles).All(eR => assAllRoles.Any(assR => assR == eR.Name));
}
//.........
}

如您所见,如果我使用此方法定义RoleClass为null或FreeRoles为null的对象列表,它将引发NullException。

--我认为最好的预期建议将取决于三个因素:

  • 从表达体检测所需片段的可能性

  • 根据IEnumerable情况的需要修改片段,反之亦然

  • 重建并返回新的表达式

这种方式将帮助我保持方法的静态,并通过扩展方法对其进行修改:例如,WithSplittedUnion((

而不是传统的方式,即我现在使用的是跟随

public class AssignmentsEx
{
public LinqExpressionPurpose purpose{get;}

public AssignmentsEx(LinqExpressionPurpose purpose) : base(purpose)
{
Purpose = purpose
}
public Expression<Func<T, bool>> RolesInclosedBy<T>(IAssignedInstitution assignedInstitution) where T : class, IAssignedInstitution
{
var allStaticRoles = AppRolesStaticData.AdminRolesStr.GetAll();
var assAllRoles = assignedInstitution.AllRoles.Select(s => s.Name).ToList();
var hasAllRoles = allStaticRoles.All(assR => assAllRoles.Any(sR => sR == assR));
if (hasAllRoles)
return e => true;
Expression<Func<T, bool>> whenToObject = e => (e.FreeRoles == null || e.FreeRoles.All(eR => assAllRoles.Any(assR => assR == eR.Name)))
&& (e.RoleClass == null || e.RoleClass.Roles == null || e.RoleClass.Roles.All(eR => assAllRoles.Any(assR => assR == eR.Name)));
Expression<Func<T, bool>> whenToEntity = e => e.FreeRoles.Union(e.RoleClass.Roles).All(eR => assAllRoles.Any(assR => assR == eR.Name));
return Purpose switch
{
LinqExpressionPurpose.ToEntity => whenToEntity,
LinqExpressionPurpose.ToObject => whenToObject,
_ => null,
};
}
}

我希望解释清楚,提前感谢

在我看来,您需要的是ExpressionVisitor来遍历和修改ExpressionTree。我想改变的一件事是你称Any的方式。代替

e.Entity != null && 
((e.Entity.ListA != null && e.Entity.ListA.Any(...)) 
|| (e.Entity.ListB != null && e.Entity.ListB.Any(...)))

我会选择

(
e.Entity != null && e.Entity.ListA != null && e.Entity.ListB != null
? e.Entity.ListA.Union(e.Entity.ListB)
: e.Entity != null && e.Entity.ListA != null
? e.Entity.ListA
: e.Entity.ListB != null
? e.Entity.ListB
: new Entity[0]
).Any(...)

我发现构建ExpressionTree更容易,结果也会一样。

示例代码:

public class OptionalCallFix : ExpressionVisitor
{
private readonly List<Expression> _conditionalExpressions = new List<Expression>();
private readonly Type _contextType;
private readonly Type _entityType;
private OptionalCallFix(Type contextType, Type entityType)
{
this._contextType = contextType;
this._entityType = entityType;
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
// Replace Queryable.Union(left, right) call with:
//     left == null && right == null ? new Entity[0] : (left == null ? right : (right == null ? left : Queryable.Union(left, right)))
if (node.Method.DeclaringType == typeof(Queryable) && node.Method.Name == nameof(Queryable.Union))
{
Expression left = this.Visit(node.Arguments[0]);
Expression right = this.Visit(node.Arguments[1]);
// left == null
Expression leftIsNull = Expression.Equal(left, Expression.Constant(null, left.Type));
// right == null
Expression rightIsNull = Expression.Equal(right, Expression.Constant(null, right.Type));
// new Entity[0].AsQueryable()
Expression emptyArray = Expression.Call
(
typeof(Queryable),
nameof(Queryable.AsQueryable),
new [] { this._entityType },
Expression.NewArrayInit(this._entityType, new Expression[0])
);
// left == null && right == null ? new Entity[0] : (left == null ? right : (right == null ? left : Queryable.Union(left, right)))
return Expression.Condition
(
Expression.AndAlso(leftIsNull, rightIsNull),
emptyArray,
Expression.Condition
(
leftIsNull,
right,
Expression.Condition
(
rightIsNull,
left,
Expression.Call
(
typeof(Queryable), 
nameof(Queryable.Union), 
new [] { this._entityType }, 
left, 
Expression.Convert(right, typeof(IEnumerable<>).MakeGenericType(this._entityType))
)
)
)
);
}
return base.VisitMethodCall(node);
}
protected override Expression VisitMember(MemberExpression node)
{
Expression expression = this.Visit(node.Expression);
// Check if expression should be fixed
if (this._conditionalExpressions.Contains(expression))
{
// replace e.XXX with e == null ? null : e.XXX
ConditionalExpression condition = Expression.Condition
(
Expression.Equal(expression, Expression.Constant(null, expression.Type)),
Expression.Constant(null, node.Type),
Expression.MakeMemberAccess(expression, node.Member)
);
// Add fixed expression to the _conditionalExpressions list
this._conditionalExpressions.Add(condition);
return condition;
}
return base.VisitMember(node);
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node.Type == this._contextType)
{
// Add ParameterExpression to the _conditionalExpressions list
// It is used in VisitMember method to check if expression should be fixed this way
this._conditionalExpressions.Add(node);
}
return base.VisitParameter(node);
}
public static IQueryable<TEntity> Fix<TContext, TEntity>(TContext context, in Expression<Func<TContext, IQueryable<TEntity>>> method)
{
return ((Expression<Func<TContext, IQueryable<TEntity>>>)new OptionalCallFix(typeof(TContext), typeof(TEntity)).Visit(method)).Compile().Invoke(context);
}
}

你可以这样称呼它:

OptionalCallFix.Fix(context, ctx => ctx.Entity.ListA.Union(ctx.ListB));

最新更新