0

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

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

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

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

编辑2

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

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

编辑3

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

4

2 回答 2

1

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

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<>、Array 等]) . 仍然需要额外的时间来理清要添加/删除哪些内容,这最终是问题所在。通过分解这两个查询,我能够获得相当的速度,但是将其放入内存世界(通过 ToList())使其比 hashset 版本慢:

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

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

编辑:我想通过将完整列表发送到数据库并在存储过程中处理它来扩展我的意思:

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 数据传递给存储过程,并以类似的方式使用 XQuery 来获取您需要添加的信息。

于 2012-05-24T15:42:24.350 回答
0

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

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

您可能需要两个查询:

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

使用 LINQ 代码会比您的解决方案更复杂且可读性更低

于 2012-05-24T15:41:37.393 回答