DacFx/SSDT public model #2. DACFx API: work with package

Api

Продолжаем рассказ о DACFx/SSDT (начало тут). Сегодня мы поговорим о том, как программно манипулировать пакетом (DACPAC) как единым целым, т.е. без анализа содержимого.

К сожалению, пока полноценной документации (я не имею в виду документацию по библиотеке классов – она присутствует), по использованию DACFx API не очень много, но все же есть. Неплохим введением в предмет может послужить статья DacFx Public Model Tutorial, которая является по-сути, предваряющей документацией к набору примеров по DacFx http://dacsamples.codeplex.com/.

Остальное, увы, это отдельные статьи в MSDN, форумах и блогах.

Ну да ладно, будем приступать и так, но прежде чем знакомиться с самим API, давайте поговорим о том, где лежат сборки DACFx и как их можно подключить в свой проект.

Где найти DACFx?

DACFx не использует размещение своих сборок в GAC, а размещает их по пути “%ProgramFiles(x86)%\Microsoft SQL Server\<SQL_Version>\DAC\bin\”. Например, для DACFx для 2014 сервера у меня установлен по пути “C:\Program Files (x86)\Microsoft SQL Server\120\DAC\bin\”.

Второе место, где можно найти DACFx – папка расширения SSDT, расположенная вместе с остальными расширениями внутри папки Visual Studio. В частности у меня это “C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\Extensions\Microsoft\SQLDB\DAC\120\”.

Наличие 2-х мест где размещен DACFx, не сильно важно, если вы собираетесь просто прочитать содержимое DAC-пакета или что-то сделать с моделью. Но оно становится важным, когда вы разрабатываете расширения для DACFx, т.к. они должны размещаться в одной из подпапок DACFx.

Поэтому, если вы не планируете использовать консольные утилиты типа SqlPackage, то самым оптимальным вариантом я считаю загрузку сборок DACFx из репозитория NuGet.

Теперь можно и разобраться с основными элементами API DACFx.

Работа с пакетом

Для работы с пакетами предназначены 2 класса с вполне говорящими названиями:

  • Microsoft.SqlServer.Dac.BacPackage
  • Microsoft.SqlServer.Dac.DacPackage

По функционалу и использованию классы различаются довольно значительно. Хотя есть и ряд общих черт. Из них самое основное, что нам нужно знать:

  • Для открытия (загрузки в память) пакета используется статический метод Load()
  • Все остальные значимые операции с пакетом собраны в отдельный сервисный класс Microsoft.SqlServer.Dac.DacServices

Вот простейший пример деплоя базы через API DacFx:

public void SimpleDeploy()
{
    var dacServices = 
		new DacServices(@"Data Source=(local);Integrated Security=True");

    using (var dacPackage = DacPackage.Load("Northwind.dacpac"))
    {                
        dacServices.Deploy(dacPackage, "Northwind");
    }
}

Для более тонкого управления параметрами деплоя предназначаются два следующих класса:

  • Microsoft.SqlServer.Dac.DacDeployOptions – для задания опций деплоя из кода
  • Microsoft.SqlServer.Dac.DacProfile – для чтения настроек деплоя из файла профиля

Файл профиля можно создать из студии

image

в которой есть вполне приличный редактор параметров деплоя:image

image

Причем, в случае собственного кода вы можете легко реализовать сценарий “прочитать настройки из профиля, но некоторые критичные жестко задать из кода”:

public void DeployWithProfile()
{
    var profile = DacProfile.Load("Northwind.publish.xml");
    var dacServices = new DacServices(profile.TargetConnectionString);

    var deployOptions = profile.DeployOptions;
    deployOptions.CreateNewDatabase = false;

    using (var package = DacPackage.Load("Northwind.dacpac"))
    {
        dacServices.Deploy(
            package,
            profile.TargetDatabaseName,
            upgradeExisting: true,
            options: deployOptions);
    }

}

Что ещё полезного можно сделать через класс DacServices? Вот краткий перечень оставшихся методов:

ExportBacpac() /
ImportBacpac()
Экспорт / импорт схемы вместе со всеми данными. Основное назначение – перенос данных + схемы в облако (SQL Azure) и обратно.
Extract() Операция, обратная деплою – извлекает и сохраняет в виде пакета модель указанной базы
GenerateCreateScript() / GenerateDeployScript() Генерация TSQL-скрипта по модели из пакета. Разница между методами в том, что первый генерирует скрипт создания базы с 0 (а потому не требует ничего кроме исходного пакета), а второй – скрипт обновления существующей базы (поэтому ему нужно подключение к базе или пакет, который её заменит).
GenerateDeployReport() Генерация отчета об изменениях, которые внесет устанавливаемый пакет, а также набор сгенерированных предупреждений (например, что добавляемое Not Null поле не имеет default значения, а значит при наличии в таблице данных, скрипт обновления упадет с ошибкой).
GenerateDriftReport()

Тоже отчет, но уже об изменениях в самой базе. Данный метод работает только для зарегистрированных Data-Tier Applications и делает следующее:

  • Извлекает из метаданных SQL Server пакет, из которого развертывалась последний раз база
  • Сравнивает модель в пакете и текущую базу
  • Формирует список изменений

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

Register() / Unregister() Регистрирует / разрегистрирует уже существующую базу как Data-Tier Application

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

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

<?xml version="1.0" encoding="utf-8"?>
<DeploymentReport xmlns="http://schemas.microsoft.com/sqlserver/dac/DeployReport/2012/02">
  <Alerts>
    <Alert Name="DataMotion">
      <Issue Value="[dbo].[Categories]" />
    </Alert>
    <Alert Name="DataIssue">
      <Issue Value="The column [dbo].[Categories].[CategoryName_1] is being dropped, 
				data loss could occur." Id="1" />
      <Issue Value="The column [dbo].[Categories].[CategoryName] on table [dbo].[Categories] must be added, 
					but the column has no default value and does not allow NULL values. 
					If the table contains data, the ALTER script will not work. To avoid this issue you must either: 
					add a default value to the column, mark it as allowing NULL values, or enable the generation 
					of smart-defaults as a deployment option." Id="2" />
      <Issue Value="The column [dbo].[Employees].[Email1] is being dropped, data loss could occur." Id="3" />
    </Alert>
  </Alerts>
  <Operations>
    <Operation Name="Rename">
      <Item Value="[dbo].[Employees].[ReportsTo1]" Type="SqlSimpleColumn" />
    </Operation>
    <Operation Name="Create">
      <Item Value="[Northwind]" Type="SqlAssembly" />
      <Item Value="[dbo].[Categories].[CategoryName]" Type="SqlIndex" />
      <Item Value="[dbo].[CustOrdersDetail]" Type="SqlProcedure" />
      <Item Value="[dbo].[CustOrdersOrders]" Type="SqlProcedure" />
      <Item Value="[dbo].[Procedu--re1]" Type="SqlProcedure" />
      <Item Value="[dbo].[SqlStoredProcedure1]" Type="SqlProcedure" />
      <Item Value="[dbo].[FK_Products_Categories]" Type="SqlForeignKeyConstraint" />
    </Operation>
    <Operation Name="TableRebuild">
      <Item Value="[dbo].[Categories]" Type="SqlTable">
        <Issue Id="2" />
      </Item>
    </Operation>
    <Operation Name="Alter">
      <Item Value="[dbo].[Employees]" Type="SqlTable">
        <Issue Id="3" />
      </Item>
      <Item Value="[dbo].[Alphabetical list of products]" Type="SqlView" />
      <Item Value="[dbo].[Product Sales for 1997]" Type="SqlView" />
      <Item Value="[dbo].[Products by Category]" Type="SqlView" />
      <Item Value="[dbo].[Sales by Category]" Type="SqlView" />
      <Item Value="[dbo].[Category Sales for 1997]" Type="SqlView" />
      <Item Value="[dbo].[SalesByCategory]" Type="SqlProcedure" />
    </Operation>
    <Operation Name="Drop">
      <Item Value="[dbo].[FK_Products_Categories]" Type="SqlForeignKeyConstraint" />
    </Operation>
    <Operation Name="Refresh">
      <Item Value="[dbo].[Invoices]" Type="SqlView" />
      <Item Value="[dbo].[Employee Sales by Country]" Type="SqlProcedure" />
    </Operation>
  </Operations>
</DeploymentReport>


Из интересного в этом примере – констрейнт FK_Products_Categories, упоминается дважды: как удаляемый и создаваемый. Т.е. он будет сначала удален, а затем пересоздан повторно.

А вот, Deploy Report для того же самого пакета, базы и сервера, но с опцией пересоздавать пакет с 0. Как видите, здесь ровно одни операции создания:

<?xml version="1.0" encoding="utf-8"?>
<DeploymentReport xmlns="http://schemas.microsoft.com/sqlserver/dac/DeployReport/2012/02">
  <Alerts />
  <Operations>
    <Operation Name="Create">
      <Item Value="[Northwind]" Type="SqlAssembly" />
      <Item Value="[dbo].[Categories]" Type="SqlTable" />
      <Item Value="[dbo].[Categories].[CategoryName]" Type="SqlIndex" />
      <Item Value="[dbo].[CustomerCustomerDemo]" Type="SqlTable" />
      <Item Value="[dbo].[CustomerDemographics]" Type="SqlTable" />
      <Item Value="[dbo].[Customers]" Type="SqlTable" />
      <Item Value="[dbo].[Customers].[City]" Type="SqlIndex" />
      <Item Value="[dbo].[Customers].[CompanyName]" Type="SqlIndex" />
      <Item Value="[dbo].[Customers].[PostalCode]" Type="SqlIndex" />
      <Item Value="[dbo].[Customers].[Region]" Type="SqlIndex" />
      <Item Value="[dbo].[Employees]" Type="SqlTable" />
      <Item Value="[dbo].[Employees].[LastName]" Type="SqlIndex" />
      <Item Value="[dbo].[Employees].[PostalCode]" Type="SqlIndex" />
      <Item Value="[dbo].[EmployeeTerritories]" Type="SqlTable" />
      <Item Value="[dbo].[Order Details]" Type="SqlTable" />
      <Item Value="[dbo].[Order Details].[OrderID]" Type="SqlIndex" />
      <Item Value="[dbo].[Order Details].[OrdersOrder_Details]" Type="SqlIndex" />
      <Item Value="[dbo].[Order Details].[ProductID]" Type="SqlIndex" />
      <Item Value="[dbo].[Order Details].[ProductsOrder_Details]" Type="SqlIndex" />
      <Item Value="[dbo].[Orders]" Type="SqlTable" />
      <Item Value="[dbo].[Orders].[CustomerID]" Type="SqlIndex" />
      <Item Value="[dbo].[Orders].[CustomersOrders]" Type="SqlIndex" />
      <Item Value="[dbo].[Orders].[EmployeeID]" Type="SqlIndex" />
      <Item Value="[dbo].[Orders].[EmployeesOrders]" Type="SqlIndex" />
      <Item Value="[dbo].[Orders].[OrderDate]" Type="SqlIndex" />
      <Item Value="[dbo].[Orders].[ShippedDate]" Type="SqlIndex" />
      <Item Value="[dbo].[Orders].[ShippersOrders]" Type="SqlIndex" />
      <Item Value="[dbo].[Orders].[ShipPostalCode]" Type="SqlIndex" />
      <Item Value="[dbo].[Products]" Type="SqlTable" />
      <Item Value="[dbo].[Products].[CategoriesProducts]" Type="SqlIndex" />
      <Item Value="[dbo].[Products].[CategoryID]" Type="SqlIndex" />
      <Item Value="[dbo].[Products].[ProductName]" Type="SqlIndex" />
      <Item Value="[dbo].[Products].[SupplierID]" Type="SqlIndex" />
      <Item Value="[dbo].[Products].[SuppliersProducts]" Type="SqlIndex" />
      <Item Value="[dbo].[Region]" Type="SqlTable" />
      <Item Value="[dbo].[Shippers]" Type="SqlTable" />
      <Item Value="[dbo].[Suppliers]" Type="SqlTable" />
      <Item Value="[dbo].[Suppliers].[CompanyName]" Type="SqlIndex" />
      <Item Value="[dbo].[Suppliers].[PostalCode]" Type="SqlIndex" />
      <Item Value="[dbo].[Territories]" Type="SqlTable" />
      <Item Value="[dbo].[DF_Order_Details_Discount]" Type="SqlDefaultConstraint" />
      <Item Value="[dbo].[DF_Order_Details_Quantity]" Type="SqlDefaultConstraint" />
      <Item Value="[dbo].[DF_Order_Details_UnitPrice]" Type="SqlDefaultConstraint" />
      <Item Value="[dbo].[DF_Orders_Freight]" Type="SqlDefaultConstraint" />
      <Item Value="[dbo].[DF_Products_Discontinued]" Type="SqlDefaultConstraint" />
      <Item Value="[dbo].[DF_Products_ReorderLevel]" Type="SqlDefaultConstraint" />
      <Item Value="[dbo].[DF_Products_UnitPrice]" Type="SqlDefaultConstraint" />
      <Item Value="[dbo].[DF_Products_UnitsInStock]" Type="SqlDefaultConstraint" />
      <Item Value="[dbo].[DF_Products_UnitsOnOrder]" Type="SqlDefaultConstraint" />
      <Item Value="[dbo].[FK_CustomerCustomerDemo]" Type="SqlForeignKeyConstraint" />
      <Item Value="[dbo].[FK_CustomerCustomerDemo_Customers]" Type="SqlForeignKeyConstraint" />
      <Item Value="[dbo].[FK_Employees_Employees]" Type="SqlForeignKeyConstraint" />
      <Item Value="[dbo].[FK_EmployeeTerritories_Employees]" Type="SqlForeignKeyConstraint" />
      <Item Value="[dbo].[FK_EmployeeTerritories_Territories]" Type="SqlForeignKeyConstraint" />
      <Item Value="[dbo].[FK_Order_Details_Orders]" Type="SqlForeignKeyConstraint" />
      <Item Value="[dbo].[FK_Order_Details_Products]" Type="SqlForeignKeyConstraint" />
      <Item Value="[dbo].[FK_Orders_Customers]" Type="SqlForeignKeyConstraint" />
      <Item Value="[dbo].[FK_Orders_Employees]" Type="SqlForeignKeyConstraint" />
      <Item Value="[dbo].[FK_Orders_Shippers]" Type="SqlForeignKeyConstraint" />
      <Item Value="[dbo].[FK_Products_Categories]" Type="SqlForeignKeyConstraint" />
      <Item Value="[dbo].[FK_Products_Suppliers]" Type="SqlForeignKeyConstraint" />
      <Item Value="[dbo].[FK_Territories_Region]" Type="SqlForeignKeyConstraint" />
      <Item Value="[dbo].[CK_Birthdate]" Type="SqlCheckConstraint" />
      <Item Value="[dbo].[CK_Discount]" Type="SqlCheckConstraint" />
      <Item Value="[dbo].[CK_Quantity]" Type="SqlCheckConstraint" />
      <Item Value="[dbo].[CK_UnitPrice]" Type="SqlCheckConstraint" />
      <Item Value="[dbo].[CK_Products_UnitPrice]" Type="SqlCheckConstraint" />
      <Item Value="[dbo].[CK_ReorderLevel]" Type="SqlCheckConstraint" />
      <Item Value="[dbo].[CK_UnitsInStock]" Type="SqlCheckConstraint" />
      <Item Value="[dbo].[CK_UnitsOnOrder]" Type="SqlCheckConstraint" />
      <Item Value="[dbo].[Alphabetical list of products]" Type="SqlView" />
      <Item Value="[dbo].[Current Product List]" Type="SqlView" />
      <Item Value="[dbo].[Customer and Suppliers by City]" Type="SqlView" />
      <Item Value="[dbo].[Invoices]" Type="SqlView" />
      <Item Value="[dbo].[Order Details Extended]" Type="SqlView" />
      <Item Value="[dbo].[Order Subtotals]" Type="SqlView" />
      <Item Value="[dbo].[Orders Qry]" Type="SqlView" />
      <Item Value="[dbo].[Product Sales for 1997]" Type="SqlView" />
      <Item Value="[dbo].[Products Above Average Price]" Type="SqlView" />
      <Item Value="[dbo].[Products by Category]" Type="SqlView" />
      <Item Value="[dbo].[Quarterly Orders]" Type="SqlView" />
      <Item Value="[dbo].[Sales by Category]" Type="SqlView" />
      <Item Value="[dbo].[Sales Totals by Amount]" Type="SqlView" />
      <Item Value="[dbo].[Summary of Sales by Quarter]" Type="SqlView" />
      <Item Value="[dbo].[Summary of Sales by Year]" Type="SqlView" />
      <Item Value="[dbo].[Category Sales for 1997]" Type="SqlView" />
      <Item Value="[dbo].[CustOrderHist]" Type="SqlProcedure" />
      <Item Value="[dbo].[CustOrdersDetail]" Type="SqlProcedure" />
      <Item Value="[dbo].[CustOrdersOrders]" Type="SqlProcedure" />
      <Item Value="[dbo].[Employee Sales by Country]" Type="SqlProcedure" />
      <Item Value="[dbo].[Procedu--re1]" Type="SqlProcedure" />
      <Item Value="[dbo].[Sales by Year]" Type="SqlProcedure" />
      <Item Value="[dbo].[SalesByCategory]" Type="SqlProcedure" />
      <Item Value="[dbo].[SqlStoredProcedure1]" Type="SqlProcedure" />
      <Item Value="[dbo].[Ten Most Expensive Products]" Type="SqlProcedure" />
    </Operation>
  </Operations>
</DeploymentReport>

Ну и пример небольшого Drift отчета:

<DriftReport xmlns="http://schemas.microsoft.com/sqlserver/dac/DriftReport/2012/02">
  <Additions>
    <Object Name="[CategoryName_1]" Parent="[dbo].[Categories]" Type="SqlSimpleColumn" />
    <Object Name="[Email1]" Parent="[dbo].[Employees]" Type="SqlSimpleColumn" />
    <Object Name="[CategoryName_1]" Parent="[dbo].[Alphabetical list of products]" Type="SqlComputedColumn" />
    <Object Name="[CategoryName_1]" Parent="[dbo].[Category Sales for 1997]" Type="SqlComputedColumn" />
    <Object Name="[CategoryName_1]" Parent="[dbo].[Product Sales for 1997]" Type="SqlComputedColumn" />
    <Object Name="[CategoryName_1]" Parent="[dbo].[Products by Category]" Type="SqlComputedColumn" />
    <Object Name="[CategoryName_1]" Parent="[dbo].[Sales by Category]" Type="SqlComputedColumn" />
  </Additions>
  <Removals>
    <Object Name="[CustOrdersDetail]" Parent="[dbo]" Type="SqlProcedure" />
    <Object Name="[CustOrdersOrders]" Parent="[dbo]" Type="SqlProcedure" />
    <Object Name="[CategoryName]" Parent="[dbo].[Categories]" Type="SqlSimpleColumn" />
    <Object Name="[Email]" Parent="[dbo].[Employees]" Type="SqlSimpleColumn" />
    <Object Name="[CategoryName]" Parent="[dbo].[Alphabetical list of products]" Type="SqlComputedColumn" />
    <Object Name="[CategoryName]" Parent="[dbo].[Category Sales for 1997]" Type="SqlComputedColumn" />
    <Object Name="[CategoryName]" Parent="[dbo].[Product Sales for 1997]" Type="SqlComputedColumn" />
    <Object Name="[CategoryName]" Parent="[dbo].[Products by Category]" Type="SqlComputedColumn" />
    <Object Name="[CategoryName]" Parent="[dbo].[Sales by Category]" Type="SqlComputedColumn" />
  </Removals>
  <Modifications>
    <Object Name="[SalesByCategory]" Parent="[dbo]" Type="SqlProcedure" />
    <Object Name="" Parent="[dbo].[Categories].[CategoryName]" Type="SqlIndexedColumnSpecification" />
    <Object Name="[CategoryName]" Parent="[dbo].[Categories]" Type="SqlIndex" />
    <Object Name="[Categories]" Parent="[dbo]" Type="SqlTable" />
    <Object Name="[Employees]" Parent="[dbo]" Type="SqlTable" />
    <Object Name="[Alphabetical list of products]" Parent="[dbo]" Type="SqlView" />
    <Object Name="[Category Sales for 1997]" Parent="[dbo]" Type="SqlView" />
    <Object Name="[Product Sales for 1997]" Parent="[dbo]" Type="SqlView" />
    <Object Name="[Products by Category]" Parent="[dbo]" Type="SqlView" />
    <Object Name="[Sales by Category]" Parent="[dbo]" Type="SqlView" />
  </Modifications>
</DriftReport>

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

This entry was posted in Разработка ПО and tagged , , , . Bookmark the permalink.

2 Responses to DacFx/SSDT public model #2. DACFx API: work with package

  1. RoyalSaleGold says:

    Доброго времени суток Михаил ,а если базу выгрузить с облака в какой формат она ложится на HDD ?…

    • Если выгружать средствами DacFx, то это будет формат BACKPACK. В принципе, это тот же формат, что и DACKPACK, только помимо схемы там еще лежат данные.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s