如何在提供IQueryable输出的linq查询中使用Func [英] How to use Func in a linq query that provide an IQueryable output

查看:53
本文介绍了如何在提供IQueryable输出的linq查询中使用Func的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我提供了以下查询(简化版)以从我的服务返回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.

您需要的是:

  1. 占位符函数以获取对表达式的引用
  2. 如果要将查询发送到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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆