在 .NET Core/7 应用程序中,此查询将崩溃,并在
NullReferenceException
上显示
y.Id
record Widget(int Id);
var xWidgets = new[] { new Widget(1), new Widget(2), new Widget(3) };
var yWidgets = Array.Empty<Widget>();
var zWidgets = Array.Empty<Widget>();
(from x in xWidgets
join y_ in yWidgets on x.Id equals y_.Id into y__
from y in y__.DefaultIfEmpty()
join z_ in zWidgets on y.Id equals z_.Id into z__
from z in z__.DefaultIfEmpty()
select y).ToList()
有没有办法避免测试
y
来防止错误?null
我不想仅为单元测试添加特殊代码(EF/SQL 查询不需要测试)。我希望能够使用相同的查询,无论代码是在生产环境中运行(针对 EF/SQL)还是在单元测试环境中运行。
join z_ in zWidgets on y == null ? 0 : y.Id equals z_.Id into z__
来防止错误?
方法是使用不像 LINQ to Objects 那样工作的 LINQ 提供程序。既然这听起来不适合您,那么让我们探索其他可能性。
我希望能够使用相同的查询,无论代码是在生产环境(针对 EF/SQL)还是在单元测试环境中运行...
我想要一个无论源是 SQL 表还是内存集合(如本例所示)都有效的查询。问题在于,仅对内存源需要进行 null 检查 - 对于 SQL,它工作得很好,因为连接被转换为 LEFT OUTER JOIN...
我只是不想用条件检查 null,因为将它们添加到查询中会影响 EF 以一种不需要且可能有害的方式生成的 SQL。如果您将 null 检查移至在 LINQ to Objects 中始终运行的某个位置,而(在生产中)针对数据库运行的查询仍转换为简单的
y
,会怎么样?
null
我试图避免仅仅为了单元测试而修改生产代码。 我绝对理解这种情绪。编写代码的方式应该是一个实现细节,开发人员应该能够在不破坏自动化测试的情况下更改它。您如何知道任何通过的测试在针对数据库执行时不会产生不同的行为?唯一确定的方法是实际测试数据库而不是内存中的集合。
1但我也理解需要平衡这种需求与快速测试的需求。另外,还有一个原则
2
,您应该 听您的测试。如果您在此测试中遇到的困难实际上表明您的代码应该更改为更具可读性并且更可能与各种 LINQ 提供程序兼容的代码,该怎么办?
当我在 LINQ 查询中看到 LEFT OUTER JOIN
时,几乎总是因为有人试图将 SQL 转换为 C#。当有人开始考虑他们的数据模型时,他们通常会得到类似这样的结果:
var queryResults = (from x in xWidgets
select new {
X = x,
Ys =
from y in yWidgets
where y.Id == x.Id
select new {
Zs = zWidgets.Where(z => z.Id == y.Id)
}
}).ToList();
var finalResults =
from result in queryResults
from y in result.Ys.DefaultIfEmpty()
from z in (y?.Zs ?? Array.Empty<Widget>()).DefaultIfEmpty()
select y;
这创建了一个易于推理的可查询模型,并且通常可以更好地转换为您实际希望进行计算或向用户显示的内容。事实上,它避免了尴尬的连接语法,这只是锦上添花。我不知道你的具体情况的细节,但通过采用这样的模型,代码库实际上可能会得到改进。