c# - EF Core Include() 语句对于 IQueryable 为空
问题描述
好的,所以如果没有大量代码来支持它,这可能有点难以解释,但我会尽力而为。
本质上,我正在做一个涉及一对多关系的查询(目前在 ef core 2.1 上)。但是,“许多”集合在具体化时为空。
这是有问题的查询(为简洁起见,删除了一些代码)
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 => 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 => m.Users).AsEnumerable()
,这太荒谬了,因为它会将所有记录带回(几千个),因此性能不存在。
它需要是 IQueryable 的原因是可以应用分页,从而减少从数据库中提取的信息量。
任何帮助表示赞赏。
解决方案
这个问题有两个部分:
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 认为即使您使用它也不需要它......它可能会在 VisualInclude()
的窗口上显示警告Output
Studio),否则它会尝试仅投影和请求它从new
表达式中理解的字段(不包括Users
)。
所以你需要在这里明确......尝试:
ActiveUsers = Users.Count(u => !u.IsDeleted && u.Enabled && u.UserType == UserType.Active);
并将Users
实际包括在内。
2) 查询无法翻译时自动进行客户端评估
在这种情况下,Users
将被包括在内,因为它在实际表达式中......但是,EF 仍然不知道如何转换ms.GetConsumed()
为提供程序查询,所以它会工作(因为Users
会被加载),但不会在数据库上运行,它仍将在内存上运行(它将进行客户端投影)。Output
同样,如果您在 Visual Studio 的窗口中运行它,您应该会在该窗口中看到有关此问题的警告。
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
推荐阅读
- unity3d - 统一:转换孩子的位置并将父母留在原处
- c# - VSTS 多个私有 NuGet 源
- spring-cloud-dataflow - Spring Cloud 数据流仪表板
- ruby-on-rails - 为什么 ActiveRecord 关联 CollectionProxy 数据作为文本输出到屏幕?
- unit-testing - 用 Mocha Chai 和 Sinon 测试去抖动的 Vue 方法
- php - 字符串到数组(正则表达式、爆炸或其他)?
- node.js - 检索用户 ID 时出错
- forms - 未为所有用户加载表单 - Access 2016
- sql - 使用连接从嵌套选择中选择
- java - 我该如何解决这个问题?java.lang.ClassCastException