【问题标题】:.NET Core 3 InvalidOperationException on OrderBy with dynamic field name具有动态字段名称的 OrderBy 上的 .NET Core 3 InvalidOperationException
【发布时间】:2019-12-26 23:56:05
【问题描述】:

我正在从 .NET Core 2 或 3 版本迁移现有的 Web API。 经过几个问题,我设法使它工作,除了按列名的动态排序。

这是我的代码,在 .net core 2 上运行良好:

public async Task<IEnumerable<Clientes_view>> GetClientes(int bActivos, int nRegistroInic, int nRegistros, string sOrdenar, 
        int nSentido, string sFiltro, int nTipo = -1, int idCliente = -1)
    {
        var clientes = this.context.Set<Clientes_view>()
           .Where(e => e.RazonFantasia.Contains(sFiltro) || e.RazonFantasia.Contains(sFiltro)
               || e.Cuit.Contains(sFiltro) || e.Mail.StartsWith(sFiltro) || string.IsNullOrEmpty(sFiltro))
           .Where(e => (e.Activo && bActivos == 1) || bActivos == -1 || (!e.Activo && bActivos == 0))
           .Where(e => e.IdTipoCliente == nTipo || nTipo == -1)
           .Where(e => e.IdCliente == idCliente || idCliente == -1);

        if (!string.IsNullOrEmpty(sOrdenar))
        {
            var propertyInfo = this.context.Set<Clientes_view>().First().GetType().GetProperty(sOrdenar, 
                BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

            if (propertyInfo != null) if (nSentido == -1) clientes = clientes.OrderByDescending(e => propertyInfo.GetValue(e, null));
                else clientes = clientes.OrderBy(e => propertyInfo.GetValue(e, null));
        }

        clientes = clientes.Skip(nRegistroInic).Take(nRegistros);

        return await clientes.ToListAsync();
    }

我得到的错误如下:

System.InvalidOperationException:LINQ 表达式 'DbSet .Where(c => True) .Where(c => c.Activo && True || False || False) .Where(c => True) .Where(c => True) .OrderBy(c => __propertyInfo_3.GetValue( 对象:c, index: null))' 无法翻译。以可翻译的形式重写查询,或通过插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的调用显式切换到客户端评估。

有什么想法吗? 谢谢!

【问题讨论】:

  • 它的 EFcore 对吗?
  • 是的 EFCore 是
  • 因为我的 Web API 控制器将列名作为查询字符串进行排序。并且前端应用程序不是 NET 客户端。基本上我需要按列名(字符串)对查询进行排序
  • entityframeworkcore.com/knowledge-base/54232892/… 我认为你需要使用 OrderBy("String_ColumnName")
  • 哇,谢谢,这正是我需要的!该库与 .net Core 3 兼容吗?

标签: c# linq entity-framework-core ef-core-3.0


【解决方案1】:

您需要实际生成成员访问表达式,您所做的只是使用反射来获取某个对象的值,并将其作为表达式提供。这不起作用,查询提供者将无法翻译。

你需要做这样的事情:

if (!String.IsNullOrEmpty(sOrdenar))
{
    var type = typeof(Clientes_view);
    var prop = type.GetProperty(sOrdenar);
    if (prop != null)
    {
        var param = Expression.Parameter(type);
        var expr = Expression.Lambda<Func<Clientes_view, object>>(
            Expression.Convert(Expression.Property(param, prop), typeof(object)),
            param
        );
        if (nSentido == -1)
            clientes = clientes.OrderByDescending(expr);
        else
            clientes = clientes.OrderBy(expr);
    }
}

【讨论】:

  • 迄今为止最好的解决方案。
【解决方案2】:

您的问题是您在 order by 中使用反射,而您可能应该使用排序字符串。 options之一

Install-Package System.Linq.Dynamic
using System.Linq.Dynamic;

然后就可以排序了

query.OrderBy("item.item_id DESC")

如果您没有很多排序选项,则没有任何库的其他选项是:

switch(sOrdenar){
   case "Field1"
     clientes = nSentido == -1 ? clientes.OrderBy(entity=> entity.Field1) : clientes.OrderByDescending(entity=> entity.Field1);
     break;
   case "OtherField"
          clientes = nSentido == -1 ? clientes.OrderBy(entity=> entity.OtherField) : clientes.OrderByDescending(entity=> entity.OtherField);
     break;
}

我个人更喜欢第二个选项,因为这样我可以确定用户只能对允许的字段进行排序,否则如果你有大表并且用户开始对错误的字段进行排序,你可能会遇到性能问题(永远不要相信你的用户:) )。

【讨论】:

  • Install-Package System.Linq.Dynamic 是免费版还是授权版?
【解决方案3】:

EF Core 尝试将尽可能多的查询转换为服务器端查询(即 SQL)。在 3.0 之前的版本中,任何无法转换的代码都在客户端上静默运行 - 但是,这可能会导致大量且通常不直观的性能问题,因此从 3.0 开始决定,如果任何查询代码无法翻译,则会出现异常立即被扔掉。

参考:https://docs.microsoft.com/en-us/ef/core/querying/client-eval#previous-versions

最终结果是您要么需要重新架构代码以分离可以在服务器上运行和不能在服务器上运行的部分,要么强制所有内容在客户端上运行。参考文档解释了如何实现后者,但请注意,这样做可能会对性能产生重大影响。

在您的情况下,if (!string.IsNullOrEmpty(sOrdenar)) 块内的内容是导致问题的原因。您应该知道,这意味着无论何时执行该块,它后面的分页(SkipTake)都没有在服务器上执行,总是在客户端上执行——所以如果你曾经遇到过性能问题用这个方法,现在你知道为什么了吧!

【讨论】:

  • 我相信 SkipTake 如果没有采用 sOrdenar 分支,则在服务器上执行,但如果是,则在客户端上执行。这确实使它在性能方面有点不可预测。但是,如果 OP 坚持在查询中使用反射,则无法绕过客户端分页。
  • @IMil 是的,您是正确的-我已经编辑了我的答案以澄清这一点-谢谢!
  • 感谢伊恩的详细解释。现在我对我的代码停止工作的原因有了更好的了解。当然我需要我的查询在服务器端运行。对我来说非常奇怪的是 EFCore 无法将简单的 orderby 转换为 SQL 代码。所以真正的问题应该是如何使用列名执行服务器端排序。
  • @ercpap 但它不是“简单的排序”,它是对反射库的调用。 EF Core 和经典 EF 都无法将其转换为服务器端。它一直是客户端。
  • 好的,让我换个说法:如何在服务器端对这个查询进行排序,而不根据列名(字符串)进行反射?我不“想”使用反射。没有它,我找不到其他方法。
【解决方案4】:

很明显,通过反射调用属性并不能自动转换为 SQL 查询。

以前唯一可行的方法是永远不会采用此分支,或者整个查询由您的应用程序而不是在数据库端处理。

要解决此问题,请按照错误消息的建议进行操作:将查询分解为 DB 和应用程序部分,例如

if (!string.IsNullOrEmpty(sOrdenar))
{
    IEnumerable<Clientes_view> list = await clientes.AsAsyncEnumerable();
    list = list.Where(.....); //here you may use everything you like
    return list;
}

如果您正在寻找一种在服务器端动态生成OrderBy 部分的方法,请查看this answer;显然它是为经典 EF 编写的,但应该可以在 EF Core 中稍作调整。

【讨论】:

  • 很抱歉,我在这个问题上看不到任何明显的东西。我不明白为什么 EFCore 不能将简单的订单转换为 SQL 代码。
  • @ercpap 我添加了一个链接,指向一个类似问题的解决方案。
  • @ercpap 关键点是“通过反射调用属性”——没有现有的查询提供者会翻译这种“简单”的表达式。顺便说一句,this.context.Set&lt;Clientes_view&gt;().First().GetType() 是获取typeof(Clientes_view) 的可怕方式。
  • @ercpap 实际上,看起来该解决方案对于您的情况可能有点过于通用。尝试像该答案建议的那样构造selector,然后写OrderBy(selector),这可能就足够了。但不能保证:)
猜你喜欢
  • 2021-11-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-27
  • 1970-01-01
  • 2018-05-12
  • 2021-11-29
相关资源
最近更新 更多