Riepilogo: voglio sapere come posso rilevare definizioni specifiche dal corpo dell'espressione e poi modificarlo nel modo che desidero, come ad esempio
e.Entity.ListA.Union(e.ListB).Any(...)...
Per
e.Entity != null &&
((e.Entity.ListA != null && e.Entity.ListA.Any(...))
|| (e.Entity.ListB != null && e.Entity.ListB.Any(...)))
Come parte della scrittura di un codice C # pulito, ho scritto un insieme di espressioni predefinite e usando estensioni LinqKit che posso combinare tra loro, quindi estenderà facilmente il dinamismo della scrittura di espressioni complesse, fino a quando tutto andrà bene. Inoltre, voglio usarli per filtrare i casi IQuerable e IEnumerable. Tuttavia, come sai, ci sono alcuni casi in cui l'espressione definita non funzionerà nel precedente o nel secondo caso, con successo ho evitato molti di questi problemi. Fino a quando non sono arrivato al caso in cui ho fatto una soluzione ma sento ancora che non è l'ideale.
Inizierò prima mostrando il problema, quindi spiegherò la soluzione desiderata, alla fine condividerò il mio tentativo.
//---
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));
}
//.........
}
Come vedi Se uso questo metodo per definire un elenco di oggetti con RoleClass è null o FreeRoles è null, verrà generata una NullException.
- il suggerimento più atteso penso che giocherà su tre fattori:
possibilità di rilevare il frammento desiderato dal corpo dell'espressione
modificare il frammento in base alle necessità per il caso IEnumerable o viceversa
ricostruire e restituire nuova espressione
in questo modo mi aiuterà a mantenere il metodo statico e modificarlo tramite il metodo di estensione: ad es. ex.WithSplittedUnion ()
piuttosto che il modo tradizionale cioè sto usando ora come segue
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,
};
}
}
Spero che la spiegazione sia chiara, grazie in anticipo
Da come la vedo, ciò di cui hai bisogno è ExpressionVisitor
per attraversare e modificare ExpressionTree
. Una cosa che cambierei è il modo in cui chiami Any
. Invece di
e.Entity != null &&
((e.Entity.ListA != null && e.Entity.ListA.Any(...))
|| (e.Entity.ListB != null && e.Entity.ListB.Any(...)))
Vorrei andare
(
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(...)
Trovo più facile costruire ExpressionTree
e il risultato sarà lo stesso.
Codice di esempio:
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);
}
}
Puoi chiamarlo così:
OptionalCallFix.Fix(context, ctx => ctx.Entity.ListA.Union(ctx.ListB));