2012-08-11 91 views
2

很多時候我發現自己寫這樣的代碼:如何編寫與LINQ to SQL(和高性能)兼容的FirstOrException()擴展方法?

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到工作對象利用目前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到對象的情況下。這些工作很好,但似乎不好,必須趕上例外並重新拋出它。我嘗試了不同的方法,通過採取(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 - 不是缺少結果,而是另一個問題。這會導致我的代碼錯誤地認爲沒有結果,但實際上完全是另一個問題。

...

嗯,所以經常發生,當你鍵入了一個詳細的問題,我想我找到了一個更好的解決方案,我將它張貼在下面的答案。但我會留下問題,以防萬一有人發現更好的東西:-)

回答

0

我發現Take(1).ToArray()是最乾淨的解決方案。它導致只有一個查詢被髮送到數據庫。請注意,在下面的代碼中,我正在爲IEnumerable和IQueryable分別執行擴展方法,以支持LINQ to SQL和LINQ to Objects。

''' <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 
     Dim aFirst = ieThis.Take(1).ToArray() 
     If aFirst.Length <= 0 Then 
      Throw New InvalidOperationException(sMessage) 
     Else 
      Return aFirst(0) 
     End If 
    End Function 

    ''' <summary> 
    ''' Returns the first element of a queryable (e.g. LINQ to SQL) sequence. 
    ''' If the sequence is empty, an InvalidOperationException is thrown with the specified message. 
    <Extension()> _ 
    Function FirstOrException(Of T)(iqThis As IQueryable(Of T), sMessage As String) As T 
     Dim aFirst = iqThis.Take(1).AsEnumerable() 
     If aFirst.Length <= 0 Then 
      Throw New InvalidOperationException(sMessage) 
     Else 
      Return aFirst(0) 
     End If 
    End Function 
+0

,因爲我還沒有收到超過devundef的,它們雖然快速,簡單的引用類型的集合,將不會爲值類型的集合的工作以外的任何建議,我會接受我自己的答案。 – 2012-08-14 17:29:52

0

您已經做了擴展而沒有更改Linq-to-sql的優化。你至少需要打一次數據庫來檢查是否有某些東西。您不需要使用Take(1),只需撥打FirstOrDefault()並檢查它是否返回Nothing

Function GetFirstOrException(Of T)(sMessage As String, sFirst as T) as T 
    if (T is Nothing) 
     Throw New InvalidOperationException(sMessage) 
    Return T 
End Function 

<Extension()> _ 
Function FirstOrException(Of T)(iqThis As IQueryable(Of T), sMessage As String) As T 
    Return GetFirstOrException(sMessage, idThis.FirstOrDefault()) 
End Function 

<Extension()> _ 
Function FirstOrException(Of T)(iqThis As IEnumerable(Of T), sMessage As String) As T 
    Return GetFirstOrException(sMessage, idThis.FirstOrDefault()) 
End Function 
+0

我沒想過從我的擴展方法中調用輔助方法。這更好。謝謝。 – 2012-08-13 21:31:09

+0

其實我們甚至不需要使用輔助方法,是嗎?我們可以在擴展方法內部測試任何東西......現在我記得爲什麼我沒有這樣做:我想要這些方法適用於值類型以及引用類型。值類型不能爲null/Nothing。例如,在Int32列表中,如果列表中沒有項目,或者第一個項目等於零(Int32的默認值),則不可能通過FirstOrDefault()來判斷。這就是爲什麼我需要Take()。 – 2012-08-13 21:34:34