Reputation: 13733
I got stuck with attempt of generic casting or building a LINQ expression that could be casted to the required generic type T
.
Here is the failing example:
public override void PreprocessFilters<T>(ISpecification<T> specification)
{
if (typeof(ICompanyLimitedEntity).IsAssignableFrom(typeof(T)))
{
var companyId = 101; // not relevant, retrieving company ID here
// and now I need to call the method AddCriteria on the specification
// as it were ISpecification<ICompanyLimitedEntity>
// which it should be because at this point I know
// that T is ICompanyLimitedEntity
var cle = specification as ISpecification<ICompanyLimitedEntity>;
// ^-- but of course this conversion won't work, cle is null
// what should I do now?
// I need to call it like this:
cle.AddCriteria(e => e.CompanyId == null ||
e.CompanyId == 0 || e.CompanyId == companyId);
// BTW, AddCriteria receives Expression<Func<T, bool>> as an argument
// tried nasty casts inside of the expression - this doesn't throw NullReference
// but it doesn't translate to SQL either - gets evaluated locally in memory, which is bad
specification.AddCriteria(e => ((ICompanyLimitedEntity)e).CompanyId == null ||
((ICompanyLimitedEntity)e).CompanyId == 0 ||
((ICompanyLimitedEntity)e).CompanyId == companyId);
// this one also doesn't work
Expression<Func<ICompanyLimitedEntity, bool>> lex = e => e.CompanyId == null ||
e.CompanyId == 0 || e.CompanyId == companyId;
// it's null again -----------v
specification.AndCriteria(lex as Expression<Func<T, bool>>);
}
}
Is there any way to cast T or build a Linq expression that would treat T
as ICompanyLimitedEntity
and execute the query on the SQL server?
Upvotes: 5
Views: 690
Reputation: 205629
Here is how you can build the desired expression.
First you create compile time expression with interface type parameter
Expression<Func<ICompanyLimitedEntity, bool>> exprI = e => e.CompanyId == null ||
e.CompanyId == 0 || e.CompanyId == companyId;
then replace the parameter inside the body with a new parameter of type T
and use the modified expression as a body for the new lambda expression:
var parameterT = Expression.Parameter(typeof(T), "e");
var bodyT = exprI.Body.ReplaceParameter(exprI.Parameters[0], parameterT);
var exprT = Expression.Lambda<Func<T, bool>>(bodyT, parameterT);
where ReplaceParameter
is the typical ExpressionVisitor
based helper for replacing parameter with another expression:
public static partial class ExpressionUtils
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
=> new ParameterReplacer { Source = source, Target = target }.Visit(expression);
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
=> node == Source ? Target : node;
}
}
Note that you'll still need the member access expression fixer from Why Linq "where" expression after Select gets evaluated locally when created through a generic method?
Upvotes: 2