2015-02-24 41 views
4

我在LINQPad中运行此查询。它的工作原理除了ProductSeries有重复的记录。LINQ外部连接有重复

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(),但我得到这个错误:

An expression of type 'LINQPad.User.ProductSeries' is not allowed in a subsequent from clause in a query expression with source type 'System.Linq.IQueryable'. Type inference failed in the call to 'SelectMany'.

我怎样才能获得FirstOrDefault()为ProductSeries以便有只有一排每个EtaRecord?

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

+3

左外连接可以返回重复,展示一下你期待的,什么是当前输出 – 2015-02-24 14:36:24

+0

查询当前不包含重复项,所以我无法显示错误的输出。问题是数据可能会改变,并且将来会有重复。我需要防范这一点。我只需要每个etaRecord一行。那个etaRecord可以有一个空的ProductSeries,或者只有一个ProductSeries,即使有多个。 – 2015-02-24 14:56:28

+0

在我的文章中添加了.NET小提琴链接。 – 2015-02-24 16:38:43

回答

0

你是说,你要加入的集合有重复?因为如果是这样,您可以提前将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(); 

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

+0

这正是我所说的。我只是试过你的查询,但现在所有的行都有一个空的ProductSeriesRecord。 – 2015-02-24 14:47:20

+0

嗯,如果你删除DefaultIfEmpty?听起来像DefaultIfEmpty正在返回类型的默认值,因为它的Nothing/Null为引用类型 – Tim 2015-02-24 14:53:33

+0

如果我删除'DefaultIfEmpty()',那么我只会得到具有ProductSeries的行。我错过了所有没有的行。 – 2015-02-24 14:54:54

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

+0

在您的组群中,我必须将'productSeriesGroup'更改为'productSeries'。现在我剩下这个错误:'名称'productSeriesRecord'在当前上下文中不存在 – 2015-02-24 15:03:01

+0

@BobHorn查看更新 – 2015-02-24 15:05:15

+0

在组行上:'名称'productSeriesRecord'在当前上下文中不存在 – 2015-02-24 15:09:02

0

我会在一个子查询做到这一点:

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对象,这可能是低效率的操作,因为为每个etaRecord执行子查询,但由于整个语句被翻译成SQL,因此查询优化器将处理优化的执行在计划。

这就是LINQ到实体的故事。

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

所以联接语法被放弃,以虚构的方式ProductSeries属于EtaRecord第一条记录进行查询。

+0

连接在性能上我听到的更好 – 2015-02-24 15:07:11

+0

@EhsanSajjad不确定这是否适用于此。我只是添加一些关于性能的笔记。 – 2015-02-24 15:08:59

+0

这似乎会返回正确的结果,但会生成数千个SQL语句,这正是我最初尝试修复的内容。这需要40秒,而不是花1秒时间来执行。 – 2015-02-24 15:12:52

0

的问题是你的额外from条款:

from productSeries in productSeriesGroup.DefaultIfEmpty() 

你应该抛弃这一点,只需使用:

let productSeries = productSeriesGroup.FirstOrDefault() 

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

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 

我想这就是你想要的。

+0

'let'原本是在查询中,但它会导致生成数以千计的SQL语句。让我给你的代码一个镜头。谢谢。 – 2015-02-24 17:34:39

+0

三个问题:所有产品系列都为空,运行需要47秒(我的文章中的查询需要大约一秒),并且生成了数千条SQL语句。我不知道是否会更好/更容易得到重复,然后删除它们? – 2015-02-24 17:41:21

+0

@BobHorn:好吧,听起来你的加入可能无法正常工作。 (对于你的示例,使用LINQ to Objects是很好的)。如果你只是使用简单的内部连接会发生什么?我会先处理正确性,然后再提高效率。请注意,'FirstOrDefault()'可能由于缺少排序而导致问题......您可以尝试在'productSeriesRecords'上指定明确的顺序,并查看它是否有帮助。正如我所说,我首先要弄清楚正确性。 – 2015-02-24 17:43:46

0

您应该能够通过EtaRecord到额外的过滤步骤添加到组,只选择每个组的第一个记录

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