Usando um ExpressionVisitor LINQ para substituir parâmetros primitivos por referências de propriedade em uma expressão lambda
Eu estou no processo de escrever uma camada de dados para uma parte do nosso sistema que registra informações sobre trabalhos automatizados que são executados todos os dias - nome do trabalho, quanto tempo correu, qual foi o resultado, etc.
Estou falando com o banco de dados usando o Entity Framework, mas estou tentando manter esses detalhes ocultos dos módulos de nível superior e não quero que os próprios objetos de entidade sejam expostos.
No entanto, gostaria de tornar minha interface muito flexível nos critérios usados para procurar informações sobre o trabalho. Por exemplo, uma interface de usuário deve permitir que o usuário execute consultas complexas como "me dê todos os trabalhos chamados 'olá' que foram executados entre 10h e 11h que falharam". Obviamente, isso parece um trabalho para construir dinamicamenteExpression
árvores.
Então, o que eu gostaria que minha camada de dados (repositório) fosse capaz de fazer é aceitar expressões do tipo LINQExpression<Func<string, DateTime, ResultCode, long, bool>>
(expressão lambda) e, em seguida, nos bastidores converter lambda para uma expressão que o meu Entity FrameworkObjectContext
pode usar como um filtro dentro de umWhere()
cláusula.
Em poucas palavras, estou tentando converter uma expressão lambda do tipoExpression<Func<string, DateTime, ResultCode, long, bool>>
paraExpression<Func<svc_JobAudit, bool>>
, Ondesvc_JobAudit
é o objeto de dados do Entity Framework que corresponde à tabela na qual as informações do trabalho são armazenadas. (Os quatro parâmetros no primeiro delegado correspondem ao nome do trabalho, quando foi executado, o resultado e quanto tempo demorou no MS, respectivamente)
Eu estava fazendo um progresso muito bom usando oExpressionVisitor
classe até eu bater em uma parede de tijolos e recebeu umInvalidOperationException
com esta mensagem de erro:
Quando chamado de 'VisitLambda', reescrevendo um nó do tipo 'System.Linq.Expressions.ParameterExpression' deve retornar um valor não nulo do mesmo tipo. Alternativamente, substitua 'VisitLambda' e altere-o para não visitar crianças desse tipo.
Estou completamente perplexo. Por que diabos não me permite converter nós de expressão que referenciam parâmetros para nós que fazem referência a propriedades? Existe outra maneira de fazer isso?
Aqui está um código de amostra:
namespace ExpressionTest
{
class Program
{
static void Main(string[] args)
{
Expression<Func<string, DateTime, ResultCode, long, bool>> expression = (myString, myDateTime, myResultCode, myTimeSpan) => myResultCode == ResultCode.Failed && myString == "hello";
var result = ConvertExpression(expression);
}
private static Expression<Func<svc_JobAudit, bool>> ConvertExpression(Expression<Func<string, DateTime, ResultCode, long, bool>> expression)
{
var newExpression = Expression.Lambda<Func<svc_JobAudit, bool>>(new ReplaceVisitor().Modify(expression), Expression.Parameter(typeof(svc_JobAudit)));
return newExpression;
}
}
class ReplaceVisitor : ExpressionVisitor
{
public Expression Modify(Expression expression)
{
return Visit(expression);
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node.Type == typeof(string))
{
return Expression.Property(Expression.Parameter(typeof(svc_JobAudit)), "JobName");
}
return node;
}
}
}