.NET Core 3 InvalidOperationException on OrderBy 动态字段名称
.NET Core 3 InvalidOperationException on OrderBy with dynamic field name
我正在从 .NET Core 2 或 3 版本迁移现有网站 API。
在几个问题之后,我设法让它工作,除了按列名的动态 OrderBy。
这是我的代码,适用于 .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: The LINQ expression 'DbSet
.Where(c => True)
.Where(c => c.Activo && True || False || False)
.Where(c => True)
.Where(c => True)
.OrderBy(c => __propertyInfo_3.GetValue(
obj: c,
index: null))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync().
有什么想法吗?
谢谢!
很明显,通过反射调用属性不能自动转换为 SQL 查询。
它以前唯一可行的方法是从未采用此分支,或者整个查询由您的应用程序而不是在数据库端处理。
要解决此问题,请按照错误消息的建议进行操作:将查询分解为数据库和应用程序部分,例如
if (!string.IsNullOrEmpty(sOrdenar))
{
IEnumerable<Clientes_view> list = await clientes.AsAsyncEnumerable();
list = list.Where(.....); //here you may use everything you like
return list;
}
如果您正在寻找一种在服务器端动态生成 OrderBy
部分的方法,请查看 ;显然它是为经典 EF 编写的,但应该可以在 EF Core 中使用,只需稍作调整。
EF Core 尝试将尽可能多的查询转换为服务器端查询(即 SQL)。在 3.0 之前的版本中,任何无法转换的代码都会在客户端静默 运行 - 然而,这可能会导致大量且通常不直观的性能问题,因此从 3.0 开始决定,如果无法转换任何查询代码, 将立即抛出异常。
参考:https://docs.microsoft.com/en-us/ef/core/querying/client-eval#previous-versions
最终结果是,您要么需要重新构建代码,将服务器上可以和不能 运行 的部分分开,要么强制所有内容都在服务器上 运行客户。参考文档解释了如何实现后者,但请注意,这样做可能会对性能产生重大影响。
在您的情况下,if (!string.IsNullOrEmpty(sOrdenar))
块内的内容是导致问题的原因。您应该知道,这意味着无论何时执行该块,它后面的分页(Skip
和 Take
)都不会在服务器上执行,总是在客户端上执行 - 所以如果您使用此方法曾遇到过性能问题,现在你知道为什么了!
您的问题是您在 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;
}
我个人更喜欢第二种选择,因为那样我可以确定用户只能对允许的字段进行排序,否则如果你有大表并且用户开始对错误的字段进行排序,你可能会遇到性能问题(永远不要相信您的用户 :)).
您需要实际生成成员访问表达式,您所做的只是使用反射来获取某个对象的值,并将其作为表达式提供。那行不通,查询提供程序将无法翻译它。
你需要做这样的事情:
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);
}
}
我正在从 .NET Core 2 或 3 版本迁移现有网站 API。 在几个问题之后,我设法让它工作,除了按列名的动态 OrderBy。
这是我的代码,适用于 .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: The LINQ expression 'DbSet .Where(c => True) .Where(c => c.Activo && True || False || False) .Where(c => True) .Where(c => True) .OrderBy(c => __propertyInfo_3.GetValue( obj: c, index: null))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync().
有什么想法吗? 谢谢!
很明显,通过反射调用属性不能自动转换为 SQL 查询。
它以前唯一可行的方法是从未采用此分支,或者整个查询由您的应用程序而不是在数据库端处理。
要解决此问题,请按照错误消息的建议进行操作:将查询分解为数据库和应用程序部分,例如
if (!string.IsNullOrEmpty(sOrdenar))
{
IEnumerable<Clientes_view> list = await clientes.AsAsyncEnumerable();
list = list.Where(.....); //here you may use everything you like
return list;
}
如果您正在寻找一种在服务器端动态生成 OrderBy
部分的方法,请查看
EF Core 尝试将尽可能多的查询转换为服务器端查询(即 SQL)。在 3.0 之前的版本中,任何无法转换的代码都会在客户端静默 运行 - 然而,这可能会导致大量且通常不直观的性能问题,因此从 3.0 开始决定,如果无法转换任何查询代码, 将立即抛出异常。
参考:https://docs.microsoft.com/en-us/ef/core/querying/client-eval#previous-versions
最终结果是,您要么需要重新构建代码,将服务器上可以和不能 运行 的部分分开,要么强制所有内容都在服务器上 运行客户。参考文档解释了如何实现后者,但请注意,这样做可能会对性能产生重大影响。
在您的情况下,if (!string.IsNullOrEmpty(sOrdenar))
块内的内容是导致问题的原因。您应该知道,这意味着无论何时执行该块,它后面的分页(Skip
和 Take
)都不会在服务器上执行,总是在客户端上执行 - 所以如果您使用此方法曾遇到过性能问题,现在你知道为什么了!
您的问题是您在 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;
}
我个人更喜欢第二种选择,因为那样我可以确定用户只能对允许的字段进行排序,否则如果你有大表并且用户开始对错误的字段进行排序,你可能会遇到性能问题(永远不要相信您的用户 :)).
您需要实际生成成员访问表达式,您所做的只是使用反射来获取某个对象的值,并将其作为表达式提供。那行不通,查询提供程序将无法翻译它。
你需要做这样的事情:
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);
}
}