【问题标题】:EF Core Include() statement is null for IQueryableEF Core Include() 语句对于 IQueryable 为空
【发布时间】:2018-07-26 15:44:48
【问题描述】:

好的,如果没有大量代码来支持它,这可能有点难以解释,但我会尽力而为。

基本上我正在做一个查询(目前在 ef core 2.1 上),其中涉及一对多的关系。但是,“many”集合在具体化时为空。

这是有问题的查询(为简洁起见,删除了一些代码)

IQueryable<AccountViewModel> baseQuery = from ms in _managedSupportRepository.GetAllIncluding(m => m.Users) // here is the problem
                                          // a few lines of filters like the one below
                                          where string.IsNullOrEmpty(clientVersionFilter) || !string.IsNullOrEmpty(ms.ClientVersion) && ms.ClientVersion.Contains(clientVersionFilter, StringComparison.OrdinalIgnoreCase)
                                          join c in _contractRepository.GetAll() on ms.Id equals c.AssetId into contracts
                                          from c in contracts.DefaultIfEmpty()
                                          let isAssigned = c != null
                                          where !isAssignedFilter.valueExists || isAssignedFilter.value == isAssigned
                                          join a in _autotaskAccountRepository.GetAll() on ms.TenantId equals a.Id
                                          where string.IsNullOrEmpty(accountNameFilter) || !string.IsNullOrEmpty(a.AccountName) && a.AccountName.Contains(accountNameFilter, StringComparison.OrdinalIgnoreCase)
                                          select new AccountViewModel
                                          {
                                              AccountName = a.AccountName,
                                              ActiveUsers = ms.GetConsumed(), // here is the problem
                                              ClientVersion = ms.ClientVersion,
                                              ExternalIpAddress = ms.IpAddress,
                                              Hostname = ms.Hostname,
                                              Id = ms.Id,
                                              IsActive = ms.IsActive,
                                              IsAssigned = isAssigned,
                                              LastSeen = ms.CheckInTime,
                                              Status = ms.Status
                                          };

int count = baseQuery.Count();

baseQuery = baseQuery.Paging(sortOrder, start, length);

return (baseQuery.ToList(), count);

为了清楚起见,_managedSupportRepository.GetAllIncluding(m =&gt; m.Users) 方法只是 .Include() 方法的包装。

所以问题出在活动用户ActiveUsers = ms.GetConsumed(), 的视图模型中。 GetConsumed()方法如下

public long GetConsumed()
{
    return Users.Count(u => !u.IsDeleted && u.Enabled && u.UserType == UserType.Active);
}

但是,这会引发空引用异常,因为用户集合为空。 现在我的问题是,当我明确要求加载用户集合时,为什么它是空的? 目前的解决方法是将查询的第一行更改为 _managedSupportRepository.GetAllIncluding(m =&gt; m.Users).AsEnumerable(),这太荒谬了,因为它会将所有记录带回(几千条),因此性能不存在。

它需要是 IQueryable 的原因是可以应用分页,从而减少从数据库中提取的信息量。

感谢任何帮助。

【问题讨论】:

    标签: c# entity-framework entity-framework-core ef-core-2.1


    【解决方案1】:

    这个问题有两个部分:

    1) 不包括在投影中的不包括在内

    当您在提供程序上查询 EF(服务器评估)时,您不是在执行您的 new 表达式,因此:

    ActiveUsers = ms.GetConsumed(),
    

    从不实际执行ms.GetConsumed()。您为new 传递的表达式会被解析,然后转换为查询(如果是sql server,则为SQL),但ms.GetConsumed() 不会在提供程序上执行(对数据库的查询)。

    所以你需要在表达式中包含Users。例如:

    select new AccountViewModel
    {
        AccountName = a.AccountName,
        AllUsers = Users.ToList(),
        ActiveUsers = ms.GetConsumed(),
        // etc.
    }
    

    这样 EF 知道它需要 Users 进行查询并实际包含它(您没有在表达式中使用 Users,所以 EF 认为即使您 Include() 它也不需要它...它可能会在 Visual Studio 中的 Output 窗口上显示警告),否则它会尝试投影并仅请求它从 new 表达式(不包括 Users)中理解的字段。

    所以你需要在这里明确...尝试:

    ActiveUsers = Users.Count(u => !u.IsDeleted && u.Enabled && u.UserType == UserType.Active);
    

    实际上将包含Users

    2) 查询无法翻译时的自动客户端评估

    在这种情况下,Users 将被包括在内,因为它在实际表达式中... 但是,EF 仍然不知道如何将 ms.GetConsumed() 转换为提供程序查询,所以它' 会工作(因为 Users 将被加载),但它不会在数据库上运行,它仍然会在内存上运行(它将进行客户端投影)。同样,如果您在 Visual Studio 的 Output 窗口中运行它,您应该会看到有关此问题的警告。

    EF Core 允许这样做(EF6 不允许),但您可以将其配置为在发生这种情况时抛出错误(在内存中评估的查询):

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            /* etc. */
            .ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
    }
    

    您可以在此处阅读更多信息:https://docs.microsoft.com/en-us/ef/core/querying/client-eval

    【讨论】:

    • 感谢您非常详细的解释!我最终选择了更明确的选项ActiveUsers = Users.Count(u =&gt; !u.IsDeleted &amp;&amp; u.Enabled &amp;&amp; u.UserType == UserType.Active);,效果很好。我想我只是希望 ef 足够聪明来扩展方法调用本身,只是节省了在整个应用程序中重复业务逻辑!现在一切正常,感谢您的帮助。
    猜你喜欢
    • 2018-02-03
    • 2020-11-29
    • 1970-01-01
    • 2017-09-08
    • 1970-01-01
    • 1970-01-01
    • 2021-04-29
    • 2019-08-04
    • 1970-01-01
    相关资源
    最近更新 更多