2014-01-21 73 views
2

使用Linq-to-SQL項目並觀察生成的SQL的一些奇怪行爲。基本上我有一個字符串數組,並且我需要選擇列開始的所有行以及其中一個字符串。LINQ to SQL - 選擇從任何列表開始的位置

using (SqlConnection sqlConn = new SqlConnection(connString)) 
{ 
    using (IdsSqlDataContext context = new IdsSqlDataContext(sqlConn)) 
    { 
     //generated results should start with one of these. 
     //in real code base they are obviously not hardcoded and list is variable length 
     string[] args = new string[] { "abc", "def", "hig" }; 

     IQueryable<string> queryable = null; 

     //loop through the array, the first time through create an iqueryable<>, and subsequent passes union results onto original 
     foreach (string arg in args) 
     { 
      if (queryable == null) 
      { 
       queryable = context.IdsForms.Where(f => f.MatterNumber.StartsWith(arg)).Select(f => f.MatterNumber); 
      } 
      else 
      { 
       queryable = queryable.Union(context.IdsForms.Where(f => f.MatterNumber.StartsWith(arg)).Select(f => f.MatterNumber)); 
      } 
     } 

     //actually execute the query. 
     var result = queryable.ToArray(); 
    } 
} 

我希望生成的sql在功能上等同於以下內容。

select MatterNumber 
from IdsForm 
where MatterNumber like 'abc%' or MatterNumber like 'def%' or MatterNumber like 'hig%' 

但是生成的實際SQL如下,注意'hig%'是所有三個類似子句的參數。

exec sp_executesql N'SELECT [t4].[MatterNumber] 
FROM (
    SELECT [t2].[MatterNumber] 
    FROM (
     SELECT [t0].[MatterNumber] 
     FROM [dbo].[IdsForm] AS [t0] 
     WHERE [t0].[MatterNumber] LIKE @p0 
     UNION 
     SELECT [t1].[MatterNumber] 
     FROM [dbo].[IdsForm] AS [t1] 
     WHERE [t1].[MatterNumber] LIKE @p1 
     ) AS [t2] 
    UNION 
    SELECT [t3].[MatterNumber] 
    FROM [dbo].[IdsForm] AS [t3] 
    WHERE [t3].[MatterNumber] LIKE @p2 
    ) AS [t4]',N'@p0 varchar(4),@p1 varchar(4),@p2 varchar(4)',@p0='hig%',@p1='hig%',@p2='hig%' 

回答

3

看起來你正在關閉循環變量。這是C#中的一個常見問題。當arg的值是運行,而不是創建時,會計算arg的值。

創建一個臨時變量來保存值:

foreach (string arg in args) 
{ 
    var temp = arg; 
    if (queryable == null) 
    { 
     queryable = context.IdsForms.Where(f => f.MatterNumber.StartsWith(temp)).Select(f => f.MatterNumber); 
    } 
    else 
    { 
     queryable = queryable.Union(context.IdsForms.Where(f => f.MatterNumber.StartsWith(temp)).Select(f => f.MatterNumber)); 
    } 
} 

你可以閱讀有關關閉了循環變量this Eric Lippert崗位。正如Eric在文章頂部指出的那樣,正如@Magus在評論中指出的那樣,在C#5中這已經發生了變化,因此foreach變量在每次迭代中都是新副本。像上面那樣創建一個臨時變量,雖然是向前兼容的。

+2

請注意,此行爲在隨後的語言版本中已更改。然而,這樣做是向前兼容的。 – Magus

+0

而不是檢查變量'queryable'是否爲第一次迭代爲空,並複製'Where'子句,我建議你設置一個默認值爲變量:'queryable = Enumerable.Empty ().AsQueryable(); '。然後,您可以刪除條件。 – ZenLulz

0

這個怎麼樣?

queryable = context..IdsForms.Where(f => 
      { 
       foreach (var arg in args) 
       { 
        if (f.MatterNumber.StartsWith(arg)) 
         return true; 
       } 
       return false; 
      }).Select(f => f.MatterNumber); 
1

聯合是正確的,因爲你在你的LINQ中使用聯合到SQL查詢。他們都是hig%的原因是因爲λf => f.MatterNumber.StartsWith(arg)圍繞循環參數創建了一個關閉。要修復,在循環中聲明一個局部變量

foreach (string arg in args) 
    { 
     var _arg = arg; 
     if (queryable == null) 
     { 
      queryable = context.IdsForms.Where(f => f.MatterNumber.StartsWith(_arg)).Select(f => f.MatterNumber); 
     } 
     else 
     { 
      queryable = queryable.Union(context.IdsForms.Where(f => f.MatterNumber.StartsWith(_arg)).Select(f => f.MatterNumber)); 
     } 
    } 

但我同意聯盟似乎沒有必要。如果要檢查的字符串數組不會更改,則可以使用標準where子句。否則,你可以看看謂詞構建器! Check here