确定通用列表之间的差异

时间:2012-05-24 15:12:49

标签: c# mysql performance linq collections

这个问题可能有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)

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

EDIT2

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

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

EDIT3

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

2 个答案:

答案 0 :(得分:1)

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

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)

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

编辑:我最初的解决方案是根据需要采取的行动将两个列表分开,这就是为什么它如此可怕。虽然我认为你仍然会遇到同样的问题(使用LINQ [枚举]而不是Hashsets [哈希集合]),但是使用单行程可以做同样的事情(不关心采取哪种行动)。

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

我确信它仍然比使用Hashsets慢,但无论如何都要试一试。

编辑:是的,它更快,因为它实际上并没有对集合做任何事情,直到你枚举它(在foreach中或通过将其变为具体的数据类型[IE:List&lt;&gt;,Array]等等])。它仍然需要花费额外的时间来整理哪些要添加/删除,这最终是问题所在。通过分解两个查询,我能够获得相当的速度,但是进入内存世界(通过ToList())使得它比hashset版本慢:

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

老实说,我会在SQL端处理它。在存储过程中,将所有值存储在表变量中,另一列指示添加或删除(基于值是否已存在于表中)。然后你可以通过加入这个表变量来进行批量删除/插入。

编辑:我想通过将完整列表发送到数据库并在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来获取您需要添加的信息。

答案 1 :(得分:0)

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

让我们假设您使用纯SQL执行此操作。

您可能需要两个查询:

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

使用LINQ代码会比解决方案更复杂,更不易读取