Добрый день.
Если вы собираетесь использовать LINQ в проектах, стоит узнать, как ускорить работу этой технологии.
По пунктам:
Если вы собираетесь использовать LINQ в проектах, стоит узнать, как ускорить работу этой технологии.
По пунктам:
- Отключить ObjectTracking — у нас уже используется.
- Разнести не связанные таблицы по разным датаконтекстам. Сокращение размера датаконтекста позволит уменьшит количество используемой памяти и операций для контроля изменений объектов.
- Использовать CompiledQuery — думаю, прирост будет, только вот какой? Тут можно посмотреть результаты тестирования compiled vs uncompiled запросов на LINQ
В двух словах — прирост есть, в зависимости от частоты похожих запросов от 10% до 70%
Пример компилированного запроса:
Func<NorthwindDataContext, IEnumerable<Category>> func =
CompiledQuery.Compile<NorthwindDataContext, IEnumerable<Category>>
((NorthwindDataContext context) => context.Categories.
Where<Category>(cat => cat.Products.Count > 5));
Далее, можно создать статический класс с набором этих компилированных запросов:
/// <summary>
/// Utility class to store compiled queries
/// </summary>
public static class QueriesUtility
{
/// <summary>
/// Gets the query that returns categories with more than five products.
/// </summary>
/// <value>The query containing categories with more than five products.</value>
public static Func<NorthwindDataContext, IEnumerable<Category>>
GetCategoriesWithMoreThanFiveProducts
{
get
{
Func<NorthwindDataContext, IEnumerable<Category>> func =
CompiledQuery.Compile<NorthwindDataContext, IEnumerable<Category>>
((NorthwindDataContext context) => context.Categories.
Where<Category>(cat => cat.Products.Count > 5));
return func;
}
}
}
Использование же этого класса будет следующим:
using (NorthwindDataContext context = new NorthwindDataContext())
{
var categories = QueriesUtility.GetCategoriesWithMoreThanFiveProducts(context);
}
Кроме того, поддержание многих запросов в одном месте позволит избежать дублирование кода и более легкую его поддержку
- Использовать DataLoadOptions.AssociateWith — смысл в том чтобы не использовать LazyLoading, а грузить связанные таблицы сразу. Но грузить не все данные, а лишь по определенному условию.
using (NorthwindDataContext context = new NorthwindDataContext())
{
DataLoadOptions options = new DataLoadOptions();
options.AssociateWith<Category>(cat=> cat.Products.Where<Product>(prod => !prod.Discontinued));
context.LoadOptions = options;
}
- Использовать Optimistic concurrency — добавить в каждую таблицу поле типа TimeStamp — таким образом сам LINQ будет отвечать за concurrency. Кроме того, используя такой подход, можно передавать entity из одного датаконтекст. Если же приложению Optimistic Concurrency не нужна — ее можно отключить. В свойствах Entity в дизайнере выставить UpdateCheck равным UpdateCheck.Never
- Мониторить запросы, которые генерирует LINQ. Большинство запросов будет генерироваться на лету, поэтому, как большинство генераторов-дизайнеров от MS, LINQ может сгенерировать не совсем оптимальный запрос — подтягивать лишние колонки, к примеру. Логирование делается очень просто — using (NorthwindDataContext context = new NorthwindDataContext())
{
context.Log = Console.Out;
}
У себя на работе мы используем DataContextManager, через который создаем все датаконтексты, поэтому привязать логирование ко всему приложению будет еще проще:
internal static class DataContextManager
{
public static DataContextType Create<DataContextType>(bool readOnlyAccess)
where DataContextType: DataContext, new()
{
DataContextType dc = new DataContextType();
//DebugWriter is a TextWriter that writes to DebugInfo.txt file
dc.Log = Common.Logging.Logger.DebugWriter;
dc.ObjectTrackingEnabled = !readOnlyAccess;
dc.DeferredLoadingEnabled = false;
return dc;
}
}
- Использовать метод Attach только тогда, когда это действительно нужно. Например, не использовать AttachAll для коллекций, а проверять каждый объект из коллекции на изменения и привязывать/не привязывать его.
- Быть более внимательным при работе c контролем изменений объектов. При работе с датаконтекстом в режиме не только чтения простые запросы могут создавать дополнительные затраты ресурсов. Например, очень простой запрос:
using (NorthwindDataContext context = new NorthwindDataContext())
{
var a = from c in context.Categories
select c;
}
Однако этот запрос будет тратить больше ресурсов нежели следующий:
using (NorthwindDataContext context = new NorthwindDataContext())
{
var a = from c in context.Categories
select new Category
{
CategoryID = c.CategoryID,
CategoryName = c.CategoryName,
Description = c.Description
};
}
Почему? Потому что в первом все еще продолжает работать Object Tracking, в то время как во втором LINQ просто отдает вам объекты и забывает о них.
- Получать только нужное количество строк используя Take и Skip методы. Стандартный сценарий для постраничного просмотра:
/// <summary>
/// Gets the products page by page.
/// </summary>
/// <param name=”startingPageIndex”>Index of the starting page.</param>
/// <param name=”pageSize”>Size of the page.</param>
/// <returns>The list of products in the specified page</returns>
private IList<Product> GetProducts(int startingPageIndex, int pageSize)
{
using (NorthwindDataContext context = new NorthwindDataContext())
{
return context.Products
.Skip<Product>(startingPageIndex * pageSize)
.Take<Product>(pageSize)
.ToList<Product>();
}
}
«Преждевременная оптимизация — корень всех зол». Это сказал еще Дональд Кнут.
Поэтому будьте внимательны, особенно с использованием CompiledQuery. Запросы LINQ не компилируются, как Regex. Компиляция запроса LINQ создает объект в памяти, в котором уже есть SQL-запрос и делегат для работы с ним.
В принципе, слова Кнута относятся к любым оптимизациям, поэтому не стоит сломя голову оптимизировать все подряд. Лучший выход — попробовать подход и проверить, приносит ли он реальную пользу.
— позволяет быстро написать код с LINQ и проанализировать его выполнение и результаты