好吧,所以如果没有大量的代码来支持它可能很难解释,但我会尽力而为。
本质上,我正在执行一个查询(涉及ef核心2.1),涉及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);
}
但是,这将引发null引用异常,因为Users集合为null。
现在我的问题是,当我明确要求加载时,为什么Users集合为null?
目前的一种解决方法是将查询的第一行更改为_managedSupportRepository.GetAllIncluding(m => m.Users).AsEnumerable()
,这很荒谬,因为它将所有记录都带回来(几千条),因此性能不存在。
它必须为IQueryable的原因是可以应用分页,从而减少了从数据库提取的信息量。
感谢您的帮助。
答案 0 :(得分:3)
此问题有两个部分:
当您在提供程序上对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认为即使您{{1 }} ...它可能会在Visual Studio的Include()
窗口中显示警告,否则它将尝试投影并仅请求从Output
表达式中理解的字段(它不会t包括new
)。
因此,您需要在此处明确...尝试:
Users
实际上将包含ActiveUsers = Users.Count(u => !u.IsDeleted && u.Enabled && u.UserType == UserType.Active);
。
在这种情况下,将包含Users
,因为它在实际的表达式中... 但是,EF仍然不知道如何将Users
转换为提供者查询,因此它可以工作(因为ms.GetConsumed()
将被加载),但是它不会在数据库上运行,它仍将在内存上运行(它将进行客户端投影)。同样,如果您在Visual Studio的Users
窗口中运行此警告,则应该对此进行警告。
EF Core允许这样做(EF6不允许),但是您可以将其配置为在发生这种情况时引发错误(查询将在内存中进行评估):
Output
您可以在此处了解更多信息:https://docs.microsoft.com/en-us/ef/core/querying/client-eval