需要使用 Entity Framework 加快将大对象图写入数据库的速度
Need to speed up writing large object graph to database with Entity Framework
我在当前项目中使用 EntityFramework Core。在这个项目中,我有一个接受大型 (4,000K) 文本文件的 API 端点。端点读取并解析文件并将数据转换为对象图。
然后我需要将整个图表写入 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 吗?
我们遇到了类似的问题,但少了一级。最适合我们的解决方案是使用 BulkExtensions 并将每个级别包装在一个 try-catch 块中,如果保存错误则回滚所有更改。
https://github.com/borisdj/EFCore.BulkExtensions
没有外部库的本机选项是关闭 DBContext 上的 AutoDetectChangesEnabled 和 ValidateOnSaveEnabled。但它仍然比使用 BuilExtensions 慢一点。
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
我们的用例是始终插入新行而不是更新现有行。所以,我不能说 BulkExtensions 的 InsertOrUpdate
方法的性能。但是,值得一试。
使用 Entity Framework Extensions 的演示版本,我能够将 5 分钟的插入时间缩短到大约 10 分钟。 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 所以我们会看看是否有办法让它工作。
我在当前项目中使用 EntityFramework Core。在这个项目中,我有一个接受大型 (4,000K) 文本文件的 API 端点。端点读取并解析文件并将数据转换为对象图。
然后我需要将整个图表写入 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 吗?
我们遇到了类似的问题,但少了一级。最适合我们的解决方案是使用 BulkExtensions 并将每个级别包装在一个 try-catch 块中,如果保存错误则回滚所有更改。
https://github.com/borisdj/EFCore.BulkExtensions
没有外部库的本机选项是关闭 DBContext 上的 AutoDetectChangesEnabled 和 ValidateOnSaveEnabled。但它仍然比使用 BuilExtensions 慢一点。
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
我们的用例是始终插入新行而不是更新现有行。所以,我不能说 BulkExtensions 的 InsertOrUpdate
方法的性能。但是,值得一试。
使用 Entity Framework Extensions 的演示版本,我能够将 5 分钟的插入时间缩短到大约 10 分钟。 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 所以我们会看看是否有办法让它工作。