我经常发现自己在编写这样的代码:
Dim oRenewalOrder = (From c in dc.Orders _
Where c.CustomerID = iCustomerID _
And c.Type = "RENEWAL").FirstOrDefault()
If oRenewalOrder Is Nothing Then
Throw New Exception("Can't find renewal order for customer " & iCustomerID) ' or assert, if you prefer
End If
' Continue processing...
我更喜欢使用 First() 方法,因为当没有元素时它已经抛出异常。但是这个异常并没有告诉你是什么原因造成的,这使得调试变得更加困难。所以我想写一个 FirstOrException() 扩展方法,我可以这样使用:
Dim oRenewalOrder = (From c in dc.Orders _
Where c.CustomerID = iCustomerID _
And c.Type = "RENEWAL").FirstOrException("Can't find renewal order for customer " & iCustomerID)
' Continue processing...
问题是很难以通用方式编写这样的方法,同时使用 LINQ to Objects并利用 LINQ to SQL 提供的查询优化。我能想到的最好的是:
''' <summary>
''' Returns the first element of a sequence.
''' If the sequence is empty, an InvalidOperationException is thrown with the specified message.
<Extension()> _
Function FirstOrException(Of T)(ieThis As IEnumerable(Of T), sMessage As String) As T
Try
Return ieThis.First()
Catch ex As InvalidOperationException
Throw New InvalidOperationException(sMessage, ex)
End Try
End Function
我还为 IEnumerable(Of T) 编写了另一个等效的扩展方法,以涵盖 LINQ to Objects 的情况。这些工作得很好,但是必须捕获异常并重新抛出它似乎很糟糕。我尝试了一种不同的方法,使用 Take(1) 和 AsEnumerable(),但是当我分析它时,它运行了两个单独的 SELECT TOP 1 语句:
<Extension()> _
Function FirstOrException(Of T)(iqThis As IQueryable(Of T), sMessage As String) As T
Dim aFirst = iqThis.Take(1).AsEnumerable()
If aFirst.Count() <= 0 Then
Throw New InvalidOperationException(sMessage)
Else
Return aFirst(0)
End If
End Function
所以我回到异常处理方法。我不喜欢它,因为当出现不同的问题时,集合底层的任何 LINQ 提供程序都有可能抛出 InvalidOperationException - 不是缺少结果,而是其他问题。这会导致我的代码错误地认为没有结果,而实际上这完全是一个不同的问题。
...
好吧,正如您输入详细问题时经常发生的那样,我想我找到了更好的解决方案,我将在下面的答案中发布。但如果有人发现更好的东西,我会留下这个问题:-)