Я использую Entity framework для управления всеми операциями с базой данных.
На одном конкретном экране моего приложения мне нужно получить данные из многих главных таблиц, которые не связаны друг с другом. В коде С#, если я запрашиваю каждую таблицу отдельно, это занимает много времени и более 10 запросов, которые нельзя объединить.
Есть ли вариант, где я могу объединить их, чтобы сделать один вызов базы данных, но при этом продолжать использовать linq.
В настоящее время я делаю
var master1=await (from p in context.Master1
select p).ToListAsync()
var master2=await (from p in context.Master2
select p).ToListAsync()
var master3=await (from p in context.Master3
select p).ToListAsync()
Что такое альтернатива?
См. WhenAll
: learn.microsoft.com/en-us/dotnet/api/…
var master1 = (from p in context.Master1
select p).ToListAsync()
var master2 = (from p in context.Master2
select p).ToListAsync()
var master3 = (from p in context.Master3
select p).ToListAsync()
await Task.WhenAll(master1, master2, master3);
Это не будет одиночный вызов базы данных, но он позволит им запрашивать данные, не дожидаясь предыдущего оператора.
Код продолжится только после WhenAll, когда все задачи будут завершены.
Первый шаг — очистить поиск данных только от того, что необходимо. Вызов, который запрашивает 10 несвязанных таблиц, звучит как плохой дизайн. Если это случай, когда вы получаете 10-кратные запросы, рассмотрите возможность рефакторинга дизайна, чтобы они запрашивались меньшими партиями асинхронно (т. е. Ajax) по мере загрузки страницы, а не все сразу.
Что касается загрузки данных, используйте проекцию, а не выборку сущностей. Часто, когда я вижу подобный код, первое, что я ищу, — это штрафы за ленивую загрузку. Это были бы случаи, когда я вижу что-то столь же невинное, как:
var viewModel = new SearchViewModel();
var products = await context.Products.ToListAsync();
viewModel.Products = products;
// ... repeat for other lookups...
return View(viewModel);
Это вызывает тревогу.
async
не серебряная пуля производительности.Для пунктов 1 и 2: При загрузке данных для возврата к представлению используйте проекцию, чтобы избежать неожиданностей отложенной загрузки и свести к минимуму объем данных, передаваемых как из БД, так и клиенту. Ленивая загрузка может сделать вызовы сервера смехотворно медленными, когда отдельный запрос отлаживает и выполняется быстро, но вы ждете от многих секунд до даже минут, прежде чем страница вернется. Подключение SQL Profiler к базе данных покажет, что после выполнения метода сотни, если не тысячи дополнительных запросов попадают в базу данных помимо 10 или около того, которые вы первоначально вызвали. Это сериализатор, «касающийся» свойств навигации, и это значительно медленнее, чем если бы вы загружали все данные с нетерпением. Решением этих проблем является проекция:
var viewModel = new SearchViewModel();
var products = await context.Products
.Select(x => new ProductSummaryViewModel
{
ProductId = x.ProductId,
Name = x.Name
}).ToListAsync();
viewModel.Products = products;
// ... repeat for other lookups...
return View(viewModel);
Вы можете упростить это еще больше, используя Automapper и его метод ProjectTo
вместо Select
, который работает с EF IQueryable
. Projection позволяет избежать неожиданностей отложенной загрузки и строит запросы, которые возвращают только те данные, которые вам нужны.
Для пункта № 3: async
не ускоряет операции/вызовы, во всяком случае, делает их немного медленнее. Что он делает, так это делает код вашего сервера более отзывчивым, пока он занят получением более крупных операций. Слишком часто я вижу код, структурированный так, как будто async
— это решение по принципу «все или ничего». У него есть свое применение, но его следует использовать экономно там, где он может принести наибольшую пользу. Асинхронное получение небольших наборов или отдельных записей по идентификаторам не приносит никакой пользы. Если операция выполняется не более, чем, скажем, 250 мс и не будет вызываться с очень высокой частотой на массивном многоядерном микросервисном сервере, я, честно говоря, не стал бы вводить накладные расходы на async
.
В конечном итоге все сводится к тому, чтобы вы запрашивали ровно столько данных, сколько вам абсолютно необходимо, а затем искали дальнейшую оптимизацию или реструктуризацию вызовов (например, Ajax или разбиение вашего пользовательского интерфейса более атомарно, чем все на одном экране), если все еще есть барьер отзывчивости.
Только если вы сможете спроецировать их всех на один и тот же (анонимный) тип. Затем вы можете использовать .Concat. Но почему нельзя объединить более 10 запросов? Если это из-за таймаутов, то 10 запросов за 1 вызов будет еще хуже.