0

我不擅长数据库和 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;
}

所以我有这个表格类型的类,它有行并且它们有单元格。请注意 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 吗?

4

1 回答 1

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

对于文档中的每一行,它们中的每一个都会将它们与语言中的相应行进行匹配,并返回组合行的标题和英文名称(如果存在没有匹配语言的文档,则不会返回,如果有两种语言具有相同的代码 - 但在这种情况下,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是具有TitleEnglishName属性的匿名对象。

现在,至于这方面的费用。这主要取决于GetLanguages()和的性质GetDocuments()

不管来源是什么,这本质上是搜索这两种方法的每一个结果的问题——这就是操作的本质。但是,最有效的方法仍然是根据我们对源数据的了解而有所不同。让我们首先考虑一个 Linq2Objects 表单。有很多方法可以做到这一点,但让我们想象他们正在返回List预先计算好的 s:

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

让我们暂时假设 Linqjoin不存在,并想象我们将如何编写功能上与上面的代码等效的东西。我们可能会得出这样的结论:

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,引擎知道不可能有两种语言具有相同的代码,因此它可以执行我们上面提到的优化的等效项使用 aDictionary而不是 a ILookup

第三件好事是,如果我们有索引languages.codedocuments.languageCode然后查询引擎将使用这些索引进行更快的检索和匹配,也许可以从索引中获取所需的所有内容而无需访问表,调用先访问哪个表避免在第二个中测试不相关的行,依此类推。

第四件好事是,RDBMS 已经从几十年来关于如何尽可能快地进行这种检索的研究中受益,所以我们有一些我不知道也不需要知道的事情从。。得到好处。

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

于 2012-08-03T15:33:43.910 回答