четверг, 26 декабря 2013 г.

Реализация асинхронной загрузки FillAsync для DbDataAdapter в .NET Framework 4.5 с помощью async и await

Добрый день.
Не так давно я начал изучать нововведения в .NET Framework 4.5
В частности заинтересовала реализация async и await методов.
Сам постоянно работаю с базами данных, которые загружаю с помощью BackgroundWorker или другого потока, чтобы не блокировать интерфейс пользователя при долгой загрузке.
Но меня расстроило отсутствие встроенной возможности асинхронной загрузки через  DbDataAdapter от которого наследуются адаптеры данных. Метод FillAsync просто отсутствует.

OpenAsync() в DbConnection есть, ReadAsync() в DbDataReader есть, а вот FillAsync() в DbDataAdapter нет. Для меня это оказалось серьезным поводом не обновлять мои проекты, т.к. везде используется Fill в адаптере данных.

Но немного подумав, я решил все таки реализовать такой метод сам.
В итоге я написал расширение для DbDataAdapter.
И хочу рассказать вам как я это сделал.

Добавляем в проект новый класс с любым именем, например StaticData.
Со следующим кодом:

using System.Data;
using System.Data.Common;
using System.Threading.Tasks;

namespace AsyncAwaitTesting1
{
    static class StaticData
    {
        public static async Task FillAsync(this DbDataAdapter da, DataTable ds)
        {
            var t = new Task(() => Load(da, ds));            
            t.Start();
            return await t;
        }

        private static int Load(DbDataAdapter da, DataTable dt)
        {            
            return da.Fill(dt);
        }
    }
}




Допустим в проекте уже имеется набор данных. Перейдем к редактору кода:


Добавим код на на рисунке ниже


Прошу обратить внимание что для примера тут расширен адаптер данных только для одной таблицы Codebranch. По идее это нужно сделать для всех таблиц набора данных.
Еще обратите внимание что пространство имен тут тоже другое.

В итоге верхний код добавляет метод FillAsync для адаптера данных.
Работает это так:
int i = await codebranchTableAdapter1.FillAsync(dynPlatformDataSet1.Codebranch);

А вот сам код обработчика


Теперь, при таком сценарии действия метод Fill выполняется асинхронно, не блокируя UI.
Пока неудобство представляет необходимость вручную добавлять методы FillAsync для каждого типизированного адаптера данных.

Хотелось бы чтобы разработчики, все таки учли это и сами включили метод FillAsync для адаптеров данных в будущих редакциях NET Framework.

Пока что все

пятница, 9 августа 2013 г.

Firebird DDEX provider для Visual Studio 2012

Добрый день, коллеги.
Представляю вашему внимаю новый релиз установщика провайдера DDEX для работы в Visual Studio 2012 с базами данных Firebird. Поддерживается ADO.NET и Entity Framework.

Скачать вы можете по ссылке.

четверг, 4 июля 2013 г.

Размещение WCF библиотеки в службе Windows.

В этой статье хочу рассказать как правильно разместить службу WCF (Windows Communication Foundation) в службе Windows 7.

Создадим новый проект WCF Service Library.

В Solution Explorer находим файл app.config, нажимаем на нем правой кнопкой и выбираем Edit WCF Configuration.

Переходим к пункту Host. Выбираем базовый адрес и нажимаем кнопку "Edit"

И вписываем адрес: net.tcp://localhost:8732/Service1/

Должно выглядеть вот так:

На пункте Binding нажимаем правую кнопку и выбираем New Binding Configuration

Далее выбираем netTcpBinding


Переходим к пункту serviceMetadata и меняем значение HttpGetEnabled на False.

Переходим к пункту Service Endpoint и меняет Binding на netTcpBinding


В BindingConfiguration ставим NewBinding0

Переходим ко второму пункту (Empty Name) и в Binding указываем mexTcpBinding

На этом настройка конфигурации закончена. Нажимаем File - Save.

В проект добавляем службу Windows.

На дизайнере службы нажимаем правую кнопку и добавляем установщик (Add Installer).
 

Выбираем появившийся serviceProcessInstaller1 и меняет Account с User на LocalSystem

Добавляем в проект WindowsService1 ссылку на проект WcfLibraryService (Add reference)
Выбираем serviceInstaller1, переключаемся на вкладку событий, и дважды щелкаем по событию AfterInstall.

Переходим к коду, добавляем using System.Management;
В процедуру serviceInstaller1_AfterInstall вписываем следующий код:

Code Snippet
  1. private void serviceInstaller1_AfterInstall(object sender, InstallEventArgs e)
  2. {
  3.     ConnectionOptions coOptions = new ConnectionOptions();
  4.     coOptions.Impersonation = ImpersonationLevel.Impersonate;
  5.  
  6.     ManagementScope mgmtScope = new System.Management.ManagementScope(@"root\CIMV2", coOptions);
  7.     mgmtScope.Connect();
  8.  
  9.     using (ManagementObject wmiService = new ManagementObject("Win32_Service.Name='" + serviceInstaller1.ServiceName + "'"))
  10.     {
  11.         ManagementBaseObject InParam = wmiService.GetMethodParameters("Change");
  12.         InParam["DesktopInteract"] = true;
  13.         wmiService.InvokeMethod("Change", InParam, null);
  14.     }
  15. }

Это действие позволит службе взаимодействовать с рабочим столом пользователя.
Хотя в Windows 7 это уже не актуально. И только добавляем больше проблем.

Весь код файла выглядит так:

Code Snippet
  1. using System.ComponentModel;
  2. using System.Configuration.Install;
  3. using System.Management;
  4.  
  5.  
  6. namespace WindowsService1
  7. {
  8.     [RunInstaller(true)]
  9.     public partial class ProjectInstaller : System.Configuration.Install.Installer
  10.     {
  11.         public ProjectInstaller()
  12.         {
  13.             InitializeComponent();
  14.         }
  15.  
  16.         private void serviceInstaller1_AfterInstall(object sender, InstallEventArgs e)
  17.         {
  18.             ConnectionOptions coOptions = new ConnectionOptions();
  19.             coOptions.Impersonation = ImpersonationLevel.Impersonate;
  20.  
  21.             ManagementScope mgmtScope = new System.Management.ManagementScope(@"root\CIMV2", coOptions);
  22.             mgmtScope.Connect();
  23.  
  24.             using (ManagementObject wmiService = new ManagementObject("Win32_Service.Name='" + serviceInstaller1.ServiceName + "'"))
  25.             {
  26.                 ManagementBaseObject InParam = wmiService.GetMethodParameters("Change");
  27.                 InParam["DesktopInteract"] = true;
  28.                 wmiService.InvokeMethod("Change", InParam, null);
  29.             }
  30.         }
  31.     }
  32. }

В свойствах проекта WcfServiceLibrary на последней вкладке снимаем галочку Start WCF Service Host.

Копируем файл app.config в проекте WcfServiceLibrary

Этот файл нужно вставить в проект WindowsService

Идея такая, что файл app.config должен быть одинаковый в этих двух проектах.
Потому что служба WCF будет вызываться из службы Windows, то в проекте службы Windows должны находиться параметры WCF службы. Эти параметры служба Windows (WindowsService1) не может достать из файла app.config проекта WcfServiceLibrary.

В проекте WindowsService1 на файле Service1 нажимаем правой кнопкой и выбираем View Code


Добавляем ссылку на сборку System.ServiceModel;
И в коде вписываем using System.ServiceModel;

В процедуре OnStart вписываем код:

Code Snippet
  1. protected override void OnStart(string[] args)
  2. {
  3.     if (myServiceHost != null)
  4.     {
  5.         if (myServiceHost.State == CommunicationState.Opened) myServiceHost.Close();
  6.     }
  7.  
  8.     try
  9.     {
  10.         myServiceHost = new ServiceHost(typeof(WcfServiceLibrary1.Service1));
  11.         myServiceHost.Open();
  12.     }
  13.     catch (Exception ex)
  14.     {
  15.         EventLog.WriteEntry(this.ServiceName, ex.Message);
  16.     }
  17. }

Это позволит службе Windows при запуске запустить хостинг WCF службы
В процедуре OnStop вписываем код:

Code Snippet
  1. protected override void OnStop()
  2. {
  3.     if (myServiceHost != null)
  4.     {
  5.         myServiceHost.Close();
  6.         myServiceHost = null;
  7.     }
  8. }

В итоге общий код файла выглядит так:

Code Snippet
  1. using System;
  2. using System.Diagnostics;
  3. using System.ServiceProcess;
  4. using System.ServiceModel;
  5.  
  6. namespace WindowsService1
  7. {
  8.     public partial class Service1 : ServiceBase
  9.     {
  10.         public Service1()
  11.         {
  12.             InitializeComponent();
  13.         }
  14.         private static ServiceHost myServiceHost = null;
  15.  
  16.         protected override void OnStart(string[] args)
  17.         {
  18.             if (myServiceHost != null)
  19.             {
  20.                 if (myServiceHost.State == CommunicationState.Opened) myServiceHost.Close();
  21.             }
  22.  
  23.             try
  24.             {
  25.                 myServiceHost = new ServiceHost(typeof(WcfServiceLibrary1.Service1));
  26.                 myServiceHost.Open();
  27.             }
  28.             catch (Exception ex)
  29.             {
  30.                 EventLog.WriteEntry(this.ServiceName, ex.Message);
  31.             }
  32.         }
  33.  
  34.         protected override void OnStop()
  35.         {
  36.             if (myServiceHost != null)
  37.             {
  38.                 myServiceHost.Close();
  39.                 myServiceHost = null;
  40.             }
  41.         }
  42.     }
  43. }

Добавляем новый проект Windows Forms. И делаем его запускаемым по-умолчанию (Set as Startup project).
Кидаем на форму кнопку и добавляем ссылку на службу (Add Service Reference)
Нажимаем кнопку Discover и выбираем службу Service1


В обработчик нажатия на кнопку вписываем код:

Code Snippet
  1. private void button1_Click(object sender, EventArgs e)
  2. {
  3.     ServiceReference1.Service1Client myService = new ServiceReference1.Service1Client();
  4.     MessageBox.Show(myService.GetData(123), "My Service");
  5.     myService.Close();
  6. }

Весь файл должен выглядеть так:

Code Snippet
  1. using System;
  2. using System.Windows.Forms;
  3.  
  4. namespace WindowsFormsApplication1
  5. {
  6.     public partial class Form1 : Form
  7.     {
  8.         public Form1()
  9.         {
  10.             InitializeComponent();
  11.         }
  12.  
  13.         private void button1_Click(object sender, EventArgs e)
  14.         {
  15.             ServiceReference1.Service1Client myService = new ServiceReference1.Service1Client();
  16.             MessageBox.Show(myService.GetData(123), "My Service");
  17.             myService.Close();
  18.         }
  19.     }
  20. }

Нажимаем F6 - для компиляции проекта. Ошибок быть не должно.
Открываем папку проекта WindowsService1


В эту папку нужно скопировать файл InstallUtil.exe
Чтобы его найти на вашем компьютере воспользуйтесь поиском.

Создадим файл InstallServiceAndStart.bat со следующим текстом:
InstallUtil.exe WindowsService1.exe
net start Service1

Создадим файл UninstallServiceAndStop.bat со следующим текстом:
net stop Service1
InstallUtil.exe /u WindowsService1.exe

В итоге папка должна выглядеть так:

Если вы используете Windows XP то для запуска/удаления службы можно пользоваться этими BAT файлами. Если же Windows 7 - то нужно сделать так:
В меню найдите пункт Microsoft Visual Studio 2010\Visual Studio tools

В этой папке есть "Open Visual Studio Command Prompt (2010)", нажмите на нем правой кнопкой мыши и выберите "Запуск от имени Администратора". Отобразится та же консоль.
Поменяйте текущую папку.

Для установки и запуска службы скопируйте содержимое файла InstallServiceAndStart.bat и вставьте в это окно и нажмите Enter (для Windows 7). Если в Windows 7 запускать файлы, то после каждого удаления службы Windows придется перезагружать компьютер.
Если у вас Windows XP то можно просто запускать батники.


Служба установлена и запущена.
Теперь нужно запустить приложение WindowsFormsApplication1. Возможно что Visual Studio не позволит запустить отладку, т.к.он не сможет заменить файл WindowsService1.exe потому, что он будет выполняться ОС. Я просто открыл папку и запустил WindowsFormsApplication1.exe


Нажимаем кнопку и через несколько секунд получаем сообщение


Это говорит о том, что наша служба Windows работает.
Если в службу вносите изменения то нужно произвести удаление службы.
Для этого в Windows XP просто запустите UninstallServiceAndStop.bat в Windows 7 нужно найти пункт Microsoft Visual Studio 2010\Visual Studio tools

В этой папке есть "Open Visual Studio Command Prompt (2010)", нажмите на нем правой кнопкой мыши и выберите "Запуск от имени Администратора". Поменяйте текущую папку на папку с файлом WindowsService1.exe. Скопируйте текст из файла