EF Core Как правильно записать из CSV в базу данных множество сущностей, связанных друг с другом?
Приветствую всех) Жирным выделю особенно важные для меня моменты, в которых я сомневаюсь.
Проблема в том, что сущностей создается очень много (более 6000), и сначала я завожу в базу все сущности категорий (около 140), а только потом читаю из файла и создаю сущности товаров, у которых имеется свойство int CategoryId.
Из-за того что сразу 6к+ товаров выгружать - переполняется память, разбил все на итерации по 200 строк - запись в базу, 200 строк - запись и т.д. Изначально модели товаров создаются без CategoryId, 200 моделей создалось и я вызываю метод
private async Task SetProductCategoryConnectionsAsync(List<ProductCreationViewModel> models)
{
List<Category> categories = await _categoryService.GetAllCategoriesAsync();
foreach (var product in models)
{
foreach (Category category in categories)
{
if (product.CategoryName.ToLower() == category.Title.ToLower())
{
product.CategoryId = category.Id;
}
}
}
}
Здесь у меня уже есть сомнения, потому как все категории тянуть из базы ради присвоения какой-то одной - не очень. Но с другой стороны, если не так, то при каждом присвоении у меня возникнет потребность стучаться в базу в цикле - что тоже вызывает сомнения. Как быть, ума не приложу...
Конечно, все это работает, за исключением одной проблемы - иногда мой код отрабатывает намного быстрее, чем сущности пишутся в базу и происходит следующее - мои категории еще не успевают записаться, как уже вызывается метод SetProductCategoryConnectionsAsync, приведенный выше и тогда связи не проставляются и, соответственно ошибка Foreign Key.
Что-то мне подсказывает - что я куда-то не в ту сторону пошел со своей реализацией.
Ниже приведу примеры методов всей (почти) цепочки методов от чтения из файла до записи в базу.
Буду весьма признателен за помощь.
P.S. Есть мнение - что CodeFirst метод мне не подходит и неплохо бы переписать проект на DbFirst.
Метод, запускающий сервис выгрузки товаров
public async Task Upload(string filePath)
{
filePath = _environment.ContentRootPath + "wwwroot" + filePath;
try
{
await UploadCategories(filePath);
await UploadColors(filePath);
await UploadMaterials(filePath);
await UploadProducts(filePath);
await UploadImages(filePath);
}
catch (Exception e)
{
Console.WriteLine(e);
Console.WriteLine(string.Join('\n', Logger));
Console.WriteLine($"Количество ошибок: {Logger.Count}");
}
}
Метод, читающий категории из файла
private async Task UploadCategories(string filePath)
{
int iterator = 0;
using (StreamReader reader = new StreamReader(filePath))
{
bool isEnd = false;
// Ignore first line in document
await reader.ReadLineAsync();
while (!isEnd)
{
List<string?> data = new List<string?>();
while (iterator < 200 && !isEnd)
{
string? temp = await reader.ReadLineAsync();
if (temp is not null)
{
data.Add(temp);
iterator++;
}
else
isEnd = true;
}
List<CategoryViewModel> models = await GetCategoryViewModels(data);
iterator = 0;
await _categoryService.UpdateOrCreateCategoriesRangeAsync(models);
}
reader.Close();
}
}
Метод GetCategoryViewModels(data) возвращает нам список вьюмоделей категорий (он работает нормально, по нему у меня вопросов нет, так что его мы опустим).
Метод который записывает в базу новые сущности, а если такие уже есть, то обновляет их, вот к нему у меня вопросы есть - тут я по циклу каждый раз из базы тащу сущность категории и работаю с ней (можно сказать 200 раз подряд), мне кажется что это немного не верно:
public async Task<bool> UpdateOrCreateCategoriesRangeAsync(List<CategoryViewModel> models)
{
List<Category> categories = new List<Category>();
foreach (CategoryViewModel model in models)
{
Category? category =
await _db.Categories.FirstOrDefaultAsync(c => c.Title.ToLower() == model.Title.ToLower());
if (category is null)
{
category = _mapper.Map<Category>(model);
categories.Add(category);
}
else
{
category.Title = model.Title;
category.ParentCategoryId = model.ParentCategoryId;
}
}
_db.Categories.UpdateRange(categories);
await _db.SaveChangesAsync();
return true;
}
Ну а далее, таким же образом создаются товары, которые потом вызывают метод SetProductCategoryConnectionsAsync, который я показывал в самом начале. Покажу еще раз, чтобы вам не крутить вверх:
private async Task SetProductCategoryConnectionsAsync(List<ProductCreationViewModel> models)
{
List<Category> categories = await _categoryService.GetAllCategoriesAsync();
foreach (var product in models)
{
foreach (Category category in categories)
{
if (product.CategoryName.ToLower() == category.Title.ToLower())
{
product.CategoryId = category.Id;
}
}
}
}
Огромное спасибо тому кто дочитал аж до этой строчки. Собственно буду рад любым подсказкам, только скажите в каком направлении двигаться? Как правильно и эффективно выполнять выгрузку объемных CSV файлов в базу данных? Нормально ли в цикле обращаться к базе 200 раз подряд?