将内存中列表与数据库列表进行比较的有效方法

Efficient way to compare in-memory list with database list

提问人:Liturghian Pope 提问时间:10/8/2023 最后编辑:marc_sLiturghian Pope 更新时间:10/10/2023 访问量:64

问:

使用 .NET 6 和 Entity Framework,我正在尝试一种有效的方法来查找和标记数据库中记录之间的差异,基于复合键。考虑使用由 ID 和 Year 组成的复合主键的此类。

class Building
{
    public int ID { get; set; }
    public int Year { get; set; }
    public string Name { get; set; }
    public double Size { get; set; }
    public decimal LastSalePrice { get; set; }
}

我在内存中检索了所有年份为 == 2022 的建筑物,现在我需要将列表中的所有建筑物与具有 2021 年份的建筑物进行比较并标记差异。

在我看来,有两种方式:

  1. 需要另一个类来保存标志(这可能不是最好的方法,因为构建列表和更改标志都需要发送到前端,这需要一个额外的类来将所有信息放在一起)
  2. 通过添加 'NameChanged'、'SizeChanged'、'LastSalePriceChanged' 布尔值来标记差异来修改构建类

我的目标是避免检索 2021 年的建筑物列表,然后在内存中执行逐项比较。相反,我正在寻找一种在数据库级别运行的解决方案,以干净地标记差异。例如,如果建筑物在 2022 年存在,但在 2021 年不存在,则所有标志都应设置为“true”。

我对性能和资源使用方面的最有效方法感兴趣,并避免不必要的数据检索。

谢谢!

PS:我尝试检索年份为“2021”的建筑物列表并比较每个建筑物 ID,但它非常冗长,并且是一个“面向业务逻辑”的解决方案。这不是我想要的“干净的、面向数据层的”解决方案。

实体框架 LINQ 优化 体系结构 比较

评论

0赞 Jérôme Richard 10/8/2023
“我正在寻找一种在数据库级别运行的解决方案,以清晰地标记差异”如果您使用 SQL 数据库并且列表很大,AFAIK,则 SQL 引擎无法做到这一点,因此任何能够做到这一点的工具/API 都会效率低下(当然,每个项目执行 1 个请求,这太疯狂了)。检索整个列表,或者至少是其中的一大块肯定要快得多(尽管速度很慢,因为 SQL 数据库根本不是为此类操作而设计的)。如果将列表转换为表,那么 SQL 引擎可能会有效地进行比较(但不确定这比内存中更快)。
0赞 Gert Arnold 10/9/2023
通常,面向数据根本不是“干净的”,如果“干净”意味着更少的代码,那么业务逻辑应该更干净。提取所有字段两次或包含两个嵌套类(每个 ID 包含所有 2021 和 2022 数据)的类中的所有数据,并让该类包含逻辑以将所需标志显示为仅获取属性。这是面向对象的:数据单元(对象),封装了处理数据的逻辑。

答:

0赞 Steve Py 10/9/2023 #1

如果每个年份的 ID 相同,并且复合唯一键是 ID 和年份的组合,则可以使用自联接:

var differingBuildings = await context.Buildings
    .Join(context.Buildings.DefaultIfEmpty(), b1 => b1.Id, b2 => b2.Id,
         (b1, b2) => new { BuildingYear1 = b1, BuildingYear2 = b2 })
    .Where(x => x.BuildingYear1.Year = year1 
        && x.BuildingYear2.Year = year2
        && (x.BuildingYear1.Name != x.BuildingYear2.Name
            || x.BuildingYear1.Size != x.BuildingYear2.Size
            || x.BuildingYear1.LastSalePrice != x.BuildingYear2.LastSalePrice))
    .Select(x => new 
    {
        x.BuildingYear2.Id,
        x.BuildingYear2.Year,
        NameChanged = x.BuildingYear1 == null || x.BuildingYear1.Name != x.BuildingYear2.Name,
        SizeChanged = x.BuildingYear1 == null || x.BuildingYear1.Size != x.BuildingYear2.Size,
        LastSalePriceChanged = x.BuildingYear1 == null || x.BuildingYear1.LastSalePrice != x.BuildingYear2.LastSalePrice
    }).ToListAsync();

这将返回名称、大小或销售价格不同的任何建筑物两年,以及最近(假设第 2 年>第 1 年)年份和值更改的标志。这可能不是一个特别高性能的查询,因此根据数据量,简单地将两年的所有建筑物(或基于 ID 的建筑物集)加载到内存中并在那里进行比较可能会更有效。

如果 Year2 表示您在内存中更新的数据,那么我只需将 Year 1 加载到内存中并进行比较。试图将所有建筑物的数据反馈给 SQL 以实现某种形式的连接(如上所述)是没有意义的。获取上一年的数据并在内存中进行比较。如果您有更新的 ID 子集,则可以使用该集按 ID 获取等效的建筑物。

评论

0赞 NetMage 10/10/2023
对于匹配但不匹配的情况,您不需要正确的连接吗?(我建议使用右连接。year2year1b1 => new { b1.Id, b1.Year }, b2 => new { b2.Id, Year = b2.Year-1 }
0赞 Steve Py 10/10/2023
第 1 年和第 2 年只是被比较的两年,它们通常是相邻的,但不需要。我只是将它们表示为变量,而不是 2021 年和 2022 年。这两年的记录都需要存在才能返回结果,否则就没有什么可比较的了。
1赞 NetMage 10/10/2023
从最初的问题:“例如,如果一栋建筑在 2022 年存在,但在 2021 年不存在,则所有标志都应设置为'真实'。
0赞 Steve Py 10/10/2023
啊,是的,好点子。我已经更新了答案以应用 DefaultIfEmpty,并在标志集中是否找到较早的年份时合并。查询是从内存中写入的,因此可能需要对其进行调整,我通常不会显式编写外部连接。
0赞 NetMage 10/11/2023
我认为这并不能完全解决问题,因为如果没有,例如 匹配的建筑物存在,所以你需要类似的东西?Whereyear1|| x == null