【问题标题】:C# IQueryable use dictionary inside Where throws Unable to create a constant value of type ExceptionC# IQueryable 在 Where throws 中使用字典无法创建异常类型的常量值
【发布时间】:2017-03-01 09:17:16
【问题描述】:

我在 IQueryable lambda linq 中使用字典抛出了

Unable to create a constant value of type 'System.Collections.Generic.KeyValuePair`2

代码:

Dictionary<int, int> keyValues = new Dictionary<int, int>();

IQueryable<Account> = context.Account
    .Where(W => keyValues
        .Where(W1 => W1.Key == S.AccountID)
        .Where(W1 => W1.Value == S.Balance)
        .Count() > 0);

详情:

我有这样的字典里面的数据

AccountID 余额

11000

2 2000

3 3000

我希望用户具有 (ID = 1 AND Balance = 1000) OR (ID = 2 AND BALANCE = 2000) OR (ID = 3 AND BALANCE = 3000)

那么我该如何为它编写 lambda 表达式呢?

已编辑

谢谢@caesay,你的回答对我帮助很大。

我希望你再帮我一个忙。

根据您的回答,我创建了如下所示的表达式:

private static Expression<Func<Accounting, bool>> GenerateExpression(Dictionary<int, int> lstAccountsBalance)
{
    try
    {
        var objAccounting = Expression.Parameter(typeof(Accounting));
        Expression expr = null;
        const bool NOT_ALLOWED = false;

        if (lstAccountsBalance != null && lstAccountsBalance.Count > 0)
        {
            var clauses = new List<Expression>();

            foreach (var kvp in lstAccountsBalance)
            {
                clauses.Add(Expression.AndAlso(
                    Expression.Equal(Expression.Constant(kvp.Key), Expression.Property(objAccounting, nameof(Accounting.ID))),
                    Expression.Equal(Expression.Constant(kvp.Value), Expression.Property(objAccounting, nameof(Accounting.Balance)))
                ));
            }

            expr = clauses.First();
            foreach (var e in clauses.Skip(1))
            {
                expr = Expression.OrElse(e, expr);
            }

            var notAllowedExpr = Expression.AndAlso(
                    Expression.Equal(Expression.Constant(NOT_ALLOWED), Expression.Property(objAccounting, nameof(Accounting.ALLOWED))),
                    Expression.Equal(Expression.Constant(true), Expression.Constant(true))
                );

            expr = Expression.And(notAllowedExpr, expr);
        }

        var allowedExpr = Expression.AndAlso(
                Expression.Equal(Expression.Constant(!NOT_ALLOWED), Expression.Property(objAccounting, nameof(Accounting.ALLOWED))),
                Expression.Equal(Expression.Property(objAccounting, nameof(Accounting.ID)), Expression.Property(objAccounting, nameof(Accounting.ID)))
            );

        if (expr != null)
        {
            expr = Expression.OrElse(allowedExpr, expr);
        }
        else
        {
            expr = allowedExpr;
        }

        return Expression.Lambda<Func<Accounting, bool>>(expr, objAccounting);
    }
    catch (Exception ex)
    {
        throw objEx;
    }
}

之后我编译了这样的表达式:

Expression<Func<Accounting, bool>> ExpressionFunctions = GenerateExpression(lstAccountsBalance);

var compiledExpression = ExpressionFunctions.Compile();

我是这样使用的:

.Select(S => new
{
    Accounting = S.Accounts
        .Join(context.AccountInfo,
                objAccounts => objAccounts.ID,
                objAccountInfo => objAccountInfo.ID,
                (objAccounts, objAccountInfo) => new Accounting
                {
                    ID = objAccounts.ID,
                    Balance = objAccountInfo.Balance,                               
                })
        .Where(W => W.ID == user.ID)
        .AsQueryable()
        .Where(W => compiledExpression(W))
        .Select(S1 => new Accounting()
        {
            ID = S1.ID,
            Balance = S1.Balance
        })
        .ToList(),
}

它会抛出异常并显示消息:

System.NotSupportedException:LINQ 表达式节点类型“Invoke” 在 LINQ to Entities 中不受支持。

不编译

没有编译它就像魅力一样。它提供了我想要的输出。

 Expression<Func<Accounting, bool>> ExpressionFunctions = GenerateExpression(lstAccountsBalance);

用途:

.Select(S => new
{
    Accounting = S.Accounts
        .Join(context.AccountInfo,
                objAccounts => objAccounts.ID,
                objAccountInfo => objAccountInfo.ID,
                (objAccounts, objAccountInfo) => new Accounting
                {
                    ID = objAccounts.ID,
                    Balance = objAccountInfo.Balance,                               
                })
        .Where(W => W.ID == user.ID)
        .AsQueryable()
        .Where(ExpressionFunctions)
        .Select(S1 => new Accounting()
        {
            ID = S1.ID,
            Balance = S1.Balance
        })

谢谢你..

【问题讨论】:

  • 那么最后一个样本有什么问题?它不正是你想要的吗?最后一个例子有什么问题?
  • 我遇到了性能问题。在我的数据库中,我有 5,00,000 条记录,从表中取出 1,000 条记录需要 10 到 12 秒。我想提高查询的性能 我想将时间减少到 3 到 4 秒。
  • 这个表达式被编译成 sql,并在你的数据库服务器上执行。在这里调用Compile 正在将表达式树转换为C# 代码——它不能再编译为SQL,即使它不会加快任何速度。这里的瓶颈是你的数据库服务器,相比之下,从这个 linq 表达式生成 sql 已经非常快了。
  • @caesay 谢谢你变化很大。我真的很感激你的工作。而 Skip and Take 到最后一条记录,例如从 4,99,000 获取数据并占用 1,000,而不是 49 秒。它确实变化缓慢。有什么建议可以提高这种性能吗?谢谢:)
  • 所以第一步是find out what SQL that EF is generating,然后在 SQL Management Studio 中运行它并分析它以查看查询的哪一部分花费的时间最长。也许您在数据库中缺少一个可以加快速度的索引。试试这个,如果你没有任何运气,请打开一个与性能相关的新问题。

标签: c# entity-framework linq lambda entity-framework-6


【解决方案1】:

EF linq 查询中的所有内容都需要编译为表达式树,然后编译为 SQL,但字典是 IEnumerable,因此 EF 无法知道如何编译它。

您可以自己构建表达式树,也可以使用System.Linq.Dynamic nuget 包自己构建sql。

使用System.Linq.Dynamic的例子,首先安装nuget包并将using添加到你的文件顶部,然后会有一个Where重载,它接受一个字符串作为参数:

context.Account.Where(
    String.Join(" OR ", keyValues.Select(kvp => $"(ID = {kvp.Key} AND Balance = {kvp.Value})")));

基本上,Where 子句中的所有内容都将直接作为 SQL 执行,因此如果接受用户输入,请小心使用 Where(string, params object[]) 重载参数化您的查询。

表达式树(未经测试)方法可能如下所示:

var account = Expression.Parameter(typeof(Account));
var clauses = new List<Expression>();
foreach(var kvp in keyValues)
{
    clauses.Add(Expression.AndAlso(
        Expression.Equal(Expression.Constant(kvp.Key), Expression.Property(account, nameof(Account.AccountID))),
        Expression.Equal(Expression.Constant(kvp.Value), Expression.Property(account, nameof(Account.Balance)))
    ));
}

var expr = clauses.First();
foreach (var e in clauses.Skip(1))
    expr = Expression.OrElse(e, expr);

context.Account.Where(Expression.Lambda<Func<Account, bool>>(expr, account));

基本上在 foreach 循环中,我们创建了所有 (ID = ... AND Balance = ...),最后我们用 OR 将它们全部连接起来。

【讨论】:

  • 谢谢@caesay,我有两个表,我正在使用.Join 加入它所以一个字段来自第一个表,另一个来自第二个表。
  • @KalpeshRajai:如果我提供的内容不足以让您制定自己的解决方案,那么您需要更新您的问题。此外,如果 EF 不符合您的需求,您可以使用 context.Set&lt;Account&gt;().SqlQuery(...) 为此执行原始 sql
  • 谢谢,我修改了你的一些代码,对我有很大帮助。我有一个问题,当指定某个字段的值时,如何仅传递表达式 Where(Expression.Lambda&lt;Func&lt;Account, bool&gt;&gt;(expr, account))
  • 例如,一个名为“Flg”的文件是真的,而且只有我希望 lambda 执行这个表达式,否则不是?请让我知道我该怎么做。非常感谢。
  • 我根据您的示例创建了表达式。现在,它工作正常。我可以知道哪个表达式对于性能编译表达式或未编译表达式更好。对不起,打扰了你能帮忙吗?如何编译表达式并在 Lambda 中使用它。我厌倦了以下var finalExpr = Expression.Lambda&lt;Func&lt;Account, bool&gt;&gt;(expr, objMFPRoleOrgFun).Compile(); 我在 lambda 中使用了它,但是它抛出了异常并且没有编译它不能抛出异常.Where(W =&gt; finalExpr(W))
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-04
  • 1970-01-01
  • 2010-09-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多