Nincs jobb, mint amikor az embernek van egy olyan szkriptje kéznél, ami egyszerűen és átláthatóan teszi a dolgát és igazán megkönnyíti a fejlesztő munkáját. Az egyik gyakori feladat az adatbázis létrehozása, amit két módon lehet megtenni:
- Adatbázis attachelésével. Ez egyszerű, csak éppen a változtatásokat nehéz követni benne fejlesztés közben. Tipikusan akkor választja az ember ezt a megoldást, amikor azt hiszi, hogy ezzel kevesebb gondja lesz, de később kiderül, hogy mégse.
- SQL szkriptekkel. Ez kényelmesen szerkeszthető és verzionálható, csak éppen az a kérdés, hogyan fognak egyszerűen lefutni az SQL szkriptek.
Az utóbbi az érdekesebb, hogyan futtatunk SQL szkripteket parancssorból? Korábban az osql.exe vagy az isql.exe volt a jópajtás, az SQL Server 2005-től kezdve ezek helyét átvette az SQLCMD.
Például ha Windows Workflow Foundationt használunk, valószínűleg szükségünk lesz tracking és persistence adatbázisokra, amikhez csak az SQL szkripteket kapjuk a Microsofttól. Nosza gyűrjük be az alábbi néhány sort egy cmd fájlba és máris lehet duplán kattintva adatbázist gyártani (eltördeltem a hosszú sorokat):
SQLCMD -S .\SqlExpress -Q "CREATE DATABASE MyDB"
SQLCMD -S .\SqlExpress -d MyDB -i "C:\WINDOWS\Microsoft.NET\Framework\v3.0\
Windows Workflow Foundation\SQL\EN\SqlPersistenceService_Schema.sql"
SQLCMD -S .\SqlExpress -d MyDB -i "C:\WINDOWS\Microsoft.NET\Framework\v3.0\
Windows Workflow Foundation\SQL\EN\SqlPersistenceService_Logic.sql"
SQLCMD -S .\SqlExpress -d MyDB -i "C:\WINDOWS\Microsoft.NET\Framework\v3.0\
Windows Workflow Foundation\SQL\EN\Tracking_Schema.sql"
SQLCMD -S .\SqlExpress -d MyDB -i "C:\WINDOWS\Microsoft.NET\Framework\v3.0\
Windows Workflow Foundation\SQL\EN\Tracking_Logic.sql"
Eddig egyszerű volt, de mi van akkor, ha saját tracking profile-t is akarunk használni? Erről annyit érdemes tudni, hogy a profil egy XML fájl, aminek a tartalmát az UpdateTrackingProfile tárolt eljárás segítségével kell egy táblába betöltenünk.
Általánosabban megfogalmazva a feladatot: hogyan tudunk egy olyan tárolt eljárást futtatni, aminek egyik bemeneti paramétere egy fájl teljes tartalma?
A megoldás első lépését a BULK INSERT T-SQL utasítás jelenti. Ennek megadhatunk egy fájl útvonalat, aminek a tartalmát be tudja tölteni egy táblába. Sajnos van néhány nyűgje:
- Csak tábla típusú változót szeret, tehát nem elég egy DECLARE, csak CREATE TABLE jó neki.
- A betöltendő fájl útvonalát nem lehet változóban megadni, oda kell írni aposztrófok közé.
- Ha egy kulturáltan megformázott XML-t akarunk betölteni, akkor minden egyes sor külön rekordba kerül, ami persze nem jó, ha végül egyetlen cellában szeretnénk látni az eredményt.
Íme a megoldás:
CREATE TABLE #temp( profileXml nvarchar(max) )
BULK INSERT #temp
FROM '$(ProfilePath)'
WITH
(
ROWTERMINATOR = '<<<' -- dummy terminator, hogy az egész fájlt felolvassa
)
Csinálunk tehát egy temp táblát, mert mindenképp tábla kell. Az egész fájl beolvasását úgy oldjuk meg, hogy olyan sor elválasztó karaktert adunk meg, ami biztosan nincs a fájlban. A kérdés már csak az, hogy mi a $(ProfilePath)?
Az SQLCMD egyik remek szolgáltatása, hogy lehet változókat megadni, amiket ő behelyettesít. A fenti aposztrófok közé például így varázsolhatunk értéket (a példa kedvéért elhagytam a többi paramétert):
SQLCMD -v ProfilePath="profile.xml"
Ezzel megint csak gondunk lesz: a BULK INSERT panaszkodik, hogy nem teljes elérési utat adtunk meg. Kérdés: hogyan lehet egy cmd fájlban megtudni az aktuális mappa elérési útvonalát? Meglepő módon a cd parancs és a cd környezeti változó épp ezt adja vissza. Szerencsére az SQLCMD is tud környezeti változókat kezelni, így csak ennyit kell módosítanunk:
SQLCMD -v ProfilePath="%cd%\profile.xml"
Nem maradt más hátra, mint az UpdateTrackingProfile tárolt eljárás meghívása, amihez a már jól ismert EXEC utasítást használjuk. A profil XML-en kívül át kell neki adnunk egy TypeFullName, egy AssemblyName, és egy Version paramétert, melyeknél ugyanúgy járhatunk el, mint a fenti ProfilePath esetén. Ezek típusát és hosszát a tárolt eljárásból lehet kilesni.
Végül nekem a teljes SQL szkript így ez lett:
-- Bemeneti paraméterek
DECLARE @TypeFullName nvarchar(128)
DECLARE @AssemblyName nvarchar(256)
DECLARE @Version varchar(32)
SET @TypeFullName = '$(TypeFullName)'
SET @AssemblyName = '$(AssemblyName)'
SET @Version = '$(Version)'
-- Belső változók
DECLARE @profileXml nvarchar(max)
CREATE TABLE #temp( profileXml nvarchar(max) )
-- Tracking profile betöltése fájlból temp táblába
BULK INSERT #temp
FROM '$(ProfilePath)'
WITH
(
ROWTERMINATOR = '<<<' -- dummy terminator, hogy az egész fájlt felolvassa
)
-- Tracking profile XML kiolvasása temp táblából
SELECT @profileXml = profileXml FROM #temp
-- Tracking profile adatbázisba mentése
EXEC dbo.UpdateTrackingProfile @TypeFullName, @AssemblyName, @Version, @profileXml
-- Temp tábla törlése
DROP TABLE #temp
És a cmd fájl, ami felépíti a teljes adatbázist és beimportálja a profilt (eltördeltem a hosszú sorokat):
SQLCMD -S .\SqlExpress -Q "CREATE DATABASE SignowDB"
SQLCMD -S .\SqlExpress -d MyDB -i "C:\WINDOWS\Microsoft.NET\Framework\v3.0\
Windows Workflow Foundation\SQL\EN\SqlPersistenceService_Schema.sql"
SQLCMD -S .\SqlExpress -d MyDB -i "C:\WINDOWS\Microsoft.NET\Framework\v3.0\
Windows Workflow Foundation\SQL\EN\SqlPersistenceService_Logic.sql"
SQLCMD -S .\SqlExpress -d MyDB -i "C:\WINDOWS\Microsoft.NET\Framework\v3.0\
Windows Workflow Foundation\SQL\EN\Tracking_Schema.sql"
SQLCMD -S .\SqlExpress -d MyDB -i "C:\WINDOWS\Microsoft.NET\Framework\v3.0\
Windows Workflow Foundation\SQL\EN\Tracking_Logic.sql"
SQLCMD -S .\SqlExpress -d MyDB -i "InsertTrackingProfile.sql" -v Version=1.0.0
-v TypeFullName="MyNamespace.MyWorkflow1"
-v AssemblyName="MyLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=443f6c5b661c8da2"
-v ProfilePath="%cd%\profile1.xml"
SQLCMD -S .\SqlExpress -d MyDB -i "InsertTrackingProfile.sql" -v Version=1.0.0
-v TypeFullName="MyNamespace.MyWorkflow2"
-v AssemblyName="MyLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=443f6c5b661c8da2"
-v ProfilePath="%cd%\profile2.xml"
A legjobb az egészben, hogy mindezt nem csak fejlesztés közben lehet használni, hanem ezt a szkriptet be lehet építeni a telepítő alkalmazásba is.