C# 如何在中断更改后在EF Core 3.1中创建复杂的动态查询?

C# 如何在中断更改后在EF Core 3.1中创建复杂的动态查询?,c#,entity-framework,entity-framework-core,ef-core-3.0,.net-core-3.1,C#,Entity Framework,Entity Framework Core,Ef Core 3.0,.net Core 3.1,我有一个函数,它返回一个IQueryable: private IQueryable<string> GetActiveCellPhoneNumbersUpToDate(long serviceToken, DateTime date, bool? isPrepaid = null) { var to = date.AddDays(1).Date; var query = ViewRepository .All .Where(i =&g

我有一个函数,它返回一个
IQueryable

private IQueryable<string> GetActiveCellPhoneNumbersUpToDate(long serviceToken, DateTime date, bool? isPrepaid = null)
{
    var to = date.AddDays(1).Date;
    var query = ViewRepository
        .All
        .Where(i => i.ServiceToken == serviceToken)
        .Where(i => i.Date < to);
    if (isPrepaid.HasValue)
    {
        query = query.Where(i => i.IsPrepaid == isPrepaid);
    }
    query = query.OrderByDescending(i => i.Date);
    var result = query
        .GroupBy(i => i.CellPhoneNumber)
        .Where(i => i.First().ActionId == (int)SubscriptionAction.Subscription)
        .SelectMany(i => i.ToList())
        .Select(i => i.CellPhoneNumber)
        .Distinct();
    return result;
}
当我执行它时,我看到,它说:

LINQ表达式'i=>i的处理 “NavigationExpandingExpressionVisitor”调用的.ToList()失败。这可能表明EF核心中存在缺陷或限制。看见 欲知详情 信息

如突破性变更说明中所述,我需要在复杂的
Where
子句之前使用
AsEnumerable
ToList
,以执行LINQ的该部分并将数据引入RAM,然后继续我的查询

但对于需要动态查询的大量数据来说,这绝对是疯狂的,而且效率低下得难以想象

这个的替代品是什么?我们如何创建在运行时转换的动态复杂查询,并且只返回单个标量值

更新:现实世界的需求不是hello-world示例。它们需要复杂的过滤、排序和分组等功能,混合在一起,从关系结构中提取数据。在过去,我们会使用存储过程来实现这些目的。向数据库传递几个参数,编写难看、难以测试、不可维护、每周键入、抗重构的SQL代码来获取数据

现在我想到的唯一选择是降级回那些丑陋的存储过程。这场噩梦在EF3.1中是现实吗

更新2:这是我的场景。我有一个表,其中存储了特定服务中的手机号码订阅/取消。该表的简化版本为:

create table Subscriptions
(
    Id,
    CellPhoneNumber,
    ServiceId,
    Date,
    ActionId
)
这些可以是记录:

John,+1-541-754-3010,15,2019-10-13 12:10:06.153,1
John,+1-541-754-3010,15,2019-10-18 12:10:06.153,2

在这里,我们可以看到John已经订阅了service 15,并在其中保留了5天,然后他取消了服务。如果我们想报告2019-10-14年我们有多少订户,约翰将被计算在内。因为在那个时候,他最后的行动就是报名。但是,如果我们想报告2910-11-03的订户数量,那么John的最后一个动作就是退出服务,他不应该被计算在内。

依赖于时间间隔或记录当前状态的查询可能很棘手。通常,我们必须搜索在指定时间段内具有一种状态但不具有另一种状态的订阅。这至少需要一个子查询或CTE。即使使用索引,这也会很昂贵,因为它需要对目标表进行两次搜索或扫描

任何避免这种情况的伎俩都是受欢迎的。在此特定情况下,如果操作ID为1和2,获取活动订阅者的简单方法是获取最大(操作ID)不为2或小于2的订阅者,例如:

SELECT COUNT(Distinct cellnumber)
FROM Subscriptions 
WHERE Date <=@reportDate ....
GROUP by CellNumber
HAVING MAX(ActionID)<2
EF Core不直接支持时态表,因此我们需要在查询的这一部分使用:

var query = ctx.Subscriptions
                .FromSqlRaw("select * from Subscriptions FOR SYSTEM_TIME AS OF {0}",
                            reportDate)
                .Where(sub=>sub.Date <= reportDate && sub.ServiceToken == serviceToken);
if(isPrepaid.HasValue)
{
    query = query.Where(sub => sub.IsPrePaid==isPrepaid);
}

var actives= query.Distinct()
                  .Count();
var query=ctx.Subscriptions
.FromSqlRaw(“从{0}起的系统_时间订阅中选择*”,
报告日期)
其中(sub=>sub.Date sub.isprevented==isprevented);
}
var actives=query.Distinct()
.Count();

该查询不涉及分组。它不取决于操作值的实际数量或顺序,也不会被每个订阅的多个记录所混淆。

您链接的内容不是一个突破性的更改。它检测到了代码中已经存在的bug。EF6.2还将抛出异常,而不是使用客户端评估。那是一个bug,不是一个特性。它唯一真正的用途是掩盖EF Core 1.x和2.x中的缺陷(例如,没有GROUP BY)。到目前为止,您的代码将无法为原始LINQ查询创建一个SQL查询,无法提取客户机上的大部分数据并在那里进行过滤。不用说,这是非常缓慢和低效的为什么它是一个错误?这段代码一直运行良好,没有bug。基本上,我有一个关于一系列服务的订阅和取消的表格,我想知道的是一个服务到目前为止有多少订户。这是一个单数标量值,我不想把整个数据都提取到RAM中只是为了计算它。我现在知道的唯一方法是跳回存储过程,我认为它在技术方面正在倒退。你是说我的代码已经将整个数据带到内存中了?没错。如果使用SQL Server Profiler或扩展事件,您会发现SQL查询有点出乎意料,问题是错误的。您一开始并没有复杂的查询,它们是糟糕的查询,在SQL本身毫无意义。LINQ不能做SQL本身不能做的事情。再想想你想做什么,想想你将如何在SQL中完成它,然后编写LINQ查询。如果查询看起来太复杂,不要使用ORM。ORM用于将对象映射到关系表。不用于报告只返回数据的查询。林克削弱了这一限制,但只是在一定程度上。报告查询通常属于数据库,例如viewsI不了解时态表部分。我们在某个时间点查询表。在那个特定的时间里,假设John订阅了3次,取消了两次。约翰的最后一个状态是订阅。因此,他必须在该时间点算作认购人。每个订阅的多个记录如何在时态表中不造成混乱?
var actives= ctx.Subscriptions
                .Where(sub=>sub.Date <= reportDate )
                .GroupBy(sub=>sub.CellNumber)
                .Where(grp=>grp.Max(sub=>sub.ActionId)<2)  // Results in a HAVING clause
                .Distinct()
                .Count();
var query = ctx.Subscriptions
                .Where(sub=>sub.Date <= reportDate && sub.ServiceToken == serviceToken);
if(isPrepaid.HasValue)
{
    query = query.Where(sub => sub.IsPrePaid==isPrepaid);
}

var actives= query.GroupBy(sub=>sub.CellNumber)
                  .Where(grp=>grp.Max(sub=>sub.ActionId)<2)
                  .Distinct()
                  .Count();
SELECT COUNT(DISTINCT CellPhoneNumber)
FROM Subscriptions  FOR SYSTEM_TIME AS OF @someTime
WHERE ActionID<2
var query = ctx.Subscriptions
                .FromSqlRaw("select * from Subscriptions FOR SYSTEM_TIME AS OF {0}",
                            reportDate)
                .Where(sub=>sub.Date <= reportDate && sub.ServiceToken == serviceToken);
if(isPrepaid.HasValue)
{
    query = query.Where(sub => sub.IsPrePaid==isPrepaid);
}

var actives= query.Distinct()
                  .Count();