我有以下查询:
from ds in Db.DistributorStores
where ds.IsActive
let enterprise = ds.DistributorStore_Enterprise.Where(x => x.EnterpriseId == Enterprise.EnterpriseId).SingleOrDefault()
let parentEnterprise = ds.DistributorStore_Enterprise.Where(x => x.Enterprise.ChildEnterprises.Any(child => child.EnterpriseId == Enterprise.EnterpriseId)).SingleOrDefault()
let active = enterprise.IsActive || parentEnterprise.IsActive
let display = enterprise.Display || parentEnterprise.Display
select new DistributorEnterpriseModel
{
DistributorStoreId = ds.DistributorStoreId,
DistributorStoreName = ds.Name,
Active = active,
Display = ds.Display && display
};
查询在生产上成功运行,结果与预期一致。我有一个单元测试,它在InMemory数据库中创建了一些实体,但在上面的查询中失败了。单元测试:
[Fact]
public async void OnGetAsync_Displays_All_And_Only_Active_Dsps()
{
SeedDb();
Sut.Enterprise = new() { EnterpriseId = ENTERPRISE_ID };
await Sut.OnGetAsync(Sut.Enterprise.EnterpriseId, null, null, null, null, null, null, null, null, null, null, null, null, null, default);
int cntActiveDspsInDb = Db.DistributorStores.Where(x => x.IsActive).Count();
int cntDspsOnPage = Sut.Distributors.Items.Count;
Assert.Equal(cntActiveDspsInDb, cntDspsOnPage);
}
错误为
Message:
System.InvalidOperationException : Nullable object must have a value.
Stack Trace:
lambda_method2062(Closure , QueryContext , ValueBuffer )
Enumerator.MoveNextHelper()
Enumerator.MoveNextAsync()
Enumerator.MoveNextAsync()
EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
如果我注释掉// || parentEnterprise.IsActive
和// || parentEnterprise.IsActive
测试通过了,但这显然会破坏查询的结果。
显而易见的解决方案是添加一个空检查,如下所示:
let active = enterprise.IsActive || parentEnterprise != null && parentEnterprise.IsActive
let display = enterprise.Display || parentEnterprise != null && parentEnterprise.Display
然后测试成功通过,但生产代码崩溃并出现以下错误:
InvalidOperationException:LINQ表达式的投影映射:EmptyProjectionMember->[EntityProjectionExpression]SELECT TOP(1(d.EnterpriseId,d.DistributorStoreId FROM DistributorStores_Enterprises AS d INNER按原样加入企业d.EnterpriseId==e.EnterpriseId WHERE((d.DistributorStoreId!=NULL(&;(d.DistributorStoreId==d.DistributorStoreId((&;EXISTS(投影映射:SELECT 1 FROM Enterprises AS e WHERE((e.EnterpriseId!=NULL(&;(e.EnterpriseId==e.ParentEnterpriseId((&;(e.EnterpriseId==@__Enterprise_EnterpriseId_0(('无法翻译。以可翻译的形式重写查询,或者通过插入对"AsEnumerable"、"AsAsyncEnumerable("、"ToList"或"ToListAsync"的调用,显式切换到客户端评估。看见https://go.microsoft.com/fwlink/?linkid=2101038了解更多信息。
Q:为什么null检查会使我的产品代码中的LINQ崩溃?它只是检查null,这可能有什么问题?
错误告诉EF无法将模型表达式转换为SQL逻辑
看看这是否会起作用:
let active = enterprise.IsActive || parentEnterprise?.IsActive ?? false
我怀疑它是否会,因为表达式是在运行时计算的,但它值得一试。
如果这不起作用,您还可以创建一个Enterprise对象来表示null(研究null对象模式(。
public static Enterprise NotAnEnterprise = new([some creation logic here to provide defaults for eac property when nullable, such as defaulting IsActive to false]);
然后你会把你的linq改成
let enterprise = ds.DistributorStore_Enterprise.Where(x => x.EnterpriseId == Enterprise.EnterpriseId).SingleOrDefault() ?? Enterprise.NotAnEnterprise
这可以确保您的Enterprise对象不为null,并使您的原始let
语句为null是安全的。
null对象模式可能有助于处理这些情况,但它确实需要一些时间来适应。一个明确的好处是,您只需要在一个地方声明null替换值,而不需要将null检查和替换值分散在各处。Enterprise.Name = "Unknown"; Enterprise.Display = "Nothing To See Here";