簡介:我想知道如何從表達式的主體中檢測特定的定義,然後以所需的方式對其進行更改,例如
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(...)))
作為編寫清晰C#代碼的一部分,我編寫了一組預定義的表達式,並使用可以在它們之間組合的LinqKit擴展,因此它將輕鬆地擴展編寫複雜表達式的動力,直到一切正常。另外,我想用它們來過濾IQuerable和IEnumerable情況。但是,如您所知,在某些情況下,定義的表達式在前者或後者中不起作用,我成功地避免了很多此類問題。在我提出解決方案之前,我仍然覺得這不是理想的選擇。
我將首先顯示問題,然後解釋所需的解決方案,最後,我將分享我的嘗試。
//---
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情況修改片段,反之亦然
重建並返回新表達式
這種方式將幫助我保持方法的靜態並通過擴展方法對其進行修改:例如ex.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));