.NET
19 December 2008

10 Tips to improve Linq2Sql performance

Добрый день.
Если вы собираетесь использовать LINQ в проектах, стоит узнать, как ускорить работу этой технологии.

По пунктам:
  1. Отключить ObjectTracking — у нас уже используется.
  2. Разнести не связанные таблицы по разным датаконтекстам. Сокращение размера датаконтекста позволит уменьшит количество используемой памяти и операций для контроля изменений объектов.
  3. Использовать 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);
    }


    Кроме того, поддержание многих запросов в одном месте позволит избежать дублирование кода и более легкую его поддержку

  4. Использовать 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;
    }


  5. Использовать Optimistic concurrency — добавить в каждую таблицу поле типа TimeStamp — таким образом сам LINQ будет отвечать за concurrency. Кроме того, используя такой подход, можно передавать entity из одного датаконтекст. Если же приложению Optimistic Concurrency не нужна — ее можно отключить. В свойствах Entity в дизайнере выставить UpdateCheck равным UpdateCheck.Never

  6. Мониторить запросы, которые генерирует 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;
        }
      }


  7. Использовать метод Attach только тогда, когда это действительно нужно. Например, не использовать AttachAll для коллекций, а проверять каждый объект из коллекции на изменения и привязывать/не привязывать его.
  8. Быть более внимательным при работе 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 просто отдает вам объекты и забывает о них.

  9. Получать только нужное количество строк используя 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-запрос и делегат для работы с ним.

    В принципе, слова Кнута относятся к любым оптимизациям, поэтому не стоит сломя голову оптимизировать все подряд. Лучший выход — попробовать подход и проверить, приносит ли он реальную пользу.

  10. — позволяет быстро написать код с LINQ и проанализировать его выполнение и результаты

+40
4.4k 47
Comments 22
Top of the day