c# - 如何在提供 IQueryable 输出的 linq 查询中使用 Func

标签 c# linq iqueryable func

我提供了以下查询(简化版)以从我的服务返回 IQueryable:

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)
             });

而且根据我的查询,我的服务类中还有以下代码:

//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)产生的错误:

LINQ to Entities 不支持 LINQ 表达式节点类型“Invoke”。

对应方案(2)产生的错误:

这是一个编译错误: 需要方法、委托(delegate)或事件

非常感谢任何高级帮助。

最佳答案

这真的取决于 leaky abstractionIQueryable<> 暴露与 ORM 相结合。

在内存中执行时,第一次尝试实际上会起作用;但是,使用 ORM 时情况并非如此。 原因您的第一个代码无法与 LINQ to entities 一起使用,是 Func<>编译代码。它不代表可以轻松转换为 SQL 的表达式树。

第二次尝试是自然的尝试解决方案,但由于您的代码有点神奇地转换为表达式树而失败。在编写选择时,您并不是在针对 Expression 进行编码对象。但是当你编译代码时; C# 会自动将其转换为表达式树。不幸的是,没有办法轻松带来实际 Expression项目混合。

你需要的是:

  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 之前重写它,从而允许您将表达式直接内联到树中。

关于c# - 如何在提供 IQueryable 输出的 linq 查询中使用 Func,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47744663/

相关文章:

asp.net-mvc - 对于强类型 View ,自定义类型的 MVC LINQ to SQL JOIN 失败

c# - LINQ - 删除表达式树的部分内容

c# - 我有一个带有动画 gif 的图片框的问题,它不是移动的,由 c# 代码

c# - 需要在 Linq 中执行子查询

c# - redis查询二级索引的最有效方式

c# - 生成通用列表的组合

c# - 外部变量陷阱

c# - 如何使用 AggregateToCollection() 将 IMongoQueryable 的结果存储在集合中

C# 等价于 C++ 中的 const 指针/指向 const 的指针

c# winforms 如何旋转图像(但不围绕其中心旋转)