【问题标题】:Entity Framework - remove duplicates实体框架 - 删除重复项
【发布时间】:2020-11-22 17:48:00
【问题描述】:

我想使用 Entity Framework 删除重复记录。

这是我尝试过的

var result = _context.History
            .GroupBy(s => new
                    {
                        s.Date,
                        s.EventId
                    })
            .SelectMany(grp => grp.Skip(1)).ToList();

_context.History.RemoveRange(result);
await _context.SaveChangesAsync();

但我得到一个错误

System.InvalidOperationException:“NavigationExpandingExpressionVisitor”处理 LINQ 表达式“grp => grp.Skip(1)”失败。这可能表明 EF Core 中存在错误或限制

我知道这是对 Entity Framework 的重大更改,但我真的不知道如何更新我的代码。

【问题讨论】:

  • 首先不要使用 EF Core。 EF Core 是 ORM,而不是 SQL 替代品。这里没有对象,删除重复项最简单、最有效的方法是使用带有 ROW_NUMBER() 的 CTE,它将返回所有倍数,按您想要的任何排序顺序排列,允许您选择要保留的行
  • 例如 with dups as (select *, row_number() over (partition by date,eventid order by id desc) rn from...) delete dups where rn>1 将删除除最大 id 之外的所有重复项。 CTE 不需要返回所有列,只需要键列就足够了。您可以指定不同的ORDER BY 来选择不同的行来保留
  • @PanagiotisKanavos 这个 CTE 数据库是不可知论的还是只是 SqlServer 特定的? ORM 可能不是 SQL 的替代品,但 LINQ 应该是抽象和与数据库无关的语言集成的查询语言,那为什么不使用它呢? EF Core 因不愿意翻译而违反合同这一事实并不意味着 OP 做错了什么。
  • @IvanStoev 该操作与对象无关。不涉及任何对象。 LINQ 并不打算处理这种情况。 ORM 从来都不是用来报告查询或完全替代 SQL 的。如果你用ANSI standard替换database agnostic,是的,它是ANSI标准,甚至在MySQL 8之后的MySQL中也支持。所有其他主要数据库已经有ROW_NUMBER()
  • @IvanStoev 和 SQLite 在 version 3.25 中添加了窗口函数。此外,OP 试图做的事情在 SQL 中没有意义——该组并不是真正的分组,并且 SQL 中没有 SKIP。这是试图将(有点低效)LINQ-to-Objects 操作应用于数据库,希望 EF Core 能够以某种方式将其转换为 SQL

标签: c# .net entity-framework asp.net-core entity-framework-core


【解决方案1】:

看起来实体框架不知道如何翻译这个Skip LINQ 查询的一部分。此外,它不能翻译这个GroupBy 部分。在 EF Core 3 中它会抛出一个异常让我们知道:)

所以,一个脏但简单的方法是几乎在开头添加AsEnumerable,但是它会获取所有表并在内存中执行操作:

var result = _context.History
            .AsEnumerable()
            .GroupBy(s => new { s.Date, s.EventId })
            .SelectMany(g => g.Skip(1))
            .ToList();

_context.History.RemoveRange(result);
await _context.SaveChangesAsync();

因为在大多数情况下,获取我们可以将第一个请求一分为二的所有内容是不可接受的,因此我们只下载重复的记录。

这个question 的第二个答案可能会有所帮助,我们可以尝试这样的事情:

var keys = _context.History
                .GroupBy(s => new { s.Date, s.EventId })
                .Select(g => new { g.Key, Count = g.Count() })
                .Where(t => t.Count > 1)
                .Select(t => new { t.Key.Date, t.Key.EventId })
                .ToList();

var result = _context.History
    .Where(h => keys.Any(k => k.Date == h.Date && k.EventId == h.EventId))
    .AsEnumerable()
    .GroupBy(s => new { s.Date, s.EventId })
    .SelectMany(g => g.Skip(1))
    .ToList();

_context.History.RemoveRange(result);
await _context.SaveChangesAsync();

【讨论】:

  • 您好,欢迎来到 SO。这是我得到的错误System.InvalidOperationException: Client side GroupBy is not supported.
  • 有趣,我不知道,但看起来在 EF Core 3 中他们添加了一个明确的错误,因为 GroupBy 没有被转换为 SQL,这里的第二个答案非常好:stackoverflow.com/questions/58138556/…最简单的解决方案是将AsEnumerable() 移动到_context.History 之后的右上角。但是,它将从该表中获取所有数据到服务器并在内存中执行所有操作。你的情况可以接受吗?
  • 我已经更新了答案,以便您更容易理解我之前的评论。它可能会有所帮助:)
【解决方案2】:

在这种情况下,您将按两列进行分组:

var duplicate = DB.History.GroupBy(x => new { x.Date, x.EventId})
                         .Where(x => x.Count() > 1)
                         .SelectMany(x => x.ToList());

【讨论】:

    猜你喜欢
    • 2015-01-10
    • 2020-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-24
    相关资源
    最近更新 更多