none
Ускорение работы WPF и базы RRS feed

  • Вопрос

  • Доброго дня всем/ Проблема такая.

    У меня есть WPF приложение с базой SQLExpress (в базе около 6000) строк

    Данные из базы извелкаю в ListBox так

          lstB.Items.Clear();
          using (tSSEntities context = new tSSEntities())
          {
            var query = from pr in context.pGst
                  where pr.SortID1 == 7 && pr.SortID2 == 2
                  select pr;
            foreach (var result in query)
            {
              lstB.Items.Add(result.nameP);
            }
    

    Как бы всё работает, но при первом нажатии на кнопку думает долго. При втором и последующем обращении к базе быстро всё подгружет. Что сделать можно?

    21 августа 2011 г. 3:26

Ответы

  • > только раз не знаю про потоки, то выполняю это при загрузке окна. Но тогда видимо увеличивается время загрузки между Splashscreen и самим окном?

    ниже пример "холодного старта", который выполняется в отдельном потоке

     

    using System;
    using System.Data.SqlClient;
    using System.Diagnostics;
    using System.Windows;
    using System.Threading;
    
    namespace WpfApplication9
    {
      public partial class App : Application
      {
        public App()
        {
          ThreadPool.QueueUserWorkItem(state => Test("cold start", "select count(*) from INFORMATION_SCHEMA.COLUMNS"));
        }
        public static void Test(string notes, string sql)
        {
          var s = Stopwatch.StartNew();
          using (var sc = new SqlConnection(@"Data Source=HOME\SQLEXPRESS;Initial Catalog=Tfs_DefaultCollection;Integrated Security=True;Pooling=False;Connect Timeout=30"))
          {
            sc.Open();
            var cmd = sc.CreateCommand();
            cmd.CommandText = sql;
            var res = cmd.ExecuteScalar();
            Trace.WriteLine(notes + " timing=" + s.ElapsedTicks);
          }
        }
      }
      public partial class MainWindow : Window
      {
        public MainWindow() { InitializeComponent(); }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
          App.Test("default", "select count(*) from INFORMATION_SCHEMA.COLUMNS");
        }
      }
    }
    
    
    <Window x:Class="WpfApplication9.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
      <Grid>
        <Button Click="Button_Click" Content="select" HorizontalAlignment="Center" VerticalAlignment="Center"></Button>
      </Grid>
    </Window>
    
    
    примерный результат: (видно, что первый запрос выполняется намного дольше остальных)

    cold start timing=211790    // выполняется в отдельном потоке во время загрузки приложения.
    default timing=23591
    default timing=23354
    ...

    P.S.
    вместо вызова ThreadPool.QueueUserWorkItem (используется поток из пула), можно создать новый фоновый поток:

    var t = new Thread(() => Test("cold start", "select count(*) from INFORMATION_SCHEMA.COLUMNS"));
    t.IsBackground = true;
    t.Start();
    

    • Предложено в качестве ответа Malobukv 22 августа 2011 г. 17:59
    • Помечено в качестве ответа developers_s 23 августа 2011 г. 11:05
    22 августа 2011 г. 17:53
  • Я могу ошибаться (сам чаще использую лямбда-методы, а не linq), но реальные запросы к базе в вашем коде  выполняются именно в цикле foreach. Это отложенное выполнение запроса, lazy loading.

    В вашем случае к базе будет выполнено столько запросов, сколько строк будет в результате. Чтобы этого избежать, после select вызовите ToList() и потом уже выполняйте перечисление. Это называется материализацией - все нужные строки загрузятся за один запрос к базе.

    Избегайте использования lazy loading там, где оно не нужно, это неоправданная нагрузка на БД.

    • Помечено в качестве ответа Abolmasov Dmitry 6 сентября 2011 г. 17:50
    22 августа 2011 г. 18:10
  • Хоть в листбокс вы и кладете только ID, из базы вы тянете все. Попробуйте сделать вот так:

    var query = from pr in context.pGst
             where pr.SortID1 == 7 && pr.SortID2 == 2
             select pr.ID;
    • Помечено в качестве ответа developers_s 23 августа 2011 г. 16:12
    23 августа 2011 г. 11:33

Все ответы

  • как вариант после запуска приложения в отдельном потоке выполнить "холодный старт" - обратиться к базе. например, запросить количество строк.

    21 августа 2011 г. 9:30
  • Что сделать можно?

     


    AUTOCLOSE у базы в каком состоянии?
    http://www.t-sql.ru
    21 августа 2011 г. 10:17
    Модератор
  • Да, конечно так можно. Собственно так я и делаю, только раз не знаю про потоки, то выполняю это при загрузке окна. Но тогда видимо увеличивается время загрузки между Splashscreen и самим окном??

    Но про потоки гляну спасибо.

    • Помечено в качестве ответа developers_s 21 августа 2011 г. 12:47
    • Снята пометка об ответе Abolmasov Dmitry 6 сентября 2011 г. 17:33
    21 августа 2011 г. 12:41
  • Спасибо за наводку гляну и этот момент, если найду. Пок ане нашёл. В состоянии базы стоит в подключении стоит "Открыть".
    21 августа 2011 г. 12:47
  • А зачем вам подгружать все данные? Это не рационально. 6000 строк ещё терпимо, но при увеличении их количества, у вас может закончиться память. Гораздо быстрее и удобнее будет подгружать только ID строк в запросе. В ListBox-е настроить виртуализацию и подгружать только те записи, которые отображаются. В итоге из базы будут вытаскиваться всего-то штук 100 записей (вы же не будете просматривать все 6000).
    22 августа 2011 г. 9:51
  • > только раз не знаю про потоки, то выполняю это при загрузке окна. Но тогда видимо увеличивается время загрузки между Splashscreen и самим окном?

    ниже пример "холодного старта", который выполняется в отдельном потоке

     

    using System;
    using System.Data.SqlClient;
    using System.Diagnostics;
    using System.Windows;
    using System.Threading;
    
    namespace WpfApplication9
    {
      public partial class App : Application
      {
        public App()
        {
          ThreadPool.QueueUserWorkItem(state => Test("cold start", "select count(*) from INFORMATION_SCHEMA.COLUMNS"));
        }
        public static void Test(string notes, string sql)
        {
          var s = Stopwatch.StartNew();
          using (var sc = new SqlConnection(@"Data Source=HOME\SQLEXPRESS;Initial Catalog=Tfs_DefaultCollection;Integrated Security=True;Pooling=False;Connect Timeout=30"))
          {
            sc.Open();
            var cmd = sc.CreateCommand();
            cmd.CommandText = sql;
            var res = cmd.ExecuteScalar();
            Trace.WriteLine(notes + " timing=" + s.ElapsedTicks);
          }
        }
      }
      public partial class MainWindow : Window
      {
        public MainWindow() { InitializeComponent(); }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
          App.Test("default", "select count(*) from INFORMATION_SCHEMA.COLUMNS");
        }
      }
    }
    
    
    <Window x:Class="WpfApplication9.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
      <Grid>
        <Button Click="Button_Click" Content="select" HorizontalAlignment="Center" VerticalAlignment="Center"></Button>
      </Grid>
    </Window>
    
    
    примерный результат: (видно, что первый запрос выполняется намного дольше остальных)

    cold start timing=211790    // выполняется в отдельном потоке во время загрузки приложения.
    default timing=23591
    default timing=23354
    ...

    P.S.
    вместо вызова ThreadPool.QueueUserWorkItem (используется поток из пула), можно создать новый фоновый поток:

    var t = new Thread(() => Test("cold start", "select count(*) from INFORMATION_SCHEMA.COLUMNS"));
    t.IsBackground = true;
    t.Start();
    

    • Предложено в качестве ответа Malobukv 22 августа 2011 г. 17:59
    • Помечено в качестве ответа developers_s 23 августа 2011 г. 11:05
    22 августа 2011 г. 17:53
  • Я могу ошибаться (сам чаще использую лямбда-методы, а не linq), но реальные запросы к базе в вашем коде  выполняются именно в цикле foreach. Это отложенное выполнение запроса, lazy loading.

    В вашем случае к базе будет выполнено столько запросов, сколько строк будет в результате. Чтобы этого избежать, после select вызовите ToList() и потом уже выполняйте перечисление. Это называется материализацией - все нужные строки загрузятся за один запрос к базе.

    Избегайте использования lazy loading там, где оно не нужно, это неоправданная нагрузка на БД.

    • Помечено в качестве ответа Abolmasov Dmitry 6 сентября 2011 г. 17:50
    22 августа 2011 г. 18:10
  • Конечно же всем спасибо. Столько ответов, столько новой инфы - супер. Только хотел уточнить, что в базе 6000 записей, да конечно она будет пополняться, но в ListBox выводятся по id причём сортировка по двум столбцам, т.е. данных в сам LisyBox выводится всего 10 -15 за один вызов.

    Как бы скорость обменя с базой меня устраивает. Только со второго раза :). Со второго обращения. Т.е. когда я потом что то в listBox выбираю то подгрузка данных уже из других столбцов идёт очень быстро. Даже я когда не отпуская левую кнопку просто провожу по ListBox то данные обновляются моментально. Всё плохо только при первом обращении.

    Я понял, что здесь много вариантов решения проблемы, но пока буду копать про холодный запуск второго потока. :)

    Спасибо

     


    23 августа 2011 г. 11:11
  • Хоть в листбокс вы и кладете только ID, из базы вы тянете все. Попробуйте сделать вот так:

    var query = from pr in context.pGst
             where pr.SortID1 == 7 && pr.SortID2 == 2
             select pr.ID;
    • Помечено в качестве ответа developers_s 23 августа 2011 г. 16:12
    23 августа 2011 г. 11:33
  • Хорошо попробую вариант. Результат отпишу.

    23 августа 2011 г. 16:13