Выберите () снижение производительности - PullRequest
0 голосов
/ 09 октября 2019

Я работаю над небольшим приложением, написанным на c# .net core, и заполняю один объект в коде, потому что эта информация недоступна в базе данных, код выглядит следующим образом:

public async Task<IEnumerable<ProductDTO>> GetData(Request request)
{
    IQueryable<Product> query = _context.Products;

    var products = await query.ToListAsync();

    // WARNING - THIS SOLUTION LOOKS EXPENCIVE TO ME!

    return MapDataAsDTO(products).Select(c =>
    {
        c.HasBrandStock = products.Any(cc => cc.ParentProductId == c.Id);
        return c;
        });
    }
}

private IEnumerable<ProductDTO> MapDataAsDTO(IEnumerable<Product> products)
{
    return products.Select(p => MapData(p)).ToList();
}

Что меня беспокоит, так это код:

 return MapDataAsDTO(products).Select(c =>
 {
    c.HasBrandStock = data.Any(cc => cc.ParentProductId == c.Id);
    return c;
 });
}

Я тестировал его на 300k строках, и он кажется медленным, мне интересно, есть ли лучшие решения в этой ситуации?

Спасибо, ребята!

Приветствия

1 Ответ

1 голос
/ 10 октября 2019

Прежде всего, этот метод загружает все продуктов, и, как правило, это плохая идея, если вы не гарантируете, что общее количество записей останется разумным, а общий размер этих записей будет разумным,Если система может расти, добавьте поддержку пагинации на стороне сервера. (Страница № и размер страницы, используя Skip & Take) 300 тыс. Продуктов - это не разумное число для загрузки всех данных в одно обращение. В любом случае, вы будете обрабатывать эту кошку медленно, дорого и подвержены ошибкам из-за нагрузки на сервер без подкачки страниц. Один пользователь, делающий запрос на сервер, должен будет выделить сервер БД и загрузить 300 тыс. Строк, передать эти данные по проводам на сервер приложений, который выделит память для этих 300 тыс. Строк, а затем передать эти данные по проводной сети. клиенту, который буквально не нуждается в этих 300k строках сразу. Как вы думаете, что произойдет, когда 10 пользователей попадут на эту страницу? 100? И что происходит, когда он «замедляется», и они начинают нажимать клавишу F5 несколько раз. >:)

Во-вторых, async - это не серебряная пуля. Это не делает запросы быстрее, на самом деле делает их немного медленнее. Что он делает, так это позволяет вашему веб-серверу быть более отзывчивым на другие запросы, пока эти медленные запросы выполняются. По умолчанию для синхронных запросов, запустите их как можно более эффективно, затем для более крупных, которые оправданы, переключите их на асинхронные. MS сделала async чрезвычайно простой в реализации, возможно, слишком простой для использования по умолчанию. Сделайте его простым и синхронным для запуска, а затем измените методы для асинхронизации по мере необходимости.

Из того, что я вижу, вы хотите загрузить все продукты в DTO и для продуктов, которые распознаются как "родительские"по крайней мере, еще одного продукта, вы хотите установить для HasBrandStock их DTO значение True. Таким образом, с учетом идентификаторов продуктов 1 и 2, где родительский идентификатор 2 равен 1, DTO для идентификатора продукта 1 будет иметь значение HasBrandStock True, а для идентификатора продукта 2 будет значение HasBrandStock = False.

Одним из вариантов будет выполнение этой операции. в 2 запросах:

var parentProductIds = _context.Products
    .Where(x => x.ParentProductId != null)
    .Select(x => x.ParentProductId)
    .Distinct()
    .ToList();

var dtos = _context.Products
    .Select(x => new ProductDTO
    {
       ProductId = x.ProductId,
       ProductName = x.ProductName,
       // ...
       HasBrandStock = parentProductIds.Contains(x.ProductId)
    }).ToList();

Я использую здесь руководство Select, потому что я не знаю, что на самом деле делает ваш метод MapAsDto. Я очень рекомендую использовать Automapper и его метод ProjectTo<T>, если вы хотите упростить код отображения. Пользовательские функции сопоставления могут слишком легко скрывать дорогостоящие ошибки, такие как ToList вызовы, когда кто-то сталкивается со сценарием, который EF не может перевести.

Первый запрос получает отдельный список только идентификаторов продукта, которые являются родительским идентификатором по крайней мерееще один продукт. Второй запрос отображает все продукты в DTO, устанавливая HasBrandStock на основе того, присутствует ли каждый продукт в списке parentProductIds или нет.

Этот параметр будет работать, если относительно ограниченное число продуктов будет признано «родительскими». Этот первый список может стать настолько большим, пока он не рискует выбросить слишком много элементов для перевода в предложение IN.

Лучшим вариантом будет посмотреть на ваше отображение. У вас есть ParentProductId, есть ли у сущности продукта связанная коллекция ChildProducts?

public class Product
{
    public int ProductId { get; set; }
    public string ProductName { get; set; }
    // ...
    public virtual Product ParentProduct { get; set; }
    public virtual ICollection<Product> ChildProducts { get; set; } = new List<Product>();
}

public class ProductConfiguration : EntityTypeConfiguration<Product>
{
    public ProductConfiguration()
    {
       HasKey(x => x.ProductId);
       HasOptional(x => x.ParentProduct)
          .WithMany(x => x.ChildProducts)
          .Map(x => x.MapKey("ParentProductId")); 
    }
}

В этом примере сопоставляется ParentProductId без предоставления поля в сущности (рекомендуется). В противном случае, если вы предоставляете ParentProductId, замените вызов .Map(...) на .HasForeignKey(x => x.ParentProductId).

Это предполагает EF6 в соответствии с вашими тегами, если вы используете EF Core, тогда вы используете HasForeignKey("ParentProductId") вместоMap(...) чтобы установить свойство тени для FK без раскрытия свойства. Конфигурация сущности немного отличается от Core.

Это позволяет вашим запросам использовать взаимосвязь между родительскими продуктами и любыми связанными дочерними продуктами. Заполнение DTO может быть выполнено одним запросом:

var dtos = _context.Products
    .Select(x => new ProductDTO
    {
       ProductId = x.ProductId,
       ProductName = x.ProductName,
       // ...
       HasBrandStock = x.ChildProducts.Any()
    }).ToList();

Это использует отношение для заполнения вашего DTO и его флага за один проход. Предостережение заключается в том, что теперь существует циклическая связь между продуктом и самим собой, представленным в сущности. Это означает, что не следует передавать сущности чему-то вроде сериализатора. Это включает в себя избегание добавления сущностей в качестве членов DTO / ViewModels.

...