Необходимо ускорить запись большого графа объектов в базу данных с Entity Framework - PullRequest
0 голосов
/ 20 декабря 2018

Я использую EntityFramework Core в моем текущем проекте.В этом проекте у меня есть конечная точка API, которая принимает большой (4000 КБ) текстовый файл.Конечная точка считывает и анализирует файл и преобразует данные в граф объектов.

Затем мне нужно записать весь граф в базу данных SQL.После синтаксического анализа текстового файла у меня получается около 20 000 объектов в этом графе объектов.

График обычно имеет одну транзакцию.Транзакция имеет около 5000 подписчиков, и каждый подписчик имеет в среднем 4 преимущества.Каждая коллекция дат будет иметь 1 или 2 диапазона дат.Отклонения обычно пусты.

Мой граф объектов выглядит примерно так:

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 Mappings выглядят примерно так.(Включая только важные биты, чтобы помочь увидеть отношения).

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 голосов
/ 20 декабря 2018

Используя демонстрационную версию Расширения Entity Framework , мне удалось сократить время вставки до 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, так что посмотрим, есть ли способ заставить это работать или нет.

0 голосов
/ 20 декабря 2018

У нас была похожая проблема, но с одним меньшим уровнем.Лучшим решением для нас было использование BulkExtensions и упаковка каждого уровня в блок try-catch и откат всех изменений, если произошла ошибка сохранения.

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

Собственныйвариант без внешних библиотек состоял в том, чтобы отключить AutoDetectChangesEnabled и ValidateOnSaveEnabled в DBContext.Но это было все же немного медленнее, чем при использовании BuilExtensions.

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

Наш вариант использования заключался в том, чтобы всегда вставлять новые строки, а не обновлять существующие.Так что я не могу говорить о производительности InsertOrUpdate метода BulkExtensions.Но стоит попробовать.

...