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 и проанализировать его выполнение и результаты

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 22

      +6
      За что минус? ;)
        –1
        непонятное пугает, наверное
        • НЛО прилетело и опубликовало эту надпись здесь
            +2
            Фиг с ним)
            Когда делишься информацией, не важно, сколько минусов тебе поставят. Важно, что она кому-нибудь поможет
              –1
              Заметьте, полезной информацией :)
          –1
          Да, а почему заголовок на английском?
            –1
            Исправте «impove» на «improve» в заголовке пожалуйста.
              –1
              Спасибо
              –1
              Хочу дополнить к пункту 6.Мониторить запросы, которые генерирует LINQ.
              Это очень актуально для запросов с join-ами. Заметил, что если использовать более 1-го join-а и оператор into, в 80% случаев linq генерит неоптимальный код.

              Еще есть удобный и бесплатный инструмет для работы с linq запросами — linqpad (скачать можно здесь www.linqpad.net)
                –1
                Да, linqpad я люблю! )
                Спасибо. Добавлю в статью
                –1
                У меня есть вопрос по 5 пункту:
                как сделать UpdateCheck.Never для всех сущностей по умолчанию?
                  –1
                  Я поищу способ
                    0
                    Я удобного способа не нашел.
                    Да и на форуме MSDN пока ничего не ответили.

                    Думаю, можно DBML-файл открыть в XML-редакторе и просто сделать replace.
                      0
                      В общем я для себя вопрос апдейта решил следующим образом.
                      псевдокод:

                      public void Update(Entity entity)
                      {
                      using(MyDataContext dc = DataContextFactory.CreateInstance())
                      {
                      Entity original = dc.Entities.SingleOrDefault(c => c.Id == entity.Id);
                      ItemHelper.Clone(entity, original);
                      dc.SubmitChanges();
                      }
                      }

                      Фишка тут в методе Clone. Он с помощью рефлексии проходтся по всем полям первого объекта, помеченым атрибутом Column и копирует их значени во второй объект.
                      Для простых апдейтов работает замечательно
                        0
                        Клонирование не совсем эффективная техника.
                        Рефлекция.
                        Я бы советовал просто добавить в каждую таблицу добавить по колонке типа timestamp (MSSQL) и вам не придется с клонированием мучаться.
                          0
                          К сожалению это не всегда возможно.
                          К тому же клонирование для ссылочных типов как раз рекомендуется, дабы случайно не изменить объект
                    0
                    красиво кто-то прошелся =|
                      0
                      AlexS, привет
                      времени с публикации поста прошло немало, но надеюсь Вы эту тему еще не забросили, и сможете помочь в одном вопросе :)

                      Если сформулировать вкратце, то как сделать ObjectTrackingEnabled=false и SubmitChanges одновременно?
                      Т.е. если я извлекаю какие-то данные конструкцией select c, затем в другом приложении меняю эти данные, и читаю в исходном снова — они не меняются, LINQ соханяет их в текущий кэш.
                      Refresh объекта делать не всегда возможно (например, я удалил запись). Создавать каждый раз новый контекст — забить память (dispose не приводит к немедленному освобождению памяти).

                      А как Вы строите многопользовательские приложения?
                        0
                        Вам как раз Refresh и нужен.
                        LINQ To SQL exception with Attach(): Cannot add an entity with a key that is alredy in use

                        Насчет удаленных сущностей, их, подозреваю, нужно обрабатывать отдельно.

                        >А как Вы строите многопользовательские приложения?
                        Давно уже linq2sql не используем, да и занимаюсь теперь другими вещами. Поэтому ответить нечего.

                        Если у вас есть конкретный пример и вопрос, залейте его на StackOverflow, а я могу переслать ссылку на вопрос ребятам, которые помогут.
                          0
                          Лучше поздно, чем никогда.
                        +1
                        9. Take и только потом Skip? А не наоборот ли?

                        (Знаю, статья жутко устарела. Но все же.)

                        return context.Products
                        .Take(pageSize)
                        .Skip(startingPageIndex * pageSize)
                        .ToList();
                          0
                          Действительно, Skip().Take() должно быть. Спасибо

                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                        Самое читаемое