创建连接Linq查询以优化两个列表的循环

时间:2012-08-03 13:56:42

标签: c# linq join

我不擅长数据库和T-sql查询,所以我对如何使用Linq在C#中做类似事情感到有些困惑。

我的结构与关系数据库表几乎相同,我必须使用它来进行某种连接选择。

实际上我得到了一个复合键地址列表。这些实际上是包含一些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只有一次。

要记住的是,TheTable只有一个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吗?

1 个答案:

答案 0 :(得分: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

对于文档中的每一行,每一行都会将它们与语言中的相应行匹配,并返回组合行的标题和英文名称(如果没有匹配语言的文档,则不会如果有两种语言具有相同的代码,则返回#39;在这种情况下,db应该阻止 - 相应的文档每次都会被提及一次。)

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>(取决于源枚举/可查询),其中T是一个匿名对象{{1 }和Title属性。

现在,至于这个费用。这主要取决于EnglishNameGetLanguages()的性质。

无论来源是什么,这本质上都是搜索这两种方法的每一个结果的问题 - 这只是操作的本质。但是,最有效的方法仍然是根据我们对源数据的了解而变化。我们先考虑一个Linq2Objects表单。有很多方法可以做到这一点,但我们可以想象他们会返回预先计算好的GetDocuments()

List

让我们假装Linq的public List<Document> GetDocuments() { return _precomputedDocs; } public List<Language> GetLanguages() { return _precomputedLangs; } 暂时不存在,并想象我们如何编写与上述代码功能相同的东西。我们可能会得到类似的东西:

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};

如果我们知道两个来源都是按语言代码排序的话,我们可以更进一步,同时旋转它们,产生匹配,并在我们处理它们之后抛弃语言,因为我们和#39;在剩下的枚举中永远不会再需要它。

但是,Linq在查看两个列表时并没有那么特别的知识。尽管如此,它知道每种语言和每个文档都具有相同的代码。它真的需要检查很多东西才能找到答案。为此,它的工作效率非常高(比上面的例子好一点,因为有些优化)。

让我们考虑Linq2SQL案例,并注意实体框架和直接在数据库上使用Linq的其他方式是可比较的。让我们说所有这一切都发生在一个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}; 成员为_ctx的班级的背景下。那么我们的源方法可能是:

DataContext

public Table<Document> GetDocuments() { return _ctx.GetTable<Document>(); } public Table<Language> GetLanguages() { return _ctx.GetTable<Languages>(); } 实现了Table<T>以及其他一些方法。在这里,它不是在内存中加入内容,而是执行以下操作(禁止一些别名)SQL:

IQueryable<T>

看起来很熟悉?它与我们在开始时提到的SQL相同。

关于这一点的第一个好处是,它不会从我们不会使用的数据库中带回任何东西。

第二件好事,是数据库的查询引擎(将其转换为可运行的可执行代码)确实了解数据的性质。例如,如果我们将SELECT documents.title, languages.englishName FROM languages JOIN documents ON languages.code = documents.languageCode 表设置为在Languages列上具有唯一键或约束,则引擎知道不能使用相同代码的两种语言,所以它可以执行我们上面提到的优化,相当于我们使用code而不是Dictionary

第三件好事是,如果我们在ILookuplanguages.code上有索引,那么查询引擎将使用这些索引进行更快速的检索和匹配,也许从索引中获取所有需要而不会点击表,调用哪个表首先命中以避免在第二个表中测试不相关的行,依此类推。

第四个伟大的事情是,RDBMS已经从几十年来如何尽可能快地进行这种检索的研究中受益,因此我们已经发现了一些我不知道和不知道的事情。 #39; t需要知道如何从中受益。

总而言之,我们希望直接针对数据源运行查询,而不是针对内存中的源。有一些例外,特别是某些形式的分组(直接用某些分组操作命中DB可能意味着重复命中),如果我们一次又一次地重复使用相同的结果(在这种情况下)我们最好再针对这些结果点击一次,然后存储它们。