【问题标题】:Linq: query with three nested levelsLinq:具有三个嵌套级别的查询
【发布时间】:2012-02-21 02:08:28
【问题描述】:

所以我有三张桌子:

CREATE TABLE tblUser
(
    [pkUserID] [int] IDENTITY(1,1) NOT NULL,
    [userName] [varchar](150) NULL,
    [fkCompanyID] [int] NOT NULL
)

CREATE TABLE tblCompany
(
    [pkCompanyID] [int] IDENTITY(1,1) NOT NULL,
    [name] [varchar](255) NULL
)

CREATE TABLE tblSystem
(
    [pkSystemID] [int] IDENTITY(1,1) NOT NULL,
    [systemName] [varchar](150) NULL,
    [fkCompanyID] [int] NULL
)

这些是我的数据传输对象:

public class SystemDTO
{
    public int pkSystemId { get; set; }
    public string Name { get; set; }
    public int? fkCompanyId { get; set; }
}

public class CompanyDTO
{
    public int pkCompanyId { get; set; }
    public string Name { get; set; }
    public IEnumerable<SystemDTO> Systems { get; set; }
}

public class UserDTO
{
    public int pkUserId { get; set; }
    public string Name { get; set; }
    public IEnumerable<CompanyDTO> Companies { get; set; }
}

这是我正在尝试执行的 Linq 查询:

var result= (
        from user in db.tblUsers
        select new UserDTO()
        {
            pkUserId=user.pkUserID,
            Name=user.realName,
            Companies=
                (
                    from company in db.tblCompanies
                    where user.fkCompanyID==company.pkCompanyID
                    select new CompanyDTO()
                    {
                        pkCompanyId=company.pkCompanyID,
                        Name=company.name,
                        Systems=
                        (
                            from system in db.tblSystem
                            where system.fkCompanyId==company.pkCompanyId
                            select new SystemDTO()
                            {
                                pkSystemId=system.pkSystemID,
                                Name=system.systemName,
                                fkCompanyId=system.fkCompanyID
                            }
                        )
                    }
                )
        }
    ).ToList();

这个查询的问题是最内层的查询

from system in db.tblSystem
where system.fkCompanyId==company.pkCompanyId
select new SystemDTO()
{
    pkSystemId=system.pkSystemID,
    Name=system.systemName,
    fkCompanyId=system.fkCompanyID
}

导致 linq 将 sql 转换为每个实体一个选择。我知道我可以跳过选择并循环结果并设置属性。像这样:

var lsSystem= db.tblSystem.Select (s =>new SystemDTO(){pkSystemId=s.pkSystemID,Name=s.systemName,fkCompanyId=s.fkCompanyID}).ToList();
foreach (var user in result)
    {
        foreach (var company in user.Companies)
        {
            company.Systems=lsSystem.Where (a =>a.fkCompanyId==company.pkCompanyId).ToList();
        }   
    }

这将导致 linq 进行两次选择,而不是每个实体一次。所以现在我的问题。有没有其他方法可以做到这一点?我可以用其他方式填充内部集合吗?

任何建议将不胜感激

编辑
一个建议是使用 loadoption。我在系统和公司之间找不到加载选项。但我可以在两者之间包含 loadoption。像这样的公司和用户:

var option=new DataLoadOptions();
option.LoadWith<tblCompany>(a=>a.fkCompanytblUsers);
db.LoadOptions=option;

但这对查询没有影响,它仍然被翻译成许多选择

EDIT2

正如答案 cmets 中所说,加载选项不适用于这种 linq 查询。

【问题讨论】:

  • 谁对这个问题投了反对票。请解释..

标签: c# tsql linq-to-sql c#-4.0


【解决方案1】:

好的,这是一个用于在单个查询中获取所有内容的建议。出于演示目的,我将简化数据模型:

select *
from ParentTable
join ChildLevel1 on ...
join ChildLevel2 on ...

该查询将同时为您提供所有三个树级别。这将非常有效。但数据将是多余的。您需要进行一些客户端处理以使其再次可用:

var parents = from x in queryResults
group x by new { /* all parent columns here */ }) into g
select new Parent()
{
 ParentData = g.Key,
 Children1 = from x in g
             group x by new { /* all ChildLevel1 columns here */ }) into g
             select new Child1()
             {
              Child1Data = g.Key,
              Children2 = ... //repeat
             }
}

您需要通过分组来消除冗余。换句话说:查询已经对数据进行了非规范化,我们需要再次对其进行规范化。

这种方法非常麻烦但速度很快。

【讨论】:

  • 如果我在每个表中有 300-1000 行,在客户端进行 group by:s 是否有效?还是其他建议更有效?
  • 分组发生在客户端,但您的服务器端代码将最大限度地提高效率。这将比进行大量往返更有效。客户端对 1000 个对象进行分组完全没有问题。那真的没什么。
【解决方案2】:

我自己想办法。我能看到的最好方法是这样做(但如果其他人有更好的建议,请再次添加):

var lsSystem= db.tblSystem.Select (s =>new SystemDTO()
                                        {
                                            pkSystemId=s.pkSystemID,
                                            Name=s.systemName,
                                            fkCompanyId=s.fkCompanyID
                                        }
                                ).ToLookup (s =>s.fkCompanyId);

然后像这样在 linq 查询中使用 lsSystem:

var result= (
        from user in db.tblUsers
        select new UserDTO()
        {
            pkUserId=user.pkUserID,
            Name=user.realName,
            Companies=
                (
                    from company in db.tblCompanies
                    where user.fkCompanyID==company.pkCompanyID
                    select new CompanyDTO()
                    {
                        pkCompanyId=company.pkCompanyID,
                        Name=company.name,
                        Systems=lsSystem[company.pkCompanyID]
                    }
                )
        }
    ).ToList();

这将导致两个 select 语句,一个用于系统,一个用于用户到公司

【讨论】:

  • 这是正确的解决方案,即使它有点不方便。 SQL 在返回树时遇到问题。
  • 是的,我想是的。但我想我会把它留到明天。也许有人过来发现了我们没有想到的东西
【解决方案3】:

您是否查看过LoadOptions 以及更具体的LoadWith

这将阻止 Linq2sql 延迟加载并进行预加载。

这里的简单示例: http://davidhayden.com/blog/dave/archive/2007/08/05/LINQToSQLLazyLoadingPropertiesSpecifyingPreFetchWhenNeededPerformance.aspx

【讨论】:

  • 是的,这也是一种方法。但是我目前正在使用的数据库已经很老了,并且缺少目标表之间的关系。一些 loadoption 我发现我用那个更新了问题。
  • 您可以先选择一个平面结果集,将其移动到内存中,例如 List,然后将该列表用作创建 DTO 的输入。这应该会给你一个带有许多外部连接的选择。
  • 你能举个例子吗?
  • @Pleun 我在该查询中看不到任何延迟加载。
  • 这行不通。它只会急切地发送 N 个子查询,而不是懒惰地发送。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-07-01
  • 2017-12-15
  • 1970-01-01
  • 2017-05-11
  • 2018-09-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多