【问题标题】:LINQ Outer Join Has DuplicatesLINQ 外部联接有重复项
【发布时间】:2015-02-24 14:32:40
【问题描述】:

我正在 LINQPad 中运行此查询。除了 ProductSeries 有重复记录之外,它可以工作。

var query = from etaRecord in EtaRecord_0140

  join productSeriesRecord in ProductSeries
  on etaRecord.ProductSeriesID equals productSeriesRecord.ProductSeriesID
  into productSeriesGroup
  from productSeries in productSeriesGroup.DefaultIfEmpty()

  where etaRecord.State == "A"
  select new { EtaRecord = etaRecord, ProductSeriesRecord = productSeries };

query.Dump();

我尝试使用FirstOrDefault() 而不是DefaultIfEmpty(),但出现此错误:

“LINQPad.User.ProductSeries”类型的表达式不允许在 具有源类型的查询表达式中的后续 from 子句 'System.Linq.IQueryable'。类型推断失败 对“SelectMany”的调用。

如何获取 ProductSeries 的 FirstOrDefault() 以便每个 EtaRecord 只有一行?

.NET 小提琴在这里:https://dotnetfiddle.net/kRrold

【问题讨论】:

  • 左外连接可以返回重复项,显示你的期望和当前的输出
  • 查询当前不包含重复项,因此无法显示错误输出。问题是数据可能会改变,然后将来会有重复。我需要提防这种情况。我只想要每个 etaRecord 一行。并且该 etaRecord 可以有一个空 ProductSeries,或者只有一个 ProductSeries,即使有多个。
  • 在我的帖子中添加了 .NET fiddle 链接。
  • 您能解释一下您期望示例代码的结果是什么吗?
  • @BobHorn 更新小提琴:dotnetfiddle.net/uIIHi2

标签: c# linq linq-to-sql linqpad


【解决方案1】:

看来您需要分组:

var query = from etaRecord in EtaRecord_0140

join productSeriesRecord in ProductSeries
on etaRecord.ProductSeriesID equals productSeriesRecord.ProductSeriesID
into productSeriesGroup
from productSeries in productSeriesGroup.DefaultIfEmpty()

where etaRecord.State == "A"
group productSeries by new { etaRecord.ProductSeriesId, etaRecord } into g
select new 
       { 
         EtaRecord = g.Key.etaRecord, 
         ProductSeriesRecord = g.Select(x => x).FirstOrDefault() 
        };

UPDATED FIDDLE

【讨论】:

  • 在您的群组行中,我不得不将 productSeriesGroup 更改为 productSeries。现在我留下了这个错误:The name 'productSeriesRecord' does not exist in the current context
  • 在群线:The name 'productSeriesRecord' does not exist in the current context
  • 我认为这已经接近了。只返回了 20 行,这是不对的。只有一条记录为空,另外 19 条记录为产品系列。应该有很多行的产品系列为空。
  • 在最后一行:'System.Linq.IGrouping<AnonymousType#1,AnonymousType#2>' does not contain a definition for 'etaRecord' and no extension method 'etaRecord' accepting a first argument of type productSeries 同样的问题。
【解决方案2】:

你是说你加入的收藏有重复吗?因为如果是这样,您可以提前对 ProductSeries 集合进行分组。

var query = from etaRecord in EtaRecord_0140

join productSeriesRecord in ProductSeries.GroupBy(series => series.ProductSeriesID).Select(seriesGroup => seriesGroup.First())
on etaRecord.ProductSeriesID equals productSeriesRecord.ProductSeriesID
into productSeriesGroup
from productSeries in productSeriesGroup.DefaultIfEmpty()

where etaRecord.State == "A"
select new { EtaRecord = etaRecord, ProductSeriesRecord = etaRecord };

query.Dump();

现在假设您使用的是静态列表,而不是数据库。如果它是数据库连接,那么您可能应该对结果进行不同的处理。事后可以以类似的方式完成。

【讨论】:

  • 这正是我要说的。我刚刚尝试了您的查询,但现在所有行都有一个空 ProductSeriesRecord。
  • 啊,如果删除 DefaultIfEmpty?听起来 DefaultIfEmpty 正在返回该类型的默认值,他的 Nothing/Null 用于引用类型
  • 如果我删除 DefaultIfEmpty(),那么我只会得到具有 ProductSeries 的行。我错过了所有没有的行。
【解决方案3】:

问题是你额外的from 子句:

from productSeries in productSeriesGroup.DefaultIfEmpty() 

你应该放弃它,只使用:

let productSeries = productSeriesGroup.FirstOrDefault()

...或者只是在select 子句中使用productSeriesGroup.FirstOrDefault(),如下所示:

var query = from etaRecord in etaRecords            
            join productSeriesRecord in productSeriesRecords
            on etaRecord.ProductSeriesId equals productSeriesRecord.ProductSeriesId
            into productSeriesGroup
            select new { EtaRecord = etaRecord,
                         ProductSeriesRecord = productSeriesGroup.FirstOrDefault() };

无论是哪一种改变,现在的结果都是:

Snuh 1 - null
Snuh 2 - null
Snuh 3 - null
Snuh 4 - Description A
Snuh 5 - null
Snuh 6 - Description B

我想这就是你想要的。

【讨论】:

  • let原本在查询中,但导致生成了数千条SQL语句。让我试一下你的代码。谢谢。
  • 问题三:产品系列全部为空,运行耗时47秒(我帖子中的查询大概1秒),生成了上千条SQL语句。我想知道只获取重复项然后删除它们是否会更好/更容易?
  • @BobHorn:听起来你的加入可能无法正常工作。 (您的示例很好,使用 LINQ to Objects)。如果只使用简单的内部连接会发生什么?我会先处理正确性,然后再计算效率。请注意,FirstOrDefault() 可能由于缺少排序而导致问题...您可以尝试在 productSeriesRecords 上指定明确的顺序,看看是否有帮助。不过,正如我所说,我会先理清正确性。
  • 我能够通过添加另一个 where 子句来使自己的查询正常工作。您的查询和我的新查询现在返回相同数量的行。不幸的是,我的新查询不是解决这个问题的通用解决方案。由于我现在知道重复项在其中一列中具有特定值,因此我能够添加以下内容:where productSeries == null || productSeries.Laminate == null || productSeries.Laminate != "Unknown".
  • @BobHorn:好吧,我们仍然无法判断为什么我的查询不起作用 - 据我所知,它应该绝对没问题。如果您将select 更改为select new { EtaRecord = etaRecord, Count = productSeriesGroup.Count() },您会得到什么?不幸的是,虽然我们无法重现该问题,但很难提供帮助。
【解决方案4】:

您应该能够通过 EtaRecord 为分组添加额外的过滤步骤,并且只需选择每个组的第一条记录

query  = (from r in query 
         group r by r.EtaRecord.EtaId into results 
         select results.FirstOrDefault());

【讨论】:

    【解决方案5】:

    我会在子查询中这样做:

    var query = from etaRecord in EtaRecord_0140
                where etaRecord.State == "A"
                select new 
                { 
                    EtaRecord = etaRecord,
                    ProductSeriesRecord = 
                      (from productSeriesRecord in ProductSeries
                       where productSeriesRecord.ProductSeriesID == etaRecord.ProductSeriesID
                       select productSeriesRecord).FirstOrDefault()
                };
    

    在 LINQ to objects 中,这可能是一个低效的操作,因为子查询是为每个 etaRecord 执行的,但由于整个语句被转换为 SQL,查询优化器将负责优化的执行计划。

    这就是 LINQ-to-entities 的故事。

    LINQ-to-SQL 总是 似乎产生了 n + 1 组连接查询(即join - into)和FirstOrDefault()。我已经尝试了几种情况,但我不能让它只生成一个查询。我能找到生成一个查询的唯一解决方案是:

    var query = from etaRecord in EtaRecord_0140
                where etaRecord.State == "A"
                from productSeriesRecord in
                         ProductSeries
                            .Where(ps => ps.ProductSeriesID == etaRecord.ProductSeriesID)
                            .Take(1)
                            .DefaultIfEmpt()
                select new { EtaRecord = etaRecord, ProductSeriesRecord = productSeries };
    

    因此放弃了连接语法,并以一种相当人为的方式查询了属于EtaRecordProductSeries 的第一条记录。

    【讨论】:

    • @EhsanSajjad 不确定这是否适用于此。我只是在添加一些关于性能的注释。
    • 这似乎返回了正确的结果,但生成了数千条 SQL 语句,这是我最初试图修复的。执行时间不是 1 秒,而是 40 秒。
    猜你喜欢
    • 2011-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多