首页 > 解决方案 > 无法使用 MVC 核心中的影子属性翻译 LINQ 表达式

问题描述

我已经用与本文中的方法类似的方法实现了影子属性。我有一个损坏的 LINQ 查询,到目前为止它使用常规 CreatedDate 属性运行良好,现在我无法将它与 Shadow 属性一起使用:

    public List<Gastos> GetGastosPorSeleccion(ApplicationDbContext context, int idProveedor, int idContenedor, int idPuerto)
    {
        var gastos = context.Gastos
            .Where(
               item =>
                     (
                     (item.IdProveedor == idProveedor)
                     && (item.IdContenedor == idContenedor)
                     && (item.IdPuerto == idPuerto)
                     )
            )
            .GroupBy(item => item.Tipo)
            .Select(item => item.OrderByDescending(item => EF.Property<DateTime>(item, "CreatedDate"))
            .FirstOrDefault())
            .ToList();

        return gastos;
    }

这曾经在不使用 CreatedDate 作为 EF.Property 的情况下工作,但是,它不能再被翻译,我也不能进行客户端查询,因为我没有将该属性作为对象的一部分。

该查询应该首先按某些表字段进行过滤,然后按类型分组并从每个分组中检索最新记录。

这是我得到的例外:

System.InvalidOperationException: 'LINQ 表达式'(GroupByShaperExpression: KeySelector: (g.Tipo), ElementSelector:(EntityShaperExpression: EntityType: Gastos ValueBufferExpression: (ProjectionBindingExpression: EmptyProjectionMember) IsNullable: False)) .OrderByDescending(item => EF.Property( item, "CreatedDate"))' 无法翻译。以可翻译的形式重写查询,或通过插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的调用显式切换到客户端评估。有关详细信息,请参阅 https://go.microsoft.com/fwlink/?linkid=2101038

通过这样重写,查询在 LINQPad6 上运行良好:

Gastos
    .Where(
       item =>
             (
             (item.IdProveedor == idProveedor)
             && (item.IdContenedor == idContenedor)
             && (item.IdPuerto == idPuerto)
             )
    )
    .GroupBy(item => item.Tipo)
    .Select(item => item.OrderByDescending(item => item.CreatedDate)
    .FirstOrDefault())

标签: linqasp.net-core-mvc

解决方案


使用函数链编写不可翻译的 LINQ 查询是多么容易。不管你是否使用影子属性,查询是错误的,因为 group by 只能返回聚合结果或用于分组的字段 - 而不是整个记录。这是一个 SQL 限制,在这种情况下不要期望 EF 奇迹。

可以使用 EF 中不可用的窗口函数(任何当前版本)有效地编写此查询

这根本不是答案,但如果您可以使用第三方扩展linq2db.EntityFrameworkCore - 那么这个查询应该可以工作:

var rnQuery = from getgastos in context.Gastos
              where item.IdProveedor == idProveedor &&
                    item.IdContenedor == idContenedor &&
                    item.IdPuerto == idPuerto,
              select new 
              { 
                 Entity = item, 
                 RN = Sql.Ext.RowNumber().Over()
                    .PartitionBy(item.Tipo)
                    .OrderByDesc(EF.Property<DateTime>(item, "CreatedDate")).ToValue()
              };

var query = from q in rnQuery
            where q.RN == 1
            select q.Entity;

// switch to alternative LINQ translator
var query = query.ToLinqToDB(); 

推荐阅读