需要加快使用实体框架将大型对象图写入数据库的过程

时间:2018-12-20 15:09:48

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

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

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

该图通常具有一个事务。该交易约有5000个订户,每个订户平均有4个好处。每个日期集合将具有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吗?

2 个答案:

答案 0 :(得分:2)

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

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

不带外部库的本机选项是关闭DBContext上的AutoDetectChangesEnabled和ValidateOnSaveEnabled。但这仍然比使用BuilExtensions慢一点。

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

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

答案 1 :(得分: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,所以我们将看看是否有一种方法可以使其正常工作。