如何在提供IQueryable输出的linq查询中使用Func [英] How to use Func in a linq query that provide an IQueryable output
问题描述
我提供了以下查询(简化版)以从我的服务返回IQueryable:
I provided following query (simplified version) to return an IQueryable from my service:
var query =
(from item in _entityRepository.DbSet()
where
MyCondition
orderby Entity.EntityID descending
select new DTOModel
{
Id = Entity.EntityID,
...,
//My problem is here, when I trying to call a function into linq query:
//Size = Entity.IsPersian ? (Entity.EntitySize.ConvertNumbersToPersian()) : (Entity.EntitySize)
//Solution (1):
//Size = ConvertMethod1(Entity)
//Solution (2):
//Size = ConvertMethod2(Entity)
});
根据我的查询,我的服务类中还包含以下代码:
And also I have following codes in my service class according to my query:
//Corresponding to solution (1):
Func<Entity, string> ConvertMethod1 = p => (p.IsPersian ? p.EntitySize.ConvertNumbersToPersian() : p.EntitySize);
//Corresponding to solution (2):
Expression<Func<Entity, string>> ConvertMethod2 = (p) => (p.IsPersian ? p.EntitySize.ConvertNumbersToPersian() : p.EntitySize);
我已经看到以下错误:
对应于解决方案(1)的产生的错误:
Generated error corresponding to solution (1):
LINQ to Entities不支持LINQ表达式节点类型"Invoke".
The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
与解决方案(2)相对应的生成的错误:
Generated error corresponding to solution (2):
其编译错误:方法,委托或事件是预期的
Its a compilation error: Methods, delegate or event is expected
非常感谢您提供任何高级帮助.
Thanks a lot for any advanced help.
推荐答案
This is really down to the leaky abstraction exposed by IQueryable<>
in combination with ORMs.
在内存中执行时,第一次尝试将使工作生效;但是,使用ORM并非如此.您的第一个代码原因不适用于LINQ实体,因为 common 是 Func<>
的代码.它不代表可以轻松转换为SQL的表达式树.
The first attempt will infact work when executing in memory; however, it's not the case when utilizing an ORM. The reason your first code won't work with LINQ to entities, is that a Func<>
is compiled code. It doesn't represent an expression tree which can be easily converted to SQL.
第二次尝试是自然尝试的解决方案,但由于将代码转换为表达式树而有些神奇,因此失败了.在编写选择时,您并没有针对 Expression
对象进行编码.但是,当您编译代码时;C#将自动将其转换为表达式树.不幸的是,没有办法轻松地将 actual Expression
项引入组合.
The second attempt is the natural attempted solution, but breaks because of the somewhat magical conversion of your code into an expression tree. While you're writing the select, you're not coding against Expression
objects. But when you compile the code; C# will automatically convert it into an expression tree. Unfortunately, there's no way to easily bring actual Expression
items into the mix.
您需要的是:
- 占位符函数以获取对表达式的引用
- 如果要将查询发送到ORM,则使用表达式树重写器.
查询的最终结果是:
Expression<Func<Person, int>> personIdSelector = person => person.PersonID;
var query = Persons
.Select(p =>
new {
a = personIdSelector.Inline(p)
})
.ApplyInlines();
具有以下表达帮助器:
public static class ExpressionExtensions
{
public static TT Inline<T, TT>(this Expression<Func<T, TT>> expression, T item)
{
// This will only execute while run in memory.
// LINQ to Entities / EntityFramework will never invoke this
return expression.Compile()(item);
}
public static IQueryable<T> ApplyInlines<T>(this IQueryable<T> expression)
{
var finalExpression = expression.Expression.ApplyInlines().InlineInvokes();
var transformedQuery = expression.Provider.CreateQuery<T>(finalExpression);
return transformedQuery;
}
public static Expression ApplyInlines(this Expression expression) {
return new ExpressionInliner().Visit(expression);
}
private class ExpressionInliner : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.Name == "Inline" && node.Method.DeclaringType == typeof(ExpressionExtensions))
{
var expressionValue = (Expression)Expression.Lambda(node.Arguments[0]).Compile().DynamicInvoke();
var arg = node.Arguments[1];
var res = Expression.Invoke(expressionValue, arg);
return res;
}
return base.VisitMethodCall(node);
}
}
}
// https://codereview.stackexchange.com/questions/116530/in-lining-invocationexpressions/147357#147357
public static class ExpressionHelpers
{
public static TExpressionType InlineInvokes<TExpressionType>(this TExpressionType expression)
where TExpressionType : Expression
{
return (TExpressionType)new InvokeInliner().Inline(expression);
}
public static Expression InlineInvokes(this InvocationExpression expression)
{
return new InvokeInliner().Inline(expression);
}
public class InvokeInliner : ExpressionVisitor
{
private Stack<Dictionary<ParameterExpression, Expression>> _context = new Stack<Dictionary<ParameterExpression, Expression>>();
public Expression Inline(Expression expression)
{
return Visit(expression);
}
protected override Expression VisitInvocation(InvocationExpression e)
{
var callingLambda = e.Expression as LambdaExpression;
if (callingLambda == null)
return base.VisitInvocation(e);
var currentMapping = new Dictionary<ParameterExpression, Expression>();
for (var i = 0; i < e.Arguments.Count; i++)
{
var argument = Visit(e.Arguments[i]);
var parameter = callingLambda.Parameters[i];
if (parameter != argument)
currentMapping.Add(parameter, argument);
}
if (_context.Count > 0)
{
var existingContext = _context.Peek();
foreach (var kvp in existingContext)
{
if (!currentMapping.ContainsKey(kvp.Key))
currentMapping[kvp.Key] = kvp.Value;
}
}
_context.Push(currentMapping);
var result = Visit(callingLambda.Body);
_context.Pop();
return result;
}
protected override Expression VisitParameter(ParameterExpression e)
{
if (_context.Count > 0)
{
var currentMapping = _context.Peek();
if (currentMapping.ContainsKey(e))
return currentMapping[e];
}
return e;
}
}
}
这将允许您在到达ORM之前重新编写表达式树 ,从而允许将表达式直接内联到树中.
This will allow you to re-write the expression tree before it ever gets to an ORM, allowing you to inline the expression directly into the tree.
这篇关于如何在提供IQueryable输出的linq查询中使用Func的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!