24 Июль 2008 г.

NHibernate и валидация

Замечательный проект затеял Dario Quintana в следующей версии библиотеки дополнений NHibernate Contrib:

NHV-logo-white-background

Его предназначение - декларирование и централизованная проверка правил валидации где-нибудь поближе к модели домена.

Например, правила можно описывать в виде атрибутов:

public class User
{
 public virtual int Id
 {
     get { return id; }
 }

 [NotEmpty, NotNull]
 public virtual string UserName
 {
     get { return userName; }
     set { userName = value; }
 }

 [Email]
 public virtual string Email
 {
     get { return email; }
     set { email = value; }
 }

 [Past]
 public DateTime CreatedDate
 {
   get { return createdDate; }
   set { createdDate = value; }
 }

 [Min(18, Message="You are to young!")]
 public int Age
 {
   get { return age; }
   set { age = value; }
 }

 [CreditCardNumber]
 public string CreditCardNumber
 {
   get { return creditCardNumber; }
   set { creditCardNumber = value; }
 }

 ///...
}

Или же, если вам нужна возможность гибкой настройки этих правил, скажем, для согласования с констрейнтами в базе данных, то правила можно определить в файле конфигурации:

<nhv-mapping xmlns="urn:nhibernate-validator-1.0">
 <class name="NHibernate.Validator.Demo.Winforms.Model.Customer, NHibernate.Validator.Demo.Winforms">   
   <property name="FirstName">
     <not-empty/>
     <not-null/>
   </property>   
 
   <property name="Email">
     <email/>
   </property>

   <property name="Zip">
     <pattern  regex="^[A-Z0-9-]+$" message="Examples of valid matches: 234G-34DA | 3432-DF23"/>
     <pattern  regex="^....-....$" message="Must match ....-...."/>
   </property>   
  
 </class> 
</nhv-mapping>

Ну и пример того, как происходит процесс валидации:

Для начала создаем экземпляр валидатора:

NHVConfiguration nhvc = new NHVConfiguration();
nhvc.Properties[Environment.ApplyToDDL] = "false";
nhvc.Properties[Environment.AutoregisterListeners] = "true";
nhvc.Properties[Environment.ValidatorMode] = "UseAttribute";
nhvc.Mappings.Add(new MappingConfiguration("NHibernate.ValidatorDemo.Model", null));

ValidatorEngine validator = new ValidatorEngine();
validator.Configure(nhvc);

Ну и потом пользуемся им:

public void Create([DataBind("user")] User user)
{
 InvalidValue[] errors = validator.Validate(user);

 if (errors.Length > 0)
 {
   Flash["errors"] = errors;
   RedirectToAction("index");
 }
 else
 {
   repository.Create(user);
 }
}

Процесс валидации можно попробовать сделать более прозрачным, например, используя фильтры в ASP.NET MVC, или реализовав какие-то аспекты прямо на уровне модели домена.

P.S. Пост написан мо мотивам этого поста.

далее...

7 Июль 2008 г.

Распределение сессий ASP.NET с помощью memcached

Для многих разрабатываемых сайтов, расчитанных на успех, и, как следствие, на большой трафик, одним из важных требований является удобное горизонтальное масштабирование и высокий уровень доступности приложения. При решении этих задачи приходится решать задачи уровня системы и уровня приложения. На системном уровне все решается настройкой всевозможных кластеров, систем резервного копирования, диспетчеров нагрузки и т.д. И очень важно эти вынести эти системные задачи за пределы кода приложения, иначе добавление лишнего сервера в кластер может превратиться бесконечный бой со своим кодом.

В общем случае, главной проблемой, мешающей клонированию приложения или сервиса на многие сервера с сохранением общей функциональности является использование состояний клиента. Другими словами - использование сессий. Эта проблема связана с чисто программной реализацией сессий поверх несессионного протокола HTTP, когда идентификатор сеанса отправляется в cookies каждому клиенту, а потом по нему сервер, обслуживающий запросы "достает" нужные данные. И по умолчанию, данные сессий хранятся локально в памяти или файловой системе сервера. А в случае, когда приложение "живет" на нескольких серверах, то идентификатор сеанса каждый раз будет обрабатываться разными серверами и сессии утратят свой смысл.

В качестве решения этой проблемы в ASP.NET предлагается использовать особый режимы StateServer и SQLServer, когда состояние будет храниться на удаленном сервере, либо вообще в базе SQL Server. У первого способа главным недостатком является то, что сам по себе сервер состояний не масштабируется вообще. В случае же использования режима SQLServer и размещении базы данных состояний на отдельном кластере, мы получим масштабируемую систему, но в этом случае сильно потеряем в производительности. Конечно же, когда идет о масштабируемости, локальной производительностью можно пренебречь, посчитав, что ее можно увеличить за счет включения лишних боксов в кластер. Но что делать начинающим стартаперам, которые пока еще считают деньги? Как получить систему, относительно быструю, как в случае со StateServer, и масштабируемую, как в режиме работы с SQL Server?

Ответ прост - использование систем распределенной памяти вобще и memcached в частности, как распространенный, проверенный, простой и бесплатный продукт. Познакомиться с ним можно в статье Обзор memcaced. А в этом посте мы попробуем решить с его помощью проблемы с сессиями в ASP.NET.

Вкратце, нам хотелось бы получить систему, которая бы прозрачно интегрировалась с уже готовым приложением с минимальными изменениями в коде, достаточно быструю и надежную, не теряющую данные, как при перезагрузке отдельных приложений, так и при перезагрузке серверов и всего кластера целиком. И все это реализуется достаточно просто :) Итак, приступим:

  1. Так как среда обитания наших приложений - это Windows, то скачиваем отсюда или отсюда бинарники, скомпилированые для win32. Распаковываем его куда-нибудь и запустим единственный exe-файл. Сервер memcached запущен :) Весь процесс настройки производится через параметры запуска. Ознакомиться с ними можно запустив сервер с параметром -h.
  2. Теперь нам нужно чем-то подключаться к этому сервису. И в качестве клиента предлагается использовать enyim.com Memcached Client, и его можно использовать уже в составе готовых Memcached Providers, что максимально упрощает использование memcached. Скачав и разархивировав билиотеку куда-нибудь, добавляем в наш проект ссылки на файлы Enyim.Caching.dll, MemcachedProviders.dll и log4net.dll.
  3. В коде приложения необходимо найти все классы, экземпляры которых помещаются в сессию и отметить их атрибутом Serializable.
  4. Осталось дело за малым - сконфигурировать наше приложение должным образом:
    • В разделе configSections регистрируем секции:
      <sectionGroup name="enyim.com">
      <section name="memcached" type="Enyim.Caching.Configuration.MemcachedClientSection, Enyim.Caching" />
      </sectionGroup>
      <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
    • Добавляем секцию настройке клиента memcached:
      <enyim.com>
      <memcached>
        <servers>
          <add address="127.0.0.1" port="11211" />
        </servers>
        <socketPool minPoolSize="10" maxPoolSize="100" connectionTimeout="00:00:10" deadTimeout="00:02:00" />
      </memcached>
      </enyim.com>
      Ключевым элементом здесь является секция servers, в которой перечисляются все memcached серверы, обслуживающие приложение. Самым распространенным вариантом использования является установка экземпляра memcached на каждый из серверов, на котором работает приложение.
    • А теперь настраиваем работу сессий:
      <sessionState cookieless="true" regenerateExpiredSessionId="true" mode="Custom" customProvider="MemcachedSessionProvider">
      <providers>
        <add name="MemcachedSessionProvider" type="MemcachedProviders.Session.SessionStateProvider, MemcachedProviders"
              connectionStringName="SqlSessionServices"
              dbType="SQL"
              writeExceptionsToEventLog="false" />
      </providers>
      </sessionState>
      Интересной здесь является строка dbType="SQL" - она указывает, какого типа СУБД будет использоваться для бэкапов данных сессии. Для отключения режима бэкапа данных нужно просто указать dbType="None".
    • Заключительным этапом будет настройка параметров подключения к базе данных для бэкапов. Для этого в секцию connecitonStrings нужно добавить такую запись:
      <add name="SqlSessionServices" connectionString="..."/>
      (троеточие следует заменить на реальную строку подключения).
    • Опционально можно добавить найстройки для log4net.

Осталось запустить приложение.

При обращении к сессии, провайдер обращается к клиенту memcached, который в свою очередь посылает асинхронные запросы на данные всем memcached-серверам, используюя идентификатор сессии, как ключ к данным. И вот здесь происходит самое инетресное: если это не первый запрос к сессии и какие-то данные там уже размещены, но предыдущий запрос обслуживал другой сервер, то клиент, а за ним и провайдер получат данные от другого сервера. А если же предыдущий запрос обслуживался текущим сервером, то он получит данные от от процесса, работающего на этом же сервере, чтобы будет гораздо быстрее, чем если бы он получал их по сети. Таким образом, в случае, если нагрузка равномерно распределяется между серверами, то гарантированное попадание в локальный сервис будет примерно в каждом втором случае. А если же сюда добавить условие, что как браузеры, так и сетевое оборудование оптимизирует работу с соединениями, "придерживая" их в пулле и используя так называемые sticky connections, то количество попаданий будет стремиться к 100%.

В случае, если ни один из сервисов не вернет данных по запросу, провайдер обратится в базу данных. Этот же сценарий будет срабатывать в случае, если ни один из memcached-сервисов не будет запущен. Можно сказать, что в этом случае сессии будут работать так же, как в режиме SQLServer.

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

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

Недостатоком этой конфигурации является то, что она не работает в случае, когда не доступен SQL Server. Если указать режим бэкапа dbType="None", то в случае недоступности memcached-сервисов будет использоваться стандартный режим работы сессий, когда все данные хранятся в памяти рабочего процесса. Очень хотелось бы, чтобы провайдер стал умнее и стал использовать стандартный режим в случае, если оба хранилища не доступны. Но код провайдера доступен, логика его работы более чем проста и добавление этой функциональности не является проблемой.

File iconскачать пример

далее...

17 Декабрь 2007 г.

IIS 7.0 и Custom Errors

Разработчики, начавшие использовать IIS 7.0, могут столкнуться с интересной проблемой, которая не была актуальна для систем, работающих на базе IIS 6.0. Суть проблемы заключается в том, что если для приложения разработан свой обработчик ошибок, обрабатывающий разные типы исключений и выполняющий редирект на разные страницы, то этот редирект будет проигнорирован и пользователь увидит стандартный "желтый экран" IIS.

Решение же проблемы оказалось куда банальнее, чем сама проблема :)

Исследования выявили интересное нововведение в классе HttpRequest, которое появилось в версиях .NET 3.5, 3.0 SP1 и 2.0 SP1 - свойство TrySkipIisCustomErrors. Оно указывает IIS7, игнорировать ли стандартный обработчик ошибок, разрешая пользовательскую обработку, или обрабатывать ошибки сугубо стандартными средствами. При этом, "ручное" выставление этого свойства ни к чему не приводит. По все видимости, это свойство играет сугубо сервисную роль и используется самой инфраструктурой ASP.NET. Да и завязка на наличие SP у .NET как-то не очень вселяет оптимизм.

Впрочем, все решилось достаточно изящно. В документации к этому свойству нашлось:

The TrySkipIisCustomErrors property is used only when your application is hosted in IIS 7.0. When running in Classic mode in IIS 7.0 the TrySkipIisCustomErrors property default value is true. When running in Integrated mode, the TrySkipIisCustomErrors property default value is false.

Дейтсвительно, стоило для приложения указать использование Classic Pool вместо Default Pool, и все заработало точно так же, как на старом добром IIS 6.0 :)

далее...

26 Апрель 2007 г.

Метапрограммирование в Nemerle.

Развитие идей метапрограммирования всегда было неким Граалем для индустрии программного обеспечения. Известно правило, что на сложность решаемых задач напрямую зависит от уровня абстракции, на котором строится его решение. Согласитесь, сложно было бы разрабатывать экспертные сестемы в терминах ассемблера. Поэтому-то и были разработаны технологии логического программирования, реализующие достаточную терминологическую базу для решения задач в области принятия решений. Вот техника метапрограммирования как дает в руки разработчиков неплохое средство повышения уровня абстракции при разработке систем путем расширения выразительных способностей компилятора.

Язык программирования Nermele во многом известен именно благодаря своей мощной системе метапрограммирования, которая достаточно органично вписывается в мир .NET, позволяя использовать язык со всеми его возможностями уже "здесь и сейчас". Язык предоставляет возможность разработки расширений к процессу компиляции с помощью максросов, написанных на этом же языке и подключаемых к нужному проекту как обычная библиотека.

Для того, чтобы опробовать все эти возможности, достаточно установить последний билд компилятора и интеграции с Visual Studio. Скачать это можно с сайта проекта интеграции. Установив Nemerle и интеграцию, в студии вам будет доступна новая группа проектов.

Пример, который мы рассмотрим будет достаточно простым и хорошо всем занкомым. Предположим, вы смоделировали предметную область и подошло время воплотить эту модель в коде. Каждая сущность этой модели будет представленная в виде неких персистентных классов. Кроме логики домена вам необходимо реализизовать т.н. логику приложения, которая включает в себя механизмы ORM, Concurrency и Audit Trail. Логика приложения привносит в каждый класс вашей модели свой специфичный набор полей:

  • Каждый класс должен иметь поле идентификатора. Конечно, эта задача решается путем наследования от некого супер-класса. Но наследование может сыграть злую роль в задачах передачи данных за границы приложения. Например, в случаях какой-то экзотической сериализации. В случае распределенного приложения вообще лучше всего иметь классы с минимальной детализацией для передачи между подсистемами. Поэтому идеальным случаем было бы добавление поля идентификатора к каждому классу в домене;
  • В задаче совместнго доступа нам нужно поле с временной меткой записи, по которой мы будем определять перед каждой операцией записи, была ли сущность изменена кем-то еще;
  • Для простейшей системы аудита нам необходимо добавить поля, в которых будет хранится информация о том, кто запись создал и кто ее в последний раз изменял.

Итак, мы имеем 6 однотипных полей, которые мы должны добавлять в каждый создаваемый класс. Это и есть та самая рутина, которая не требует принятия каких-либо сложных решений и которую, благодаря этому, очень легко автоматизировать.

Пусть мы имеем следущий класс:

public class Enumeration
{
    mutable value : string;
    mutable description : string;
}

Его аналогом в C# был бы следующий код:

public class Enumeration
{
    string value;
    string description;
}

В этом классе не хватает публичных свойств к заданным полям. Это еще одна из рутинных операций, которые подлежат автоматизации. Для решения этой задачи мы воспользуемся готовым макросом, которые поставляется с компилятором:

public class Enumeration
{
    [Accessor (flags = WantSetter)]
    mutable value : string;
    [Accessor (flags = WantSetter)]
    mutable description : string;
}

Все просто: максросами-атрибутами вы указываете компилятору на то, что к данным полям необходимое сгенерировать свойства, причем, свойства должны быть с set-аксессором. Скомпилируем этот код и посмотрим рефлектором на то, как бы этот код выглядел на C#:

public class Enumeration
{
    // Fields
    private string description;
    private string value;

    // Properties
    public string Description
    {
        [DebuggerStepThrough]
        get
        {
            return this.description;
        }
        [DebuggerStepThrough]
        set
        {
            this.description = value;
        }
    }

    public string Value
    {
        [DebuggerStepThrough]
        get
        {
            return this.value;
        }
        [DebuggerStepThrough]
        set
        {
            this.value = value;
        }
    }
}

Но вернемся к нашим полям. С точки зрения разработчика было бы удобвно наделить класс Enumeration всем необходимым свойствами с помощью похожего макроса-атрибута:

[PersistentObject]
public class Enumeration
{ ....

И выглядеть этот макрос будет следующим образом (для краткости мы будем добавлять только идентификатор):

using Nemerle;
using Nemerle.Utility;

[MacroUsage (MacroPhase.WithTypedMembers, MacroTargets.Class)]
macro PersistentObject (t : TypeBuilder)
{
    t.Define(<[ decl: 
        [Accessor]
        mutable id : Guid;
    ]>);
}

Как видите, ничем сложным разработка макросов не является. Изучим этот пример детальнее.

Атрибутом MacroUsage мы указываем компилятору на то, что макрос применяется в виде атрибута к классу и применяется на этапе, когда класс "наделен" всеми свойствами классов-предков. Так же мы можем "встроиться" в процесс компиляции до того, как класс будет унаследован.

Обязательным первым параметром макросов, которые применяются как атрибуты к классам является переменная типа TypeBuilder. Эта переменная указывает на объект, умеющий манипулировать целевым типом. Если бы наш макрос применялся к полям, то первый параметр был бы типа FieldBuilder.

Единственной операцией в нашем макросе является вызов методв Define у переменной типа TypeBuilder. Этот метод принимает в качестве параметра экземпляр синтаксического дерева, соответствующего коду, который мы хотим добавить в наш класс. Мы можем, конечно же создать это экземпляр вручную, добавляя все синтаксические элементы, как узлы этого дерева. Но Nemerle предоставляет нам возможность избежать этого, позволяя нам описать не код, строящий дерево, а код, который и должен быть представлен в виде дерева. Эта возможность называется "цитированием". Для цитирования используется подобная конструкция:

def ast : PExpr = 
<[ 
    /* Код, который будет представлен в виде дерева */ 
]>

Обратите внимание, что мы внутри макроса использовали другой макрс, а именно, известный нам Accessor. Таким образом класс  Enumeration будет расширен приватным полем и свойствам к нему.

Забегая вперед, хочется отметить, что возможности макросистемы Nemerle позволяют добавлять новые классы в сборки, основанные на метаданных. Например, мы можем расширить наш макрос PersistentObject так, чтобы он для каждого класса генерировал все необходимые классы DAO и даже подготавливал базу данных. И все это на этапе компиляции!...

далее...

12 Март 2007 г.

Выходные с Nemerle

Наконец-то дошли руки до популярного ныне в своих кругах языка программирования Nemerle. За развитием этого языка я слежу давно, наблющаю за активным обсуждением этого языка на сайте RSDN. Пробовал писать на этом языке какие-то вещи, но дальше примеров дело не заходило. На сей раз к этому языку меня подтолкнуло любопытство, связанное с парадигомой функционального программирования. Как практику, мне необходимо было выбрать язык программирования для своих экспериментов, а Nemerle изначально был заявлен, как поддерживающий функциональное программирование, да и один из его предков язык ML функционален по определению. Ну и хотелось иметь язык, который нативно впишется в среду .NET. Ну и его "гибридность" теоретически может позволить "быстрый ввод" и легкое "переключение" между парадигмами в зависимости от решаемых задач. Кроме того, система метапрограммирования, реализованная в этом языке, делают его настоящим комбайном, выразительные способности которого ограничиваются исключительно фантазией пишущих на нем. К слову стоит отметить, что большая часть исходников Nemerle написана на самом Nemerle.

Итак, в качестве первого "функционального" примера я решил попробовать реализовать функцию для быстрой сортировки. Этот пример очень показателен для функционального программирования, так как очень здорово иллюстрирует идею о том, что функциональные программы близки внешне к описанию формулы в математической нотации, описывающей результат, чем к пошаговому описанию алгоритма, приводящего к заданному результату в случае императивного программирования. Чтобы проверить эту идею, в принципе, достаточно ознакомится с реализацией этого алгоритма на разных языках в этой статье. Сравните реализацию на С и на Haskell. Впечатляет?

Итак, в общем случае метод сортировки описывается следующей формулой:

   quickSort([]) = []
 qs

Нам ничего не остается, кроме как просто описать эту формулу в несколько иной нотации, а другими словами - реализовать на языке Nemerle:

def qs(l)
{
    match(l)
    {
        | [] => [];
        | x :: xs  => qs( $[ y | y in xs, y < x ] ) + [x] + qs( $[y | y in xs, y >= x] );
    }
}

Неправда ли, очень похоже на исходную формулу?

Ну и пример использования:

System.Console.WriteLine(qs([1, 9, 6, 96, 9, 6, 69, 698, 98, 69, 89, -228, -234, -27, -23, -23, 63407075, 003, 069, 036, 306, 3096]));

Результатом сортировки будет:

[-234, -228, -27, -23, -23, 1, 3, 6, 6, 9, 9, 36, 69, 69, 69, 89, 96, 98, 306, 698, 3096, 63407075]

Ура, первый эксперимент удался! Будем продолжать наши опыты и результат сразу же опубликовывать.

В качестве источника научной информации в данном эксперементе использовался цикл лекций "Основы функционального программирвоания".

далее...

Создание собственного дистрибутива Microsoft SQL Server Express

Все разработчики desktop-приложений, работающих с базами данных, всегда сталкивались с проблемами установки этой самой базы данных на машины пользователей. Сценариев, которые можно реализовать для решения данной задачи достаточно много:

  • Использование файловых баз данных, таких, как Microsoft Access, SQLite, Paradox - разворачивание базы данных представляет собой обычное копирование неких эталонных файлов в какое-нибудь общедоступное место;
  • Использование существующих серверов баз данных - в этом случае нужно либо в момент установки, либо в момент запуска приложения настроить соединение, сохранить параметры и создать структуру базы данных;
  • Установка собственных серверов баз данных вместе с приложением - этот вариант я и рассмотрю ниже.

В качестве сервера, который мы будем "встраивать" в наше приложение мы выберем Microsoft SQL Server Express. По функциональным возможностям эта редакция сервера аналогична SQL Server 2005 Standard Edition, с некоторыми ограничениями по размеру базы данных, количеству используемой памяти и процессоров. Но для небольших приложений всех этих возможностей будет более чем достаточно. Нужно отметить, что эта редакция намного удобнее в использовании предыдущей версии MSDE 2000.

Рассмотрим следующий сценарий развёртывания:

  1. Пользователь устанавливает приложение самым обычным образом (пусть даже просто вручную копирует бинарные файлы);
  2. При первом запуске приложение проверяет наличие сконфигурированной базы данных и, если таковая не обнаруживается, то пользователю предлагается установить и настроить сервер баз данных;
  3. Если пользователь соглашается, то запускается процесс установки сервера баз данных с заданными параметрами безопасности, названием экземпляра сервера и паролем для пользователя sa; при этом процесс установки должен быть максимально скрыт от пользователя для того, чтобы не нагружать его лишней информацией о приложении;
  4. На только что установленном сервере создаем базу данных, выполнив установочный DDL-скрипт.

Способы определения наличия уже установленной базы данных и последующее создание новой базы мы отложим и рассмотрим подробнее третий пункт нашего сценария.

Для осуществления нашего плана мы должны иметь в наличии некий исполняемый файл, запустив который из нашего приложения мы и получим по завершению установленный экземпляр SQL Server с заданными параметрами.

Итак, рассмотрим процесс по шагам.

  1. Скачиваем дистрибутив с сайта Microsoft SQL Server Express.
  2. Распаковываем установочные файлы.
    На этом сайте доступны версии, упакованные в некий файл вида SQLEXPR32_RUS.EXE, а нам нужно получить доступ непосредственно к файлу Setup.exe, который можно будет запустить с заданными параметрами. В этом месте мы должны выполнить фокус: дело в том, что перво-наперво скачанный инсталятор просто распаковывает содержимое инсталляционного пакета во временную директорию. Наша задача эти файлы оттуда перехватить. Задача это достаточно проста: после запуска инсталляции этот путь можно получить из такого окошка: 

    Распаковка файлов

    После того, как запустится непосредственно сам инсталлятор из архива, копируем содержимое этот папки, потому как после завершения работы инсталлятора - эта папка будет удалена.
  3. Конфигурация инсталлятора.
    В директории с инсталлятором вы найдете файл template.ini, в котором можно прописать все необходимые параметры установки по умолчанию. Дело в том, что правильно сделанный инсталятор должен уметь принимать все параметры установки из командной строки для того, чтобы всю эту установку автоматизировать. Помогает это в тех случаях, когда, например, от конечного пользователя нужно спрятать всю рутину, связанную с этим процессом, доверившись параметрам по умолчанию. Ведь даже опытные пользователи в 90% случаев установки в основном нажимают кнопку Next, не изменяя параметров по умолчанию.
    В этом файле можно указать все параметры, которые запрашиваются в диалогах. А для того, чтобы запустить установку с этими параметрами нужно указать инсталлятору, откуда брать эти параметры. Это делается с помощью такого запуска:

    setup.exe /qb /settings c:\template.ini

    • Ключ /qb указывает инсталлятору, что нужно принять все параметры и не дать пользователю их изменить через всякие диалоги. Таким образом пользователь будет отслеживать процесс установки, но не сможет ничего изменить. Альтернативный этому ключ /qn указывает инсталлятору, что процесс установки нужно проводить полностью в фоновом режиме.
    • Ключ /settings указывает, что параметры установки следует взять из указанного файла.
    Альтернативой этому способу является указание всех параметр прямо в командной строке:

    setup.exe /qn ADDLOCAL=SQL_Engine SAPWD=password SECURITYMODE=SQL

    Таким образом можно установить экземпляр сервера по умолчанию с заданным паролем для пользователя sa в совершенно невидимом режиме. Завершающем штрихом в этом пункте будет создание некого Install.bat со строкой запуска установки с заданными параметрами. Но мы на этом не остановимся.
  4. Выбор архиватора.
    Если вы помните, скачанный дистрибутив был представлен в виде исполняемого файла SQLEXPR32_RUS.EXE, размер которого был во много раз меньше развёрнутого инсталлятора. Наша задача - получить в результате нечто подобное, с той лишь разницей, что запуск нашего дистрибутива сразу начнёт установку с заданными параметрами. Опустив сравнительный анализ разных архиваторов, остановимся на замечательном архиваторе 7-zip. Для нашей задачи он показался мне наиболее подходящим.
    Следующим шагом будет создание архива максимальной степени сжатия примерно с такими параметрами: 

    Добавить в архив

    Размер архива получился около 36 мегабайт, что очень близко к оригинальному дистрибутиву.
  5. Создание SFX-архива.
    В предыдущем пункте мы сознательно не создали SFX-архив сразу, потому что нам нужна не просто возможность распаковать архив на произвольной машине, а распаковать в директорию по умолчанию и запустить после установки на выполнение установку с заданными параметрами. Все эти задачи делаются легко и просто с помощью существующих SFX-модулей. Детали того, как это делается с помощью стандартных средств 7-zip можно найти по этому адресу. Но мы воспользуемся альтернативным модулем, найти который можно по этому адресу. 
    Из скачанного пакета альтернативных модулей выбираем файл 7ZSD_LZMA.sfx и помещаем его рядом с архивом, который мы хотим "оживить". Если у вас есть желание, то можно с помощью утилиты ResourceHacker и этой заметки поменять иконку у SFX модуля на Иконка , предварительно извлечённую с помощью все того же ResourceHacker-а из инсталлятора Microsoft SQL Server Express. Затем размещаем в этой же директории текстовый файл Config.txt такого содержимого:

    ;!@Install@!UTF-8!
    RunProgram="setup.exe /qn ADDLOCAL=SQL_Engine SAPWD=password SECURITYMODE=SQL"
    GUIMode="2"
    ;!@InstallEnd@!


    После чего в командной строке выполняем следующую команду:

    copy /b 7ZSD_LZMA.sfx + config.txt + SQL2005E.7z SQL2005E.exe

    Файл SQL2005E.exe, полученный в результате этой операции и будет нашим дистрибутивом, который можно распространять со своим приложением.
  6. Интеграция в приложение.
    Этот пункт, пожалуй, самый простой. В зависимости от того, каким образом вы собираетесь устанавливать Microsoft SQL Server Express (в момент установки приложения или после проверки при каждом запуске) и будет проходить интеграция. Это может быть либо Custom Action для Windows Installer, либо в коде вашего приложения будет присутствовать подобный код:
    ProcessStartInfo psi = new ProcessStartInfo();
    psi.FileName = filePath; 
    Process p = new Process();
    p.StartInfo = psi;
    p.Start();
    p.WaitForExit(); 
    if (p.ExitCode != 0)
       throw new ApplicationException("Ошибка при установке");

Более подробную информацию о встраивании Microsoft SQL Server Express в свои приложения можно найти в статье Embedding SQL Server Express into Custom Applications.

далее...