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

时间:2019-12-13 08:07:04

标签: 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 => 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;
}

,此函数将被称为另一个仅用于计数的函数:

var prepaidsCount = GetActiveCellPhoneNumbersUpToDate(serviceToken, DateTime.Date, true);
var postPaidsCount = GetActiveCellPhoneNumbersUpToDate(serviceToken, DateTime.Date, false);

当我执行它时,我看到a breaking change of EF 3.0,它说:

  

LINQ表达式'i => i的处理       “ NavigationExpandingExpressionVisitor”的“ .ToList()”失败。这可能表示EF Core中存在错误或限制。看到   https://go.microsoft.com/fwlink/?linkid=2101433以获得更详细的信息   信息。

如重大更改说明中所述,我需要在复杂的AsEnumerable子句之前使用用户ToListWhere来执行那部分LINQ并将数据带入RAM,然后继续我的查询

但是对于需要动态查询的大量数据,这绝对是疯狂的,而且效率低下。

什么可以替代?我们如何创建动态复杂查询以在运行时进行转换,而仅返回单数的标量值?

更新:实际需求不是问候实例。他们需要将复杂的过滤,排序和分组以及其他功能混合在一起,以从关系结构中提取数据。过去,我们会出于这些目的使用存储过程。将几个参数传递给数据库,并编写难看的,难以测试的,难以维护的,每周键入的,具有重构能力的SQL代码以获取数据。

现在我想到的唯一选择就是降级到那些丑陋的存储过程。噩梦在EF 3.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订阅了服务15,并在其中保留了5天,然后他取消了服务。如果我们想报告我们在 2019-10-14 拥有多少个订户,将计为John。因为在那个时候,他的最后一个行动是报名参加。但是,如果我们想报告2910-11-03处有多少订户,那么John的最后一个动作就是退出服务,不应该算他。

1 个答案:

答案 0 :(得分:1)

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

欢迎使用任何避免 的技巧。在这种特殊情况下,如果动作ID为1和2,获取活动订阅者的简单方法是获取MAX(ActionID)不为2或小于2的用户,例如:

SELECT COUNT(Distinct cellnumber)
FROM Subscriptions 
WHERE Date <=@reportDate ....
GROUP by CellNumber
HAVING MAX(ActionID)<2

LINQ中的等价于

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();

SQL Server 2016临时表

如果有幸使用SQL Server 2016或更高版本,我们可以将Subscriptions转换为临时表,并在特定的时间点简单地计算具有特定状态的预订。我们可以使用:

SELECT COUNT(DISTINCT CellPhoneNumber)
FROM Subscriptions  FOR SYSTEM_TIME AS OF @someTime
WHERE ActionID<2

EF Core不直接支持时态表,因此对于该部分查询,我们需要使用FromSqlRaw

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();

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