У меня есть давно работающая программа, которая каждый день записывает несколько тысяч записей в базу данных PostgreSQL, используя шаблон репозитория.
Это выглядит примерно так:
public class Demo {
public IProductRepository productRepository { get; }
static void Main(string[] args) {
productRepository = new DependencyResolver(ConfigureServices).ServiceProvider.GetService<IProductRepository>();
//list of products just for demo purposes
List<Product> productList = getMagicalProductList();
//do the work here
foreach (Product p in productList) {
productRepository.AddProduct(p);
}
}
private void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IProductRepository, ProductRepository>();
}
}
А ProductRepository
выглядит так:
public class ProductRepository : IProductRepository {
public MyDbContext Context { get; }
public ProductRepository(MyDbContext context) {
Context = context;
}
public void AddProduct(Product product) {
Context.products.Add(product);
Context.SaveChanges();
}
}
Теперь, если вы посмотрите на раздел //do the work here
в моем методе Demo.Main()
, вы увидите, что он использует один и тот же контекст для вставки всех объектов Product в список. Это плохо? Если я создам новый контекст для каждой итерации цикла, не будет ли это пустой тратой ресурсов?
Кроме того, допустим, я хочу каждый раз создавать новый контекст, как это будет выглядеть в коде? Шаблон репозитория отвечает за обработку контекста, поэтому похоже, что мне придется что-то там изменить, что позволит ему автоматически создавать и удалять контексты при каждом вызове.
Обновление: в итоге я изменил свой код для обработки данных в пакетах, поэтому теперь он создает новый контекст для каждого пакета и вызывает SaveChanges()
только один раз в конце каждого пакета.
Не вызывайте SaveChanges после добавления каждого продукта. Это неэффективно. Звоните SaveChanges
один раз.
Кроме того, если вы заботитесь о производительности, я бы посоветовал не использовать средство отслеживания изменений EF Core для заполнения базы данных таким объемом данных. Есть расширения, которые могут вставить эти данные за несколько секунд, например linq2db.EntityFrameworkCore
context.BulkCopy(new BulkCopyOptions(), productList);
Если я вызову SaveChanges() после вставки, скажем, 100 000 элементов, не будет ли это использовать много памяти? Кроме того, что, если я хочу быть уверенным, что изменение было зафиксировано в базе данных, потому что это запустит отдельный процесс, чтобы выполнить дополнительную работу с этим недавно вставленным фрагментом данных?
Конечно, после каждого вызова SaveChanges. EF генерирует новый SQL-запрос, совершает двусторонний обмен данными с базой данных, регистрирует объект в средстве отслеживания изменений. Сохраняет копию старых значений. Может быть, что-то еще.
Чем больше элементов вы вставляете, тем больше времени ChangeTracker использует для обнаружения изменений.
Что мне делать, если я хочу, чтобы EF забыл о недавно добавленных элементах? Эти объекты нигде не упоминаются после того, как они добавлены в базу данных, поэтому я думал, что сборщик мусора все равно их подберет.
Если вы вставляете несколько элементов, вызовите SaveChanges один раз. После этого вы должны удалить DataContext, обычно это делается контейнером IoC с областью действия. Затем следует собрать память.
Да, но в этом случае я вставляю несколько тысяч элементов, и каждый элемент необходимо зафиксировать в базе данных, потому что он запускает отдельную задачу, которая выполняет некоторую дальнейшую параллельную обработку вновь вставленных данных.
В чем проблема? Вы переопределили SaveChanges и запускаете какую-то сложную обработку?
Не было никаких проблем. Я изменил свой код, чтобы вызывать SaveChanges() только один раз каждые 100 записей. В моем коде я пропустил часть, где он отправляет сообщение в очередь, сообщая отдельной задаче начать дальнейшую обработку данных, которые она получает из базы данных, но все должно быть в порядке, даже если это происходит каждые 100 записей.
Красиво, так лучше. Еще лучше, вы можете пересоздавать контекст для каждых 100-1000 записей, это упростит обнаружение изменений. Потому что EF запоминает ваши ранее вставленные данные и пытается обнаружить изменения в уже сохраненных данных.
Что ж, создавать каждый раз новый контекст означало бы генерировать тысячи контекстов — пустая трата ресурсов. Что еще более важно, я бы группировал вызов
SaveChanges
(т.е. вызывал бы его один раз каждые 100-1000 записей). Однако в целом шаблон репозитория с EF означает дублирование усилий без уважительной причины (EF уже реализует репозиторий и UoW).