【发布时间】:2012-08-11 00:59:03
【问题描述】:
我发现自己经常写这样的代码:
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 —— 不是缺少结果,而是其他问题。这会导致我的代码错误地认为没有结果,而实际上这是完全不同的问题。
...
好吧,正如您输入详细问题时经常发生的那样,我想我找到了更好的解决方案,我将在下面的答案中发布。但如果有人发现更好的东西,我会留下这个问题:-)
【问题讨论】:
标签: .net vb.net linq-to-sql extension-methods linq-to-objects