除了 Entity Framework 中太大外,性能不佳

Bad performance with except too large in Entity Framework

我必须将某些数据从一个数据库 (O) 迁移到另一个数据库 (D),其中一个问题是数据库的表在字段中具有不同的名称。我试图通过以下方式获取不在 D 数据库中的数据:

var dtOrigin = from o in genEntitCeres.X.AsNoTracking() select o;
var dtDestiny = from d in genEntitAgp.X.AsNoTracking() select d;

var rowsMatch = from tOrigin in dtOrigin.AsEnumerable()
                    join tDestiny in dtDestiny.AsEnumerable()
                        on new { tOrigin.a, tOrigin.b} equals
                        new { tDestiny.a, tDestiny.b}
                    select tOrigin;

var rowsNotMatch = (from tOrigin in dtOrigin.AsEnumerable()
                    where !rowsMatch.Contains(tOrigin)
                    select tOrigin);

当我尝试为每个或 rowsNotMatch.Count() 做一个时,它花费了太长时间...

我的解决方案是使用查询中的 SqlQuery 获取不匹配的行(5 秒以上,取决于数据不匹配的大小)。但我想知道 EF 是否还有其他方法,就像我的代码一样,但不会冻结。

尝试这样的事情:

var rowsNotMatch = from tOrigin in dtOrigin.AsEnumerable()
                   join tDestiny in dtDestiny.AsEnumerable()
                       on new { tOrigin.a, tOrigin.b} equals
                       new { tDestiny.a, tDestiny.b} into gD
                   from d in gD.DefaultIfEmpty()
                   where d == null
                   select tOrigin

这实质上是在 dtOrigin 和 dtDestiny 之间进行了左连接,仅返回 dtOrigin 中那些在 dtDestiny 中没有匹配项的项目。

这看起来像你在 2 个不同的 dbContexts 中打开 2 tables,想要根据每个 table 中的 2 列比较 2 tables,找到原始行table 没有匹配项。我不推荐使用 EF 来做这样的事情。在数据库 /w temp tables 中执行此操作会好得多。通过对每个 table 执行 .AsEnumerable(),您将把那些 table 的全部内容加载到内存中。

如果您必须在代码中做这样的事情:我会考虑这样的事情:

var destinationMatches = genEntitAgp.X.AsNoTracking()
  .Select(x=> new { x.id, x.a, x.b })
  .ToList();

如果目标 table 预计会很大,那么您应该使用 .Skip() 和 .Take() 在 1000 页左右的页面中执行此操作。

下一步是将所有行插入原始数据库中的缓冲区 table。缓冲区 table 保存目标行的 PK,以及要匹配的条件。

public class TempX
{
  public int Id { get; set; }
  public string a { get; set; }
  public string b { get; set; }
}

我建议为此使用一个单独的 DBContext 而不是 Origin DBContext,以在没有更改跟踪的情况下进行初始化,并清除最初在 TempX table 中的所有记录。如果您要插入 > 1000 行,您希望以 1000 个为一组执行此操作,保存更改,然后处理并重新创建每个批次的上下文以保持操作快速。源 dbContext 也需要了解 TempX,只是不适用于总体。

一旦记录在源模式的 tempX table 中:

var entities = genEntitCeres.X.AsNoTracking()
  .Where(x => !genEntitCeres.TempX.AsNoTracking().Any(tx=>tx.a == x.a && tx.b == x.b)).ToList();

同样,如果预计会有很多不匹配项,则不要使用 .ToList(),而是使用 .Skip() 和 .Take() 方法。

如果这可能在给定数据库上 运行 不止一次,那么我也会考虑希望有类似 CreatedAt/ModifiedAt dateTime 值的东西,我可以用它来过滤对于 Origin 和 Destination 数据库。 IE。记录最后一个 运行 DateTime,然后根据 > LastRunDate 从两个查询中过滤行以减少提取和比较的行数。