AspNet Core Web API высокий уровень использования памяти с EF Core - PullRequest
0 голосов
/ 11 мая 2019

Я вижу, что использование моей памяти в диагностических инструментах становится слишком узким после того, как я вызываю некоторые конечные точки.

Я попытался выделить проблему в наименьшем фрагменте, который мог, чтобы я мог устранить все другие факторы, иВ итоге я получил следующее:

    [HttpGet("test")]
    public ActionResult Test()
    {
        var results = _context.Products
            .Include(x => x.Images)
            .Include(x => x.Options)
                .ThenInclude(x => x.Lists)
                    .ThenInclude(x => x.PriceChangeRule)
            .Include(x => x.Options)
                .ThenInclude(x => x.Lists)
                    .ThenInclude(x => x.Items)
                        .ThenInclude(x => x.PriceChangeRule)
            .Include(x => x.Options)
                .ThenInclude(x => x.Lists)
                    .ThenInclude(x => x.Items)
                        .ThenInclude(x => x.SupplierFinishingItem)
                            .ThenInclude(x => x.Parent)
            .Include(x => x.Category)
                .ThenInclude(x => x.PriceFormation)
                    .ThenInclude(x => x.Rules)
            .Include(x => x.Supplier)
                .ThenInclude(x => x.PriceFormation)
                    .ThenInclude(x => x.Rules)
            .Include(x => x.PriceFormation)
                .ThenInclude(x => x.Rules)
                .AsNoTracking().ToList();

        return Ok(_mapper.Map<List<AbstractProductListItemDto>>(results));
    }

Это большой запрос с большим количеством включений, но объем данных, возвращаемых из базы данных, невелик, это ~ 10.000 элементов.Когда я сериализую этот результат, он имеет только 3,5 МБ.

Мой API использует около 300 МБ памяти, затем, когда я вызываю эту тестовую конечную точку, это значение достигает 1,2 ГБ.Я думаю, что это слишком много только для 3,5 МБ данных, но я не знаю, как внутренне работает EF Core, поэтому я просто проигнорирую это.

Моя проблема в том, насколько я понимаю,DbContext добавляется как служба с ограниченным доступом, поэтому он создается при запуске запроса, а затем уничтожается при его завершении.Вот как я это регистрирую:

    services.AddDbContext<DatabaseContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection")));

Если мое понимание верно, когда запрос завершен, этот огромный объем памяти должен быть удален, верно?

Проблема в том, что мое использование памяти никогда не возобновляется, я попытался удалить контекст вручную, также вызвав сборщик мусора вручную, но память осталась на уровне 1,2 ГБ.

Я что-то упустилздесь

1 Ответ

2 голосов
/ 11 мая 2019

Потенциальная область, которую я вижу, это то, что вы, возможно, загружаете из базы данных гораздо больше данных, чем требуется для сериализации в AbstractProductListItemDto.Например, у вашего Product могут быть поля, подобные приведенным ниже:

public class Product
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
}

Однако у вашего окончательного DTO может быть только одно или два из этих свойств, например:

public class AbstractProductListItemDto
{
    public string ProductId { get; set; }
    public string ProductName { get; set; }
}

Это также может быть верно для других таблиц, которые вы включаете (Options, Lists, Rules и т. Д.), Особенно для таблиц, которые один-ко-многим , которыеможет легко разобрать количество запрашиваемых строк / столбцов.

Потенциальный способ оптимизировать это состоит в том, чтобы выполнить проекцию самостоятельно как часть запроса LINQ.Это позволит использовать функцию EF Core, в которой он выбирает только те столбцы из базы данных, которую вы указали.Например:

Это позволит выбрать все столбцы из таблицы продуктов

var results = _context.Products.ToList();

Это позволит выбрать только столбцы Id и Name из таблицы продуктов,что приводит к меньшему использованию памяти

var results = _context.Products.Select(x => new ProductDto { 
    Id = x.Id,
    Name = x.Name,
}

Из этого вопроса я не знаю всех свойств всех элементов, которые вы отображаете, так что это будет зависеть от вас, если вы захотите это сделатьэто отображение вручную.Важная часть заключается в том, что вам нужно будет сделать это при вызове Select() до вашего звонка ToList() по вашему запросу.

Однако существует потенциальная возможностьярлык, если вы используете Automapper

Automapper включает в себя ярлык, который пытается написать эти прогнозы для вас.Это может не работать в зависимости от того, сколько дополнительной логики происходит в Automapper, но это может стоить попробовать. Вы хотите прочитать о ProjectTo<>() методе .Если бы вы использовали проекцию, код, вероятно, выглядел бы примерно так:

Редактировать: в комментариях было правильно указано, что вызовы Include () не нужны при использовании ProjectTo<>().Вот более короткий образец с оригиналом, включенным под ним

Обновлено:

using AutoMapper.QueryableExtensions;
// ^^^ Added to your usings
// 

    [HttpGet("test")]
    public ActionResult Test()
    {
        var projection = _context.Products.ProjectTo<AbstractProductListItemDto>(_mapper.ConfigurationProvider);

        return Ok(projection.ToList());
    }

Оригинал:

using AutoMapper.QueryableExtensions;
// ^^^ Added to your usings
// 

    [HttpGet("test")]
    public ActionResult Test()
    {
        var results = _context.Products
            .Include(x => x.Images)
            .Include(x => x.Options)
                .ThenInclude(x => x.Lists)
                    .ThenInclude(x => x.PriceChangeRule)
            .Include(x => x.Options)
                .ThenInclude(x => x.Lists)
                    .ThenInclude(x => x.Items)
                        .ThenInclude(x => x.PriceChangeRule)
            .Include(x => x.Options)
                .ThenInclude(x => x.Lists)
                    .ThenInclude(x => x.Items)
                        .ThenInclude(x => x.SupplierFinishingItem)
                            .ThenInclude(x => x.Parent)
            .Include(x => x.Category)
                .ThenInclude(x => x.PriceFormation)
                    .ThenInclude(x => x.Rules)
            .Include(x => x.Supplier)
                .ThenInclude(x => x.PriceFormation)
                    .ThenInclude(x => x.Rules)
            .Include(x => x.PriceFormation)
                .ThenInclude(x => x.Rules)
                .AsNoTracking(); // Removed call to ToList() to keep it as IQueryable<>

        var projection = results.ProjectTo<AbstractProductListItemDto>(_mapper.ConfigurationProvider);

        return Ok(projection.ToList());
    }
...