2012-08-03 55 views
0

我不擅長數據庫和T-sql查詢,所以我對如何在C#中使用Linq做類似的事情有點難以理解。創建聯接Linq查詢來優化將​​兩個列表循環在一起

事情是我有這樣的結構,幾乎相同的關係數據庫表,我必須做一些類型的聯接選擇。

實際上,我得到了複合鍵地址列表。這些實際上是包含幾個int值的類(字節或短或許不相關)。現在我必須通過我的結構搜索這些列表的匹配並在那裏調用一個方法。

這可能是一個簡單的連接(不記得什麼連接做什麼),但我需要一些幫助,因爲我不想這樣便宜,因爲我可以輕鬆逃脫,所以我不需要搜索通過每個地址的每一行。

public class TheLocationThing 
{ 
    int ColumnID; 
    int ColumnGroupId; 
    int RowID; 
} 

public class TheCellThing 
{ 
    TheLocationThing thing; 

    public void MethodINeedToCallIfInList() 
    { 
     //here something happens 
    } 
} 

public class TheRowThing 
{ 
    int RowId; 

    List<TheCellThing> CellsInThisRow; 
} 

public class TableThing 
{ 
    List<TheRowThing> RowsInThisTable; 
} 

所以我有這個tablething類型的行,他們有單元格。注意ColumnGroup事物,它是一個帶有ColumnId的組合鍵,因此同一個columnid可以再次出現,但每個ColumnGroup只能出現一次。

但要記住的是,InnTable中將只有一個GroupColumnId,但給出的列表可能有多個,所以我們可以將它們過濾掉。

public void DoThisThing() 
{ 
    List<TheLocationThing> TheAddressesINeedToFind = GetTheseAddresses(); //actualy is a TheLocationThing[] if that matters 

    var filterList = TheAddressesINeedToFind.Where(a => a.ColumnGroupId == this.CurrentActiveGroup); 

    //Here I have to do the join with this.TableInstance 
} 

現在,我當然只應該循環訪問該行中所有具有相同行ID的地址。

也是管理的東西,因爲IQueryable的東西,這將幫助我在這裏,特別是在最初的過濾器,我應該把它作爲Queryable?

+0

我不確定要了解「JOIN」需要做什麼?首先你應該定義你需要什麼類型的回報?它是一個TheCellThing []? – 2012-08-03 14:14:03

+0

@MarinoŠimić我需要調用TheCellThing上的每個匹配的方法。我想連接會優化查詢通過攔截比賽便宜,然後做一個雙擊foreach循環。如果還有其他方法,我很樂意聽到。 – 2012-08-03 14:32:55

回答

2

我打算給出不同的例子,因爲我不太關注你的,並用它來解釋加入的基本知識,希望能達到你需要學習的東西。我們設想兩個比LocationThing等稍微更有意義的類(它使我失去了)。

public class Language 
{ 
    string Code{get;set;} 
    string EnglishName{get;set;} 
    string NativeName{get;set;} 
} 
public class Document 
{ 
    public int ID{get; private set;}//no public set as it corresponds to an automatically-set column 
    public string LanguageCode{get;set;} 
    public string Title{get;set;} 
    public string Text{get;set;} 
} 

現在,讓我們也想像我們有分別返回所有的語言和文檔的方法GetLanguages()GetDocuments()。有幾種不同的方式可以工作,我會在稍後討論。

一個有用的連接的例子是,如果我們例如希望所有的標題和他們在語言的所有的英文名,在SQL我們將使用:

SELECT documents.title, languages.englishName 
FROM languages JOIN documents 
ON languages.code = documents.languageCode 

或離開在哪裏這樣做不會使列名不明確的表名:

SELECT title, englishName 
FROM languages JOIN documents 
ON code = languageCode 

對於文檔中的每一行,它們中的每一行都會將它們與語言中的對應行進行匹配,並返回組合行的標題和英文名稱(如果沒有匹配語言的文檔, t返回,如果有兩種語言具有相同的代碼 - 在這種情況下應該被db阻止 - 相應的文檔會被提到一次ea CH)。

的LINQ當量是:

from l in GetLanguages() 
    join d in GetDocuments() 
    on l.Code equals d.LanguageCode //note l must come before d 
    select new{d.Title, l.EnglishName} 

這將同樣每個文檔與其相應的語言相匹配,並返回一個IQueryable<T>IEnumerable<T>(取決於來源枚舉/ queryables上),其中TTitle和一個匿名對象EnglishName屬性。

現在,至於這方面的費用。這主要取決於GetLanguages()GetDocuments()的性質。

不管來源如何,這本質上都是搜索這兩種方法的每一個結果的問題 - 這只是操作的本質。然而,這樣做的最有效的方式仍然是根據我們對源數據的瞭解而變化的。我們首先考慮一下Linq2Objects表單。有很多是可以這樣做的方式,但讓我們來想象他們返回List s表示是預先計算:

public List<Document> GetDocuments() 
{ 
    return _precomputedDocs; 
} 
public List<Language> GetLanguages() 
{ 
    return _precomputedLangs; 
} 

讓我們假設的LINQ的join不存在了一會兒,並想象我們如何想寫一些功能等同於上面的代碼。我們可能會得到類似於:

var langLookup = GetLanguages().ToLookup(l => l.Code); 
foreach(var doc in GetDocuments()) 
    foreach(var lang in langLookup[doc.LanguageCode]) 
    yield return new{doc.Title, lang.EnglishName}; 

這是一個合理的一般情況。我們可以走得更遠一步,並減少存儲,因爲我們知道,所有我們最終關心的各語言是英文名稱:

var langLookup = GetLanguages().ToLookup(l => l.Code, l => l.EnglishName); 
foreach(var doc in GetDocuments()) 
    foreach(var englishName in langLookup[doc.LanguageCode]) 
    yield return new{doc.Title, EnglishName = englishName}; 

這是對不亞於我們可以做而不集的專業知識數據。

如果我們確實有特殊的知識,我們可以走得更遠。舉例來說,如果我們知道有每個代碼只使用一種語言,那麼下面會更快:

var langLookup = GetLanguages().ToDictionary(l => l.Code, l => l.EnglishName); 
string englishName; 
foreach(var doc in GetDocuments()) 
    if(langLookup.TryGetValue(doc.LanguageCode, out englishName)) 
    yield return new{doc.Title, EnglishName = englishName}; 

如果我們知道這兩個源都是由語言代碼排序,我們仍然可以走得更遠,並通過它們旋轉兩者同時產生匹配,並且在我們處理完它們之後扔掉語言,因爲我們在枚舉的其餘部分再也不需要它了。

但是,Linq在查看兩個列表時並沒有那些特殊的知識。衆所周知,每一種語言和每一份文件都有相同的代碼。它真的要檢查很多東西才能發現。爲此,它的效率非常高(比上面的例子好一些,由於一些優化)。

讓我們考慮一個Linq2SQL的情況,並且注意實體框架和直接在數據庫上使用Linq的其他方式是可比的。假設所有這些都發生在具有_ctx成員(DataContext)的類的上下文中。那麼,我們的源方法可能是:

public Table<Document> GetDocuments() 
{ 
    return _ctx.GetTable<Document>(); 
} 
public Table<Language> GetLanguages() 
{ 
    return _ctx.GetTable<Languages>(); 
} 

Table<T>一些其他方法一起實現IQueryable<T>。在這裏,不是加入內存中的東西,它會執行以下操作(禁止一些別名)SQL:

SELECT documents.title, languages.englishName 
FROM languages JOIN documents 
ON languages.code = documents.languageCode 

看起來很熟悉嗎?這是我們在開始時提到的相同的SQL。

這件事的第一件好事就是它不會從數據庫中取回任何我們不會使用的東西。

第二件好事,就是數據庫的查詢引擎(將它變成可執行代碼,然後運行)確實瞭解數據的性質。例如,如果我們將Languages表設置爲在code列上具有唯一鍵或約束,則引擎知道不會有兩種語言具有相同的代碼,因此它可以執行與我們上面提到的優化我們使用Dictionary而不是ILookup

三偉大的事情是,如果我們有languages.codedocuments.languageCode指數則查詢引擎將使用這些獲得更快的檢索和匹配,也許讓所有從索引需要沒有擊中表,撥打電話,以哪個表首先命中以避免在第二個測試中不相關的行,等等。

第四件偉大的事情是,RDBMS已經從數十年的研究中受益,如何儘快進行這種檢索,所以我們有一些事情我不知道和不需要了解如何從中受益。

總之,我們希望直接對數據源運行查詢,而不是針對內存中的源。有一些例外情況,特別是某些形式的分組(如果直接點擊數據庫,則某些分組操作可能意味着多次觸發它),如果我們快速連續重複使用相同的結果(在這種情況下,我們會更好爲這些結果命中一次,然後存儲它們)。