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 раз подряд?


Ответы (0 шт):