【问题标题】:Can someone explain why these two linq queries return different results?有人可以解释为什么这两个 linq 查询返回不同的结果吗?
【发布时间】:2011-01-17 00:40:07
【问题描述】:

我有两个 linq(到 EF4)查询,它们返回不同的结果。第一个查询包含正确的结果,但没有正确格式化/投影。

第二个查询是我想要的,但它缺少一些数据。

架构

alt text http://img220.imageshack.us/img220/9678/schema.png

查询 1

var xxxx = (from cp in _connectedClientRepository
            .GetConnectedClients(new[] { "LogEntry", "LogEntry.GameFile" })
            .AsExpandable()
            .Where(predicate)
            select cp)
    .ToList();

alt text http://img231.imageshack.us/img231/6541/image2ys.png

注意属性 GameFile 。它是 not 空值。这很棒 :) 注意到 linq 查询了吗?我急切地加载LogEntry,然后急切地加载GameFile(对于每个急切加载的LogEntry)。

这就是我所追求的 -> 对于每个急切加载的 LogEntry,请急切加载 GameFile。但是这个投影结果是错误的……

好的..接下来...

查询 2

var yyy = (from cp in _connectedClientRepository
            .GetConnectedClients(new[] { "LogEntry", "LogEntry.GameFile" })
            .AsExpandable()
            .Where(predicate)
        select cp.LogEntry)
    .ToList();

注意:上面的图片有错字...请注意包含关联输入的代码是正确的(即LogEntry.GameFile),而图片有错字。 p>

现在正确投影 -> 所有LogEntries 结果。但是请注意GameFile 属性现在是空的吗?我不知道为什么 :( 我认为我正确地渴望加载正确的链。所以这是正确的投影,但结果不正确。

强制存储库代码。

public IQueryable<ConnectedClient> GetConnectedClients(
    string[] includeAssociations)
{
    return Context.ConnectedClients
        .IncludeAssociations(includeAssociations)
        .AsQueryable();
}

public static class Extensions
{
    public static IQueryable<T> IncludeAssociation<T>(
        this IQueryable<T> source, string includeAssociation)
    {
        if (!string.IsNullOrEmpty(includeAssociation))
        {
            var objectQuery = source as ObjectQuery<T>;

            if (objectQuery != null)
            {
                return objectQuery.Include(includeAssociation);
            }
        }

        return source;
    }

    public static IQueryable<T> IncludeAssociations<T>(
        this IQueryable<T> source, params string[] includeAssociations)
    {
        if (includeAssociations != null)
        {
            foreach (string association in includeAssociations)
            {
                source = source.IncludeAssociation(association);
            }
        }

        return source;
    }
}

更新

  • 1:修复了代码示例中注意到的一些错字。
  • 2:添加了存储库代码以帮助任何困惑的人。

【问题讨论】:

  • 发送给GetConnectedClients的参数中不需要去掉“LogEntry”吗?
  • 我的猜测:投影改变了结果的形状,所以Include 不再匹配。这行得通吗? ((from cp in _connectedClientRepository.GetConnectedClients().AsExpandable().Where(predicate) select cp.LogEntry) as ObjectQuery).Include("GameFile").ToList(); ?
  • @Lasse V. Karlsen:我试过了,但没有用。我还尝试了 LogEntry.GameFile 和 LogEntry,LogEntry.Game 文件.. 也没有工作。
  • @Craig Stuntz:有趣的一点......我怀疑的一点。我很快就会尝试..但是你有没有链接到有人详细解释这个?大家是否也注意到了这一点?
  • 无非就是Include 的常用文档。重要的是,它适用于ObjectQuery&lt;T&gt;,而T 很重要。这可能会有所帮助:blogs.msdn.com/alexj/archive/2009/06/02/…

标签: c# .net sql linq linq-to-entities


【解决方案1】:

我怀疑 Craig Stuntz 的建议可能有效,但如果无效,以下内容肯定有效:

 var xxxx =_connectedClientRepository
        .GetConnectedClients(new[] { "LogEntry", "LogEntry.GameFile" })
        .AsExpandable()
        .Where(predicate)
        .ToList() // execute query
        .Select(cp => cp.LogEntry); // use linq-to-objects to project the result

【讨论】:

  • 是的。正如克雷格建议的那样,这正是我现在正在做的事情。
  • @Pure.Krome:不一定。第一个 ToLists() 确保执行 EF 查询,并将结果放入 List。 Select() 投影将其转换为 IEnumerable 而不实际迭代列表。如果您只需要迭代一次,则无需添加第二个 ToList()。但除非它是一个庞大的列表,否则我们在这里谈论的是微优化......
  • 您也可以使用 AsEnumerable() 而不是 ToList() 来强制投影到 LINQ to Objects。也就是说,如果您还不想执行 EF 查询。
  • 啊,好的。当我在没有 ToList() 的情况下尝试它时,我得到了一个编译器错误,因为我试图将 .. 作为 IList<..> 返回。明白了。
【解决方案2】:

Include() 处理查询结果,而不是中间查询。您可以在这篇文章中阅读更多关于 Include() 的信息。因此,一种解决方案是将Include() 应用于整个查询,如下所示:

var q = ((from cp in _connectedClientRepository.GetConnectedClients()
                                               .AsExpandable()
                                               .Where(predicate) 
          select cp.LogEntry) 
         as ObjectQuery).Include("GameFile").ToList();

这可能工作,但它很难看。我们能做得更好吗?

我可以想出两种方法来解决这个问题。大多数情况下,这取决于您是否真的需要返回实体类型。如果没有看到您的其余代码,我不能说是否是这种情况。通常,当您要更新(或以其他方式修改)实体类型时,您需要返回它们。如果您选择用于显示或计算目的,返回 POCO 而不是实体类型通常是更好的策略。您可以使用projection 执行此操作,当然它适用于 EF 1。在这种情况下,您可以更改存储库方法以返回 POCO 类型:

 public IQueryable<ClientInfo> GetConnectedClients()
 {
      return from cp in _context.Clients
             where // ...
             select new ClientInfo
             {
                 Id = cp.Id,
                 ClientName = cp.ClientName,
                 LogEntry = new LogEntryInfo
                            {
                                LogEntryId = cp.LogEntry.LogEntryId,
                                GameFile = new GameFileInfo
                                           {
                                               GameFileId = cp.LogEntry.GameFile.GameFileId,
                                               // etc.
                                           },
                                // etc.
                            },
                  // etc.
             };
 }

请注意,当您使用投影时,没有预加载、延迟加载和显式加载。只有您的意图,表示为查询。 LINQ 提供程序会找出您需要什么,即使您在存储库之外进一步编写查询!

另一方面,您可能需要返回实体类型而不是 POCO,因为您打算更新它们。在这种情况下,我会按照 Tomas 的建议为 LogEntries 编写一个单独的存储库方法。但如果我打算更新它们,我只会这样做,并且我可能会将其编写为更新方法,而不是“获取”方法。

【讨论】:

  • @Craig Stuntz :非常感谢您的详细回复。您上面描述的方法类似于我以前对 Linq-To-Sql 所做的方法......但看起来/感觉就像是这样麻烦。尤其是现在 EF4 已经处理了 POCO,如果它们被映射的话。所以在你上面的第一个例子中,这将是一个 POCO,而不是一个实体。我也从不在我的任何代码中使用实体.. bleh。对于更新等,我总是从左到右加载最新的更新,然后保存。但是,是的,这篇文章是最有帮助的。这里的关键是包含需要在整形之后发生。 :)
【解决方案3】:

您似乎还需要一种存储库方法,它会为您完成此操作; _connectedClientsRepository.GetLogEntriesOfConnectedClients().

【讨论】:

  • 嗯。我想我需要恭敬地不同意你的观点。我的存储库方法返回一个 IQueriable,其余的 linq 查询执行过滤器,而不是在特定的存储库方法中硬编码过滤器。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-07-31
  • 2021-01-06
  • 2012-04-19
  • 1970-01-01
  • 2022-01-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多