我似乎无法想出正确的相应LINQ to SQL语句来生成以下T-SQL。基本上,我正在尝试返回付款信息与客户的一个地址...如果存在AR地址,则为该地址,如果存在主要地址,则为主要地址,如果不存在则为任何地址。
SELECT < payment and address columns >
FROM Payment AS p
INNER JOIN Customer AS c ON c.CustomerID = p.CustomerID
OUTER APPLY (
SELECT TOP 1 < address columns >
FROM Address AS a
WHERE a.person_id = c.PersonID
ORDER BY CASE WHEN a.BusinessType = 'AR' THEN 0
ELSE 1
END
, a.IsPrimary DESC
END
) AS pa
WHERE p.Posted = 1
我们正在使用仓储模式访问数据库,因此在支付仓储的方法内,我尝试了以下内容:
var q = GetAll()
.Where(p => p.Posted == true)
.SelectMany(p => p.Customer
.Address
.OrderBy(a => a.BusinessType != "AR")
.ThenBy(a => a.Primary != true)
.Take(1)
.DefaultIfEmpty()
.Select(a => new
{
< only the columns I need from p and a >
});
但是当我执行.ToList()
时,在客户没有设置地址的记录上,它会抛出NullReferenceException
(对象引用未设置到对象的实例)。因此,我尝试了以下代码:
var q1 = GetAll().Where(p => p.Posted == true);
var q2 = q11.SelectMany(p => p.Customer
.Address
.OrderBy(a => a.BusinessType != "AR")
.ThenBy(a => a.Primary != true));
var q3 = q1.SelectMany(p => q2.Where(a => a.PersonID == p.Customer.PersonID)
.Take(1)
.DefaultIfEmpty()
.Select(a => new
{
< only the columns I need from p and a >
});
这将返回正确的结果,但是它生成的 T-SQL 将上面的整个 T-SQL 放入了外部应用程序中,然后再次连接到 Payment 和 Customer。这似乎有些低效,我想知道是否可以使其更有效率,因为上面的 T-SQL 在我使用的测试案例中只需 6 毫秒即可返回。
其他信息: 问:我认为问题在于 GetAll() 返回 IEnumerable 而不是 IQueryable...看到 GetAll() 方法会有所帮助。- Gert Arnold 答:实际上,当追溯到底层时,GetAll() 返回 Table System.Data.Linq.GetTable(),而 Table 确实实现了 IQueryable。
但是,如果我没有弄错的话,DefaultIfEmpty() 确实返回 IEnumerable,这就是抛出异常的原因,正如我在第一个 L2S 代码部分中提到的那样。
解决方案更新:
好吧,我知道我可以简单地回到直接加入表并放弃使用导航属性,而在这种情况下,我现在知道应该这样做。现在一切都说得通了。我只是习惯于喜欢使用导航属性,但在这里,最好直接加入表。
第二个 L2S 代码部分生成的 T-SQL 如此低效的原因是因为为了进入 Address 表,必须包含 Payment/Customer 数据。
当我直接加入表时,生成的 T-SQL 虽然不理想,但更接近所需的脚本代码部分。这是因为它不需要包含 Payment/Customer 数据。这时“嗯,傻瓜”灯泡亮了。
感谢所有帮助我找到答案的人!
GetAll()
返回的是IEnumerable
而非IQueryable
,因此无法将查询转换为SQL(这不敏感于null引用)。若能看到GetAll()
方法的代码便会更有帮助。 - Gert Arnold