确定泛型列表之间的差异

本文关键字:之间 列表 泛型 | 更新日期: 2023-09-27 18:35:10

这个问题

可能有 10 个重复项,但我想知道是否有比我目前这样做更好的方法。这是我用来展示如何确定差异的小示例:

        //let t1 be a representation of the ID's in the database.
        List<int> t1 = new List<int>() { 5, 6, 7, 8 };
        //let t2 be the list of ID's that are in memory.
        //these changes need to be reflected to the database.
        List<int> t2 = new List<int>() { 6, 8, 9, 10 };
        var hash = new HashSet<int>(t1);
        var hash2 = new HashSet<int>(t2);
        //determines which ID's need to be removed from the database
        hash.ExceptWith(t2); 
        //determines which ID's need to be added to the database.
        hash2.ExceptWith(t1);
        //remove contents of hash from database
        //add contents of hash2 to database

想知道我是否可以确定在一个操作中添加和删除的内容,而不是我目前必须做的两个操作。有什么方法可以提高此操作的性能吗?请记住,在实际的数据库情况下,有数十万个ID。

编辑或第二个问题,是否有可以直接在数据库上进行的 LINQ 查询,以便我可以提供新的 ID 列表并让它自动删除/添加自己?(使用 MySQL)

澄清我知道我需要两个SQL查询(或一个存储过程)。问题是我是否可以在一个操作中确定列表中的差异,以及是否可以比这更快地完成。

编辑2

SPFiredrake的此操作似乎比我的哈希集版本更快 - 但是我不知道如何确定要从数据库中添加哪些和删除哪些。有没有办法在操作中包含该信息?

t1.Union(t2).Except(t1.Intersect(t2))

编辑3

没关系,我忘记了这个语句实际上存在延迟执行的问题,尽管万一有人想知道,我通过使用自定义比较器和一个添加的变量来确定它来自哪个列表来解决我之前的问题。

确定泛型列表之间的差异

最终,您将使用一个完整的外部联接(在 LINQ 世界中,它是两个 GroupJoins)。但是,我们只关心在任一表中都没有匹配记录的值。空的右值(左外连接)表示删除,空的左值(右外连接)表示添加。因此,为了使它以这种方式工作,我们只需执行两个左外连接(切换第二种情况的输入以模拟右外连接),将它们连接在一起(可以使用联合,但没有必要,因为无论如何我们都会摆脱任何重复项)。

List<int> t1 = new List<int>() { 5, 6, 7, 8 };
List<int> t2 = new List<int>() { 6, 8, 9, 10 };
var operations = 
    t1.GroupJoin(
        t2, 
        t1i => t1i, 
        t2i => t2i, 
        (t1i, t2join) => new { Id = t1i, Action = !t2join.Any() ? "Remove" : null })
    .Concat(
        t2.GroupJoin(
            t1, 
            t2i => t2i, 
            t1i => t1i, 
            (t2i, t1join) => new { Id = t2i, Action = !t1join.Any() ? "Insert" : null })
    .Where(tr => tr.Action != null)

这将为您提供选择语句。然后,可以将此数据馈送到存储过程中,该过程删除表中已存在的值并添加其余值(或两个列表以对其运行删除和添加)。无论哪种方式,仍然不是最干净的方法,但至少这让你思考。

编辑:我最初的解决方案是根据需要采取的行动将两个列表分开,这就是为什么它如此可怕。使用单行代码也可以完成相同的操作(但是,不关心要执行哪个操作),尽管我认为您仍然会遇到相同的问题(使用 LINQ [枚举] 而不是哈希集 [哈希集合])。

// XOR of sets = (A | B) - (A & B), - being set difference (Except)
t1.Union(t2).Except(t1.Intersect(t2))

我相信它仍然会比使用哈希集慢,但无论如何都要试一试。

编辑:是的,它更快,因为它实际上不会对集合执行任何操作,直到您枚举它(在foreach中或通过将其转换为具体数据类型[IE:List<>,Array等])。仍然需要额外的时间来整理要添加/删除的内容,这最终是问题所在。通过分解这两个查询,我能够获得相当的速度,但是将其放入内存世界(通过 ToList())使其比哈希集版本慢:

t1.Except(t2); // .ToList() slows these down
t2.Except(t1); 

老实说,我会在SQL方面处理它。在存储的 proc 中,将所有值存储在一个表变量中,另一列指示添加或删除(基于表中是否已存在该值)。然后,您可以通过连接回此表变量来执行批量删除/插入。

编辑:以为我会通过将完整列表发送到数据库并在 sproc 中处理它来扩展我的意思:

var toModify = t1.Union(t2).Except(t1.Intersect(t2));
mods = string.Join(",", toModify.ToArray());
// Pass mods (comma separated list) to your sproc.

然后,在存储过程中,您将执行以下操作:

-- @delimitedIDs some unbounded text type, in case you have a LOT of records
-- I use XQuery to build the table (found it's faster than some other methods)
DECLARE @idTable TABLE (ID int, AddRecord bit)
DECLARE @xmlString XML
SET @xmlString = CAST('<NODES><NODE>' + REPLACE(@delimitedIDs, ',', '</NODE><NODE>') + '</NODE></NODES>' as XML)
INSERT INTO @idTable (ID)
SELECT node.value('.','int') 
FROM @xmlString.nodes('//NODE') as xs(node)
UPDATE id
SET AddRecord = CASE WHEN someTable.ID IS NULL THEN 1 ELSE 0 END
FROM @idTable id LEFT OUTER JOIN [SomeTable] someTable on someTable.ID = id.ID
DELETE a
FROM [SomeTable] a JOIN @idTable b ON b.ID = a.ID AND b.AddRecord = 0
INSERT INTO [SomeTable] (ID)
SELECT id FROM @idTable WHERE AddRecord = 1

诚然,这只是插入了一些ID,实际上并没有添加任何其他信息。但是,您仍然可以将 XML 数据传递给 sproc,并以类似的方式使用 XQuery 来获取需要添加的信息。

即使你用Linq版本替换它,你仍然需要两个操作。

假设您正在使用纯SQL执行此操作。

您可能需要两个查询:

  • 一个用于删除记录
  • 另一个用于添加它们

使用 LINQ 代码,它将比您的解决方案更复杂且可读性更差