【问题标题】:Check which messages already exist without loading entire table to memory在不将整个表加载到内存的情况下检查哪些消息已经存在
【发布时间】:2018-08-24 07:58:32
【问题描述】:

我想在将消息添加到数据库之前检查是否已经存在任何消息,但是我当前的查询将整个表加载到内存中。从我的代码生成的查询基本上只是select * from tableName

如何重写此查询以在数据库中进行评估?

public void AddMessages(IEnumerable<Message> messages)
{
    if (messages == null)
        throw new ArgumentNullException(nameof(messages));

    var duplicates = (from currMsg in context.Messages
                      where messages.Any(msg =>
                                                msg.Prop1 == currMsg.Prop1 &&
                                                msg.Prop2 == currMsg.Prop2 &&
                                                msg.Prop3 == currMsg.Prop3)
                      select currMsg);

    var messagesWithoutDuplicates = messages.Except(duplicates);

    context.Messages.AddRange(messagesWithoutDuplicates);
    context.SaveChanges();
}

我也可以在循环中运行它,但是我会创建许多数据库调用而不是 1,我更愿意在一次调用中执行此操作。

【问题讨论】:

  • stackoverflow.com/questions/36514355/…,你需要类似的东西来产生基于||的过滤器。
  • @IvanStoev 谢谢,现在我开始怀疑这是否值得努力。我怀疑这也不容易进行单元测试。
  • 由于 IEnumerable 消息在客户端,我认为没有办法评估服务器上的 .Any() 。如果 context.Messages 是一个巨大的表,执行多个 db 调用并检查每个单独的消息会更快吗?
  • 嗯,基于|| 的过滤器适用于任何 LINQ 方言,因此如果您使用 LINQ to Object 提供程序来模拟可查询对象,则不应该存在单元测试问题。但无论如何,总的来说,您当前的查询很好。当前的 EF Core 查询翻译器未能以某种方式将其翻译为服务器端评估。这是目前 EF Core 最大的 IMO 缺陷。
  • 你不应该被 ORM 阻止做 SQL Server 中可能发生的事情。 MERGE 在您的情况下看起来像 适合该工作的工具

标签: c# entity-framework entity-framework-core


【解决方案1】:

如果您想检查表中有多少行,可以使用 SELECT COUNT(*) FROM TABLE。 在执行任务之前执行此查询。

或者如果您希望在无法插入行时更新(重复) 您需要为此使用合并插入。

合并插入 (MySql) => https://dev.mysql.com/doc/refman/8.0/en/insert-on-duplicate.html

合并插入 (Oracle) => https://docs.oracle.com/cd/B28359_01/server.111/b28286/statements_9016.htm#SQLRF01606

【讨论】:

  • 我不需要行数。我需要在插入之前检查记录是否已经存在。如果您对此有解决方案,请发布示例代码来解释您的解决方案。
  • 在回答你之前,我需要检查我是否理解正确。示例表以索引为键。表有 4 行,每行包含 1 2 3 4。如果您尝试将 3 插入此表中,则会发生 dupkey。并且您希望检查 3 是否已经存在。这样对吗?如果那么您需要使用我在此处发布的合并插入。
【解决方案2】:

根据您的用例,您可能需要一个接一个地插入它们并信任数据库唯一索引(您有一个,对吗?)如果它是重复的,则将其扔回您的脸上。

您的代码有两个弱点除了内存消耗:并发(如果其他人在您检查重复项时插入怎么办)以及您要插入的记录本身可能是您没有重复的事实检查。

【讨论】:

  • 我可以将它包装在事务中并锁定表。如果在循环中添加许多消息,如果插入期间出现连接错误,我可能会得到不完整的结果。
  • 您仍然可以将其包装在事务中。但可以肯定的是,如果你有机会锁定桌子,那也可以。
【解决方案3】:

由于没有简单的方法可以在一次调用中完成此操作,因此我决定牺牲性能并保留可读性和可测试性。这是我的解决方案:

using (var transaction = context.Database.BeginTransaction())
{
    try
    {
        foreach (var message in messages)
        {
            var exists = context.Messages.Any(msg => msg.Prop1 == message.Prop1 &&
                                                     msg.Prop2 == message.Prop2 &&
                                                     msg.Prop3 == message.Prop3 &&);

            if (!exists)
            {
                context.Messages.Add(message);
            }
        }

        context.SaveChanges();
        transaction.Commit();
    }
    catch (Exception ex)
    {
        _logger.Error(ex);
        transaction.Rollback();
        throw;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-19
    • 2011-08-26
    • 2016-06-29
    • 1970-01-01
    • 2012-03-02
    相关资源
    最近更新 更多