【问题标题】:Need to speed up writing large object graph to database with Entity Framework需要使用 Entity Framework 加速将大对象图写入数据库
【发布时间】:2019-05-21 03:06:22
【问题描述】:

我在当前项目中使用 EntityFramework Core。在这个项目中,我有一个 API 端点,它接受一个大 (4,000K) 文本文件。端点读取并解析文件并将数据转换为对象图。

然后我需要将整个图形写入 SQL 数据库。解析文本文件后,我在这个对象图中得到了大约 20,000 个对象。

该图通常有一个事务。本次交易有大约 5000 名订阅者,每个订阅者平均有 4 个好处。每个 Dates 集合将有 1 或 2 个 DateRanges。拒绝通常是空的。

我的对象图基本上是这样的:

public class Transaction {
   public int Id {get; set;}
   ...  // Other properties
   public ICollection<Subscriber> Subscribers {get; private set;}
   public ICollection<TranRejection> Rejections {get; private set;}
}

public class Subscriber {
   public int Id {get; set;}
   public int TransactionId {get; set;}  //Foreign Key
   ... // Other properties
   public ICollection<Benefit> Benefits {get; private set;}
   public ICollection<SubscriberRejection> Rejections {get; private set;}
   public ICollection<SubscriberDateRange> Dates {get; private set;}
}

public class Benefit {
   public int Id {get; set;}
   public int SubscriberId {get; set;}  //Foreign Key
   ... // Other properties
   public ICollection<BenefitRejection> Rejections {get; private set;}
   public ICollection<BenefitDateRange> Dates {get; private set;}
}

//This abstract class w/ empty subclasses is done to take advantage of TPH
//so that all dates get stored in a single table
public abstract class DateRange {
   public int Id {get; set;}
   public int ParentId {get; set;}
   public string DateCode {get; set;}
   public DateTime BeginRange {get; set;}
   public DateTime? EndRange {get; set;}
}

public class BenefitDateRange : DateRange {}
public class SubscriberDateRange : DateRange {}

//Rejection class is handled very similar to DateRange

我的 EF 映射看起来像这样。 (仅包括有助于了解关系的重要信息)。

builder.Entity<DateRange>().ToTable("dateranges")
  .HasDiscriminator<string>("rangetype")
  .HasValue<BenefitDateRange>("benefit")
  .HasValue<SubscriberDateRange>("subscriber");
builder.Entity<DateRange>().HasKey(r => r.Id);

builder.Entity<Transaction>().HasMany(t => t.Subscribers).WithOne()
   .HasForeignKey(s => s.TransactionId);

builder.Entity<Subscriber>().HasMany(s => s.Benefits).WithOne()
   .HasForeignKey(b => b.SubscriberId);

builder.Entity<Subscriber>().HasMany(s => s.Dates).WithOne()
   .HasForeignKey(d => d.ParentId);

//Similar mappings for Benefit.Dates
//Rejections are using TPH just like DateRanges

我尝试通过单独保存片段来保存到数据库——即在没有订阅者的情况下保存交易,然后保存每个订阅者等。这至少需要 30 分钟。

然后我切换到像这样一次保存整个图表:

_dbContext.AddRange(transactions);
_dbContext.SaveChanges();

这大约需要 5 分钟。但是,这是 API 调用的一部分,我想加快速度。有没有更快的方法将整个图形保存到数据库中?我不应该为此使用 EF 吗?

【问题讨论】:

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


【解决方案1】:

我们也遇到过类似的问题,但少了一级。最适合我们的解决方案是使用 BulkExtensions 并将每个级别包装在一个 try-catch 块中,并在保存错误时回滚所有更改。

https://github.com/borisdj/EFCore.BulkExtensions

没有外部库的本机选项是关闭 DBContext 上的 AutoDetectChangesEnabled 和 ValidateOnSaveEnabled。但它仍然比使用 BuilExtensions 慢一些。

context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;

我们的用例是始终插入新行而不是更新现有行。所以,我不能说 BulkExtensions 的InsertOrUpdate 方法的性能。但是,值得一试。

【讨论】:

  • BulkExtensions 是否使用 EF 定义的关系来映射对象与其所有子对象,还是需要您重新定义这些关系或手动 BulkInsert 子列表属性等?
  • 据我记得,我必须手动定义一个数据库事务并明确地对每个对象执行 BulkInsert。隐含地维持这种关系还不够聪明。此外,必须将 SetOutputIdentity 属性设置为 true,这可以方便地设置关系。
  • 很遗憾,因为我已经通过 Fluent API 定义了所有关系。必须再次这样做似乎是多余的——尤其是当扩展直接挂在存在关系定义的 DbContext 时。无论如何,谢谢 - 我会进一步调查。
  • 请注意,此库仅支持 SqlServer 数据库。除此之外,它确实使用 EF Core 模型元数据。
  • 如果你定义了它,是的,它是多余的。就我而言,我还没有到那里,所以它成功了。就像 Ivan 指出的那样,它只适用于 SQL Server(或 Azure SQL)
【解决方案2】:

使用 Entity Framework Extensions 的演示版,我能够将 5 分钟的插入时间缩短到大约 5 分钟。 30秒!效果很好——当然,使用该解决方案需要花费 $$。我确实添加了一个 using 子句和一行代码,瞧,它起作用了。

_context.AddRange(history);
//_context.SaveChanges(); <-- Previous Code
_context.BulkSavechanges();  //New Entity Framework Extensions Code

我尝试了 EFCore.BulkExtensions。我无法让它工作。它似乎不喜欢我在 Fluent API 实体映射中创建的转换映射。

builder.Entity<Transaction>()
  .Property(t => t.Receiver)
  .HasColumnName("receiverdata")
  .HasConversion(v => JsonConvert.SerializeObject(v), v => JsonConvert.DeserializeObject<ReceiverEntity>(v));

EFCore.BulkExtensions 声明它们支持转换,所以我不确定这里有什么问题。我在 GitHub 上发布了 issue,所以我们看看是否有办法让它工作。

【讨论】:

  • 尝试使用BulkInsert 而不是BulkSavechanges,因为它不会调用DetectChanges
猜你喜欢
  • 1970-01-01
  • 2020-05-28
  • 2012-04-03
  • 2014-10-09
  • 1970-01-01
  • 2015-04-30
  • 1970-01-01
  • 2011-09-27
  • 1970-01-01
相关资源
最近更新 更多