Томский политехнический университет
Опубликован: 23.01.2013 | Доступ: свободный | Студентов: 1158 / 192 | Длительность: 12:09:00
Самостоятельная работа 4:

Создание приложения с использованием PLINQ

< Лекция 9 || Самостоятельная работа 4 || Лекция 10 >
Аннотация: В рамках данного практического занятия, будет создано консольное приложение, которое получает из СУБД MS SQL данные по сотрудникам и оформленными ими заказами (стоимость, количество, наименования продукта) с помощью LINQ и PLINQ запросов.

  1. Создадим консольное приложение и назовем его, к примеру, "PLINQApplicaions":
  2. Теперь необходимо установить связь с базой данных. Для этого откроем обозреватель серверов, с помощью пункта Server Explorer меню View или с помощью сочетания горячих клавиш "Ctrl + Alt+S"

    Рис. 13.2.
  3. Далее создадим соединение к серверу. Для этого щелкните правой кнопкой мыши по "Data Connections" и выберите пункт "Add connection…"

    Рис. 13.3.
  4. Появится диалоговое окно "Add Connection". В диалоговом окне необходимо ввести адрес или имя сервера (Sever name), где расположена база данных, тип доступа к базе данных (SQL аутентификация или Windows аутентификация) и выбрать из писка нужную базу данных (в нашем случае база данных Northwind):

    Общая схема данных Northwind выглядит следующим образом:

  5. Если связь с базой успешно установится, то в обозревателе серверов появится база данных Northwind:
  6. Теперь необходимо создать связку между SQL и LINQ. Для этого в обозреватели проекта (Solution Explorer), для этого щелкните правой кнопкой мыши по названию проекта и выберите пункт "New Item…" или с помощью клавиш "Ctrl+Shift+A":
  7. В появившемся диалоговом окне выберите пункт "Linq to SQL Classes", и введите название добавляемого файла (в нашем случае NorthwindDatabase):
  8. Появится вкладка NorthwindDatabase.dbml. Теперь из обозревателя серверов (Server Explorer) перетащите, с помощью мыши, нужные таблицы в окно созданного файла:
  9. Теперь, перейдем в файл Program.cs и создадим в классе Program новый объект класса NorthwindDataBaseDataContext (где NorthwindDataBase - название файла созданного ранее NorthwindDatabase.dbml):
    class Program
        {
            private static NorthwindDataBaseDataContext db = new NorthwindDataBaseDataContext();
    ….
            }
  10. Создадим класс InfoEmployees, со следующими свойствами:
    public class InfoEmployees 
        {
         public string FirstName { get; set; }
         public string LastName { get; set; }
         public string ProductName { get; set; }
         public decimal UnitPrice { get; set; }
         public int Quantity { get; set; }
         public DateTime OrderDate { get; set; }
        }
  11. Создадим метод, возвращающий список объектов типа InfoEmployees, с использованием интерфейса IEnumerable и LINQ - запроса к базе данных:
    public static IEnumerable<InfoEmployees> GetInfoEmployees()
      {
    
         var emp = from em in db.Employees
                   join o in db.Orders
                    on em.EmployeeID equals o.EmployeeID
                   join orderdetails in db.Order_Details
                    on o.OrderID equals orderdetails.OrderID
                   join product in db.Products
                    on orderdetails.ProductID equals product.ProductID
                   select new InfoEmployees()
                   {
    
                       FirstName = em.FirstName,
                       LastName = em.LastName,
                       ProductName = product.ProductName,
                       UnitPrice=orderdetails.UnitPrice,
                       Quantity=orderdetails.Quantity,
                       OrderDate=o.OrderDate.Value
                    };
            
        return emp;
      }
  12. В метод Main() добавим код, который выводит список сотрудников с помощью LINQ-запроса:
    static void Main(string[] args)
            {	
                IEnumerable<InfoEmployees> em = GetInfoEmployees();
                int count = 1;
    
      var infoemp = from e in em
                              select e;
       foreach (var item in infoemp)
                {
    
     Console.WriteLine("{0} {1} {2} {3} {4} {5}", item.FirstName, item.LastName,
     item.ProductName, item.Quantity, item.UnitPrice.ToString("C", 
    CultureInfo.CreateSpecificCulture("en-US")), item.OrderDate.ToString("dd.MM.yyyy"));
                    count++;
                }
    Console.WriteLine("--------------------- ");
           Console.WriteLine("Количество записей {0}", count);
           Console.ReadLine();
            }
  13. Запустим приложение в результате, на экран выведется следующее:
  14. Теперь, модифицируем программу. Создадим отдельный перегруженный метод ProcessItem() для вывода сотрудников на экран, и "заморозим" выполнение данного метода, при каждой итерации, на 10 миллисекунд (Thread.Sleep(10)) для наглядности производительности распараллеленного запроса:
    public static string ProcessItem(InfoEmployees item)
            {
    Thread.Sleep(10);
                return string.Format ("{0} {1} {2} {3} {4} {5}", item.FirstName, item.LastName, item.ProductName,
     item.Quantity, item.UnitPrice.ToString("C", CultureInfo.CreateSpecificCulture("en-US")),
     item.OrderDate.ToString("dd.MM.yyyy"));
            }
  15. Модифицируем метод Main(). В select запросе вызовем метод ProcessItem() и передадим в него объект класса InfoEmployees:
    static void Main(string[] args)
        {
            IEnumerable<InfoEmployees> em = GetInfoEmployees();
            int count = 1;
            var infoemp = from e in em
                          select ProcessItem(e);
            foreach (var item in infoemp)
            {
                Console.WriteLine(item);
                count++;
            }
            Console.WriteLine("--------------------- ");
            Console.WriteLine("Количество записей {0}", count);
            Console.ReadLine();
        }
  16. Запустим программу. В результате программа выведет на экран следующее, но с гораздо большей задержкой (из-за использования метода Thread.Sleep()):
  17. Для определения производительности LINQ - запроса модифицируем код в методе Main():
    static void Main(string[] args)
        {
            System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
            IEnumerable<InfoEmployees> em = GetInfoEmployees();
            int count = 1;
            var infoemp = from e in em
                          select ProcessItem(e);
            foreach (var item in infoemp)
            {
                Console.WriteLine(item);
                count++;
            }
             long elapsed = sw.ElapsedMilliseconds;        
             Console.WriteLine("--------------------- ");
             Console.WriteLine("Количество записей {0}", count);
             Console.WriteLine("Общее время выполнения запроса {0} мс", elapsed);
             Console.ReadLine();
         }

    Примечание: Для более точно расчета времени выполнения запроса можно использовать sw.ElapsedTicks

  18. Запустим программу. Программа выведет на экран, кроме результата LINQ-запроса, еще и время выполнения запроса (в нашем случае запрос выполняется 22409 мс):
  19. Изменим программу в методе Main(), для того что бы распараллелить выполнение метода ProcessItem():
    static void Main(string[] args)
       {
           System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
           IEnumerable<InfoEmployees> em = GetInfoEmployees();
           int count = 1;
     	     var infoemp = from e in em.AsParallel().AsOrdered()
                         select ProcessItem(e);
           foreach (var item in infoemp)
           {
          
               Console.WriteLine(item);
               count++;
           }
         long elapsed = sw.ElapsedMilliseconds;       
          
           Console.WriteLine("--------------------- ");
           Console.WriteLine("Количество записей {0}", count);
           Console.WriteLine("Общее время выполнения запроса {0} мс", elapsed);
           Console.ReadLine();
       }
    Примечание: Как видно из примера кода - PLINQ - запрос отличается от LINQ - запроса, только вызовом метода AsParallel() в фрагменте: em.AsParallel().AsOrdered(). Метод AsOrdered() предназначен для упорядочивания записей независимо от наличия упорядоченности по умолчанию. Также можно использовать следующий формат записи LINQ - запроса: var infoemp = em.AsParallel().Select(ProcessItem).AsOrdered();
  20. Запустим программу. И как видно из результата выполнения, общее время выполнения PLINQ -запроса, сократилось примерно вдвое и составило 11938 мс по сравнению с предыдущим результатом 22409 мс:

    Листинг кода программы:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Globalization;
    namespace PLINQApplications
    {
        class Program
        {
            private static NorthwindDataBaseDataContext db = new NorthwindDataBaseDataContext();
            public class InfoEmployees 
            {
             public string FirstName { get; set; }
             public string LastName { get; set; }
             public string ProductName { get; set; }
             public decimal UnitPrice { get; set; }
             public int Quantity { get; set; }
             public DateTime OrderDate { get; set; }
            }
            public static IEnumerable<InfoEmployees> GetInfoEmployees()
            {
                var emp = from em in db.Employees
                          join o in db.Orders
                           on em.EmployeeID equals o.EmployeeID
                          join orderdetails in db.Order_Details
                           on o.OrderID equals orderdetails.OrderID
                          join product in db.Products
                           on orderdetails.ProductID equals product.ProductID
                          select new InfoEmployees()
                          {                  
                              FirstName = em.FirstName,
                              LastName = em.LastName,
                              ProductName = product.ProductName,
                              UnitPrice=orderdetails.UnitPrice,
                              Quantity=orderdetails.Quantity,
                              OrderDate=o.OrderDate.Value
                          };
            
                return emp;
            }
            public static string ProcessItem(InfoEmployees item)
            {
               Thread.Sleep(10);
                return string.Format ("{0} {1} {2} {3} {4} {5}", item.FirstName, item.LastName, item.ProductName, 
    item.Quantity, item.UnitPrice.ToString("C", CultureInfo.CreateSpecificCulture("en-US")), 
    item.OrderDate.ToString("dd.MM.yyyy"));
            }
            static void Main(string[] args)
            {
                System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
                IEnumerable<InfoEmployees> em = GetInfoEmployees();
    );
                int count = 1;
                var infoemp = em.AsParallel().Select(ProcessItem).AsOrdered();
                foreach (var item in infoemp)
                {
               
                    Console.WriteLine(item);
                    count++;
                }
              long elapsed = sw.ElapsedMilliseconds;       
              
                Console.WriteLine("--------------------- ");
                Console.WriteLine("Количество записей {0}", count);
                Console.WriteLine("Общее время выполнения запроса {0} мс", elapsed);
                Console.ReadLine();
            }
        }
    }
< Лекция 9 || Самостоятельная работа 4 || Лекция 10 >
Владимир Каширин
Владимир Каширин

Вопрос по Курсу: "Параллельное программирование с использованием MS VisualStudia 2010".

При компиляции Самостоятельного задания (одновременная отрисовка прямоугольников, эллипсов и выдача в текст-бокс случайного числа) среда предупреждает: suspend - устаревшая команда; примените monitor, mutex и т.п.

Создаётся впечатление, что Задание создано в более поздней среде, чем VS 2010.