dinsdag 22 oktober 2013

Import bacpac

Sinds een tijdje kent Windows Sql Azure een soort van backup mogelijkheid. Het komt erop neer dat Windows Azure een dump van de database maakt. Deze dump wordt opgeslagen in een backup package (bacpac) die je vervolgens overal naartoe kun slepen. Zo'n bacpac importeren is kinderlijk eenvoudig. In SqlServer kies je de optie "Import Data-tier Application" en in Azure kies je bij de databases de optie "Import". In beide gevallen moet je een bacpac opgeven en de zaak gaat verder vanzelf. 
Helaas is de praktijk weerbarstig. Recentelijk had ik een dump van de database nodig om te testen en wie schets mijn verbazing? De import werkt niet. Ik kreeg de volgende foutmelding:

Error SQL72045: Script execution error.  The executed script:
DECLARE @c AS INT = 1;

WHILE @c = 1
    BEGIN
        DELETE TOP (1000)
               [Core].[Brand];
        IF @@rowcount < 1000
            SET @c = 0;

    END

Op zich wazig want deze code kon ik in geen enkel script terugvinden. Kennelijk is dit iets dat de import-utility uitvoert. Na enig zoekwerk kwam ik erachter dat de importactie triggers uitvoert en één trigger veroorzaakte daarbij een probleem. Tja, daar zit je dan met je bacpac. 
Na enig onderzoek bleek zo'n bacpac gewoon een zip-bestand te zijn. Even de extensie veranderen naar .zip en je kunt hem uitpakken. In dit pakket vind je een aantal bestanden. De .BCP-bestanden zijn bulkcopy bestanden en bevatten de data. Daarnaast heb je nog een aantal xml-bestanden, waaronder model.xml en Origin.xml. In model.xml zit de definitie van jouw database en dus ook de definitie van de triggers. Als je de probleemtrigger er nu eens gewoon uit haalt en hem later handmatig toevoegt? Het is het proberen waard. Het bestand weer zippen en opnieuw proberen te importeren. Helaas! Nu gaat het meteen mis. Mijn wijziging heeft ervoor gezorgd dat de checksum van de package niet meer klopt. Die checksum vind je in Origin.xml. Het fragment in kwestie staat hieronder.


<Checksums>   
     <Checksum Uri="/model.xml">E64500DB5070F3FF8F639D2A2570824976B3746DEAC8914E9B82652FD3696173</Checksum>
</Checksums>

Kennelijk is de checksum alleen op model.xml toegepast hetgeen de zaak aanzienlijk vereenvoudigt en zo te zien is er een 256 bits algoritme toegepast. Nou, hoeveel kunnen dat er zijn? Laten we eens het SHA-256 algoritme uitproberen. Hieronder de listing hoe je dat in C# uitwerkt.


static void Main(string[] args)
{
    SHA256Managed sha256 = new SHA256Managed();
    byte[] hash;
    using (FileStream stream = File.OpenRead("model.xml"))
    {
        hash = sha256.ComputeHash(stream);
    }
    StringBuilder outputString = new StringBuilder();
    foreach (byte b in hash)
    {
        outputString.AppendFormat("{0:x2}", b);
    }
    Console.WriteLine(outputString.ToString().ToUpper());
    Console.ReadLine();
}
Nadat de bestaande hash was vervangen voor de gegenereerde hash en de bestanden weer gezipped waren, werkte het meteen. Overigens hoef je de .zip extensie niet eens terug te veranderen in bacpac. De "Import Data-tier Application" tool blijkt het allemaal prima te begrijpen.
Natuurlijk nog wel even de verwijderde elementen toevoegen aan de geïmporteerde database en de hele zaak draait weer als voorheen.