我在当前项目中使用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吗?
答案 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,所以我们将看看是否有一种方法可以使其正常工作。