使用 Linq 合并两个列表,并根据需要从不同的表中添加数据

Posted

技术标签:

【中文标题】使用 Linq 合并两个列表,并根据需要从不同的表中添加数据【英文标题】:Combine two list using Linq and add data as needed from different tables 【发布时间】:2021-10-26 12:42:30 【问题描述】:

我需要更改一个流程,并且已经为此苦苦挣扎了几天。 当前任务检查用户在表 1 中输入的所有数字。我没有这个问题,因为我可以用这个语句返回它:

var itemsTable1 = db.Table1.Where(a =>
    searchNumbers.Contains(a.Digit1) || searchNumbers.Contains(a.Digit2) || searchNumbers.Contains(a.Digit3) ||
    searchNumbers.Contains(a.Digit4) || searchNumbers.Contains(a.Digit5) || _Digit6 == a.Digit6 && a.ValidFlag == 1
).ToList();

现在我需要在 Table2 上查找相同的数字,并确保我也带上了这些数字。尽管这些表将具有相同的数字列,但它们的总列数不会相同。我可以对 Table2 进行上述的另一个陈述,那里没有问题。但是,我还需要携带不包含数字但具有相同 ID 的记录。所以,我的场景是这样的:

Table1 = 包含数字 -> Table2 != 包含数字 Table2 = 包含数字 -> Table1 != 包含数字 Table1 = 包含数字 -> Table2 = 包含数字

最后,我需要按降序显示任一列表上的数据,我假设我必须合并两个/三个列表并将其返回给模型。

有没有办法用普通的 Linq 做到这一点?还是我最好在存储过程中创建一个 CTE 并在那里传递参数然后在 EF 中调用?

【问题讨论】:

【参考方案1】:

我假设你需要这个查询:

var query = 
    from t1 in db.Table1
    join t2 in db.Table2 on t1.Id equals t1.Id
    let t1Contains = searchNumbers.Contains(t1.Digit1) 
                || searchNumbers.Contains(t1.Digit2) 
                || searchNumbers.Contains(t1.Digit3) 
                || searchNumbers.Contains(t1.Digit4) 
                || searchNumbers.Contains(t1.Digit5) 
                || _Digit6 == t1.Digit6 && t1.ValidFlag == 1
    let t2Contains = searchNumbers.Contains(t2.Digit1) 
                || searchNumbers.Contains(t2.Digit2) 
                || searchNumbers.Contains(t2.Digit3) 
                || searchNumbers.Contains(t2.Digit4) 
                || searchNumbers.Contains(t2.Digit5) 
                || _Digit6 == t2.Digit6 && t2.ValidFlag == 1
    where t1Contains != t2Contains || t1Contains && t2Contains
    select 
    
        t1,
        t2
    ;

请注意,您没有指定所需的输出以及如何订购结果。

【讨论】:

我认为你在这里是正确的。但是,这现在引入了一个新错误:“无法将 lambda 表达式分配给范围变量”,我认为这是因为为 searchNumbers 传递了数组。关于如何解决这个问题的任何想法? 糟糕,抱歉删除了 lambda。 没问题。谢谢你。查询现在返回两个表上的内容,而不是其中一个表中的内容。 Table1 有数千条记录,而 Table2 则只有几条记录。 那么就需要Concat了。最好指定两个表中需要哪些字段。【参考方案2】:

遵循@Svyatoslav Danyliv 的建议。我创建了以下内容:

 //By using the list, we make sure that the search returns every single digit, regardless of position they occupy in the DB
                        var itemsT1 = db.Table1.Where(a => searchNumbers.Contains(a.Digit1) || searchNumbers.Contains(a.Digit2) || searchNumbers.Contains(a.Digit3) ||
                                           searchNumbers.Contains(a.Digit4) || searchNumbers.Contains(a.Digit5) || _Digit6 == a.Digit6 && a.ValidDrawResults == 1);
                               

                        var itemsT2 = db.Table2.Where(a => searchNumbers.Contains(a.Digit1) || searchNumbers.Contains(a.Digit2) || searchNumbers.Contains(a.Digit3) ||
                                           searchNumbers.Contains(a.Digit4) || searchNumbers.Contains(a.Digit5) || _Digit6 == a.Digit6 && a.ValidDrawResults == 1);                                   

                        //Create list to hold Ids from the records above
                        List<int?> t1Ids = new List<int?>();
                        List<int?> t2Ids = new List<int?>();

                        //Insert the Ids into the lists
                        foreach (var t1Id in t1Ids )
                        
                            t1Ids.Add((int)t1Id.Id);
                        

                        foreach (var t2Id in t2Ids)
                        
                            t2Ids.Add((int)t2Id.Id);
                        

                        //Get the records from opposite table that contains same Ids
                        var resultT1 = db.Table1.Where(r => t1Ids.Contains(r.Id)
                                                        );

                        var resultT2 = db.Table2.Where(r => t2Ids.Contains(r.Id)
                                                        );

                        //Combine the lists to pass to the view
                        var groupedT1 = itemsT1.Concat(resultT1).Distinct();
                        var groupedT2 = itemsT2.Concat(resultT2).Distinct();

                        using (db)
                        
                            var vmT1T2 = new ViewModelTables
                            
                                getTable1 = groupedT2.ToList(),
                                getTable2 = groupedT2.ToList()
                            ;
                            return View(vmT1T2);
                        

就带来我需要的记录而言,它的效果非常好。 再次感谢@Svyatoslav Danyliv 为我指明了正确的方向。我很感激并希望这对其他人也有帮助。

【讨论】:

嗯,它可以更好;) EF 不支持 FULL OUTER JOIN,这在您的情况下是必需的,但可以模拟。 当然。但是,这确实最终完成了工作。只要它不会在工作流程中增加太多,从性能上讲,我不介意稍微绕开马车。再次感谢。 您可以通过两次 LEFT JOIN 来做到这一点,并且不收集密钥。当 ID 过多时,包含将失败。 当您的解决方案有效时,您可能希望避免不必要地调用.ToList()。每次执行此操作时,都会枚举整个列表并将其内容复制到新的List&lt;T&gt;。这可能很昂贵,并且每个List&lt;T&gt; 都存储在堆上,增加了内存压力,导致不必要的垃圾回收。仅在绝对需要时转换为List&lt;T&gt;。在您的解决方案中,我至少数了其中的 10 个;如果您不打算在序列中添加或删除项目,IEnumerable&lt;T&gt; 就足够了。 @MikeHofer,感谢您的提示,确实有道理。我很感激并修复了上面的代码以反映您的建议。

以上是关于使用 Linq 合并两个列表,并根据需要从不同的表中添加数据的主要内容,如果未能解决你的问题,请参考以下文章

查询 - 数据集中的全外连接2个不同的表 - LINQ C#

使用 LINQ 获取两个比较列表的结果并根据结果更改列表中的属性

使用 Linq 将数据插入到两个不同的表中

python合并多个EXCEL表

左连接两个列表并使用 Linq 从右侧维护一个属性

如何从两个不同的、不相关的表中获取最新的行,并将它们合并到一个结果集中?