Zine.net online

Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc
w Szukaj

dev2dev

  • Html2Db: Co się stało z reprezentacją Kenii?

    Na wss.pl pojawił się wątek dotyczący przekształcenia danych tabelarycznych ze strony HTML na tabelę w bazie danych. W dyskusji wyraziłem wątpliwość co do trywialności rozwiązania ze względu na fakt, że zawartość stron HTML odbiega znacznie od poprawnych dokumentów XML (a do zapisu do bazy danych chciałem wykorzystać możliwości XML w SQL Server). Jednak jak się okazało istnieje świetny helper do dokumentów HTML, który znajduje się pod tym adresem: htmlagilitypack.

    Jako cel swego przekształcenia wybrałem ranking FIVB drużyn męskich.

    Dzięki zastosowaniu helpera przekształcenie response HTML w XML jest banalnie proste:

    WebClient wc = new WebClient();
    byte[] bytes = wc.DownloadData("http://www.fivb.org/en/volleyball/Rankings/Rank_men_2009_11.asp");

    UTF8Encoding utf8 = new UTF8Encoding();
    string response = utf8.GetString(bytes);

    HtmlDocument doc = new HtmlDocument();
    doc.LoadHtml(response);
    doc.OptionOutputAsXml = true;
    doc.Save(@"d:\wymiana\fivb.xml");



    Po przekształceniu w dokument XML i po zwinięciu nieistotnych elementów strony docieramy do istotnych danych tabelarycznych zawierających ranking:



    Teraz tylko jeszcze jedno "wygładzające" przekształcenie w zupełnie czysty i czytelny dokument zawierający wyłącznie dane rankingowe: nazwa drużyny, aktualne miejsce, poprzednie miejsce oraz ilość punktów (nie jest to konieczne, SQL Serer poradziłby sobie i z takim dokumentem):

    HtmlDocument doc = new HtmlDocument();
    doc.Load(@"d:\wymiana\fivb.xml");

    HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("/span/html/body/center/table[4]/tr[1]/td[2]/table[1]/tr[5]/td[1]/table[1]/tr");

    HtmlDocument output = new HtmlDocument();

    HtmlNode ranking = output.CreateElement("ranking");

    int index = 0;
    foreach (HtmlNode node in nodes)
    {
        if (index > 3)
        {
            HtmlNode n = output.CreateElement("team");
            HtmlNode name = output.CreateElement("name");
            HtmlNode current = output.CreateElement("current");
            HtmlNode previous = output.CreateElement("previous");
            HtmlNode points = output.CreateElement("points");

            name.InnerHtml = node.ChildNodes[7].InnerText;
            current.InnerHtml = node.ChildNodes[3].InnerText;
            previous.InnerHtml = node.ChildNodes[5].InnerText;
            points.InnerHtml = node.ChildNodes[9].InnerText;

            n.AppendChild(name);
            n.AppendChild(current);
            n.AppendChild(previous);
            n.AppendChild(points);

            ranking.AppendChild(n);
        }

        index++;
    }

    output.DocumentNode.AppendChild(ranking);

    File.Delete(@"d:\wymiana\ranking.xml");

    output.Save(@"d:\wymiana\ranking.xml");

    I teraz możemy przepompować ranking z dokumentu XML do tabeli SQL Server:

    CREATE TABLE #t (html nvarchar(max))

    BULK INSERT #t FROM 'D:\Wymiana\ranking.xml'

    DECLARE @txt nvarchar(max) = ''

    SELECT @txt = @txt + ' ' + ISNULL(html, '') FROM #t

    SET @txt = SUBSTRING(@txt,2,2147483647)

    DECLARE @xml xml = CAST(@txt AS xml)

    DECLARE @t table(current_rank int, previous_rank int, team nvarchar(100), points numeric)

    INSERT INTO @t
    SELECT    t.c.value('(./current)[1]', 'int') current_rank,
            replace(
                replace(
                    replace(t.c.value('(./previous)[1]', 'nvarchar(10)'),'(','')
                ,')',''),
            'N.Rkd','') previous_rank,
            t.c.value('(./name)[1]', 'nvarchar(100)') team,
            t.c.value('(./points)[1]', 'numeric') total
    FROM @xml.nodes('/ranking/team') t(c)

    UPDATE @t SET previous_rank = (SELECT MAX(current_rank)+1 FROM @t) WHERE previous_rank = 0

    SELECT previous_rank-current_rank, team from @t order by 1

    DROP TABLE #t


    Zrobiłem ten ranking i okazało się, że największy spadek w rankingu zanotowała reprezentacja Kenii, bo spadła aż o 73 miejsca. Ale nic nie byłoby w tym dziwnego gdyby nie fakt, że obecnie ma zero punktów.

    W takim razie ile miała w poprzednim notowaniu gdy była na 43 miejscu? Czyżby jakaś kara czy może tylko błąd wprowadzania danych?

    Edit:

    Pisałem powyżej, że kod XML otrzymany z helpera można bezpośrednio wykorzystać w kodzie T-SQL. Aby nie być gołosłownym podaje ten kod:
    INSERT INTO @t
    SELECT    t.c.value('(./td)[2]', 'int') current_rank,
            replace(
                replace(
                    replace(t.c.value('(./td)[3]', 'nvarchar(10)'),'(','')
                ,')',''),
            'N.Rkd','') previous_rank,
            t.c.value('(./td)[4]', 'nvarchar(100)') team,
            t.c.value('(./td)[5]', 'numeric') total
    FROM @xml.nodes('/span/html/body/center/table[4]/tr[1]/td[2]/table[1]/tr[5]/td[1]/table[1]/tr[fn:position() gt 4]') t(c)

    Jak widać po tych przykładach przekształceń przetworzenie kodu HTML zawierającego dane tabelaryczne wcale nie jest trudne.
  • Zanim się zdziwisz - Inserting Multiple Rows Using a Single INSERT Statement

    Właściwie to powinien być kolejny wpis do mojego dziennika pokładowego MSSQL 2008 ale ostatecznie uznałem, że dziennik pokładowy będzie pokazywał raczej dziwne przypadki użycia ;-)

    Wersja 2008 ma bardzo fajną nową funkcjonalność w postaci generatora listy dodawanych wierszy poprzez INSERT. Więcej na ten temat można znaleźć tutaj.

    Chciałem zastosować tę funkcjonalność do przetworzenia przecinkowej listy w tablicę zawierającą poszczególne elementy tej listy. Elementy tej listy miałby być wyrażeniami dowolnych typów.

    Skroiłem więc procedurę składowaną, która będzie przekształcała tę listę w postać dogodną do użycia przez tę funkcjonalność oraz która będzie ostatecznie wstawiała elementy tej listy do tabeli wynikowej (zakładam, że na innym poziome będzie się odbywała kontrola czy lista nie zawiera przypadkiem SQL Injection):

    CREATE PROCEDURE List2Table
    (
        @list nvarchar(max)
    )
    AS BEGIN

    CREATE TABLE #t(value SQL_VARIANT)

    SET @list = '('+REPLACE(@list, ',','),(')+')'

    DECLARE @sql nvarchar(max) = N'INSERT INTO #t VALUES '+@list

    EXECUTE sp_executesql @stmt = @sql

    SElECT value FROM #t

    DROP TABLE #t

    END

    Ale uruchomienie procedury dla przykładowych danych testowych:

    DECLARE @list nvarchar(max) = N'3,getdate(),234.56,newid(),''text'''

    EXECUTE List2Table @list = @list

    kończy sie komunikatem błędu:

    Msg 206, Level 16, State 2, Line 1
    Operand type clash: uniqueidentifier is incompatible with datetime

    (0 row(s) affected)

    Co ma piernik do wiatraka? Przecież utworzyłem tabelę z kolumną typu SQL_VARIANT, która powinna “łyknąć” wszystko jak leci.

    W takiej sytuacji trzeba zajrzeć do planu wykonania. Ale do tego celu musimy wykorzystać przykład bez dynamicznego SQL ponieważ plan zapytania procedury z dynamicznym SQL nie daje satysfakcjonującej odpowiedzi na przyczynę takiego zachowania. Czyli na przykład dla takiego zapytania:

    declare @t table (x sql_variant)

    insert into @t values (2),( 'abc'), (GETDATE()), (2.345)

    Robiąc  różne kombinacje z typami na liście doszedłem do wniosku, że działa to w ten sposób:

    • parser analizuje listę wyszukując w niej wyrażenie o typie z najwyższym priorytetem,
    • następnie tworzy rzutowania wszystkich elementów listy na ten typ,
    • a na zakończenie rzutuje uzyskane powyżej wyniki poszczególnych wartości z listy na docelowy typ kolumny w tabeli.

    przy takim podejściu to rzeczywiście będą błędy. Moim zdaniem powinno być tak, że generator planu zapytania buduje rzutowania poszczególnych wartości z listy ale na typ SQL_VARIANT a dopiero potem te wartości próbuje zapisać do tabeli. SQL_VARIANT ma najwyższy priorytet w związku z tym  błąd może wystąpić na próbie zapisu do tabeli a nie na etapie przygotowania wartości do zapisu ale to już zupełnie inna sytuacja.

    W wyniku tego jeżeli wszystkie elementy listy będą miały jednakowy typ (jakikolwiek) i będą zgodne z typem tabeli docelowej lub mające implikowany operator rzutowania wszystko działałoby “po staremu” a jednocześnie można byłoby mieć bardziej “otwartego” INSERT’a możliwością wykorzystania nowej funkcjonalności generatora rekordów.

  • Mój pierwszy raz… – https://connect.microsoft.com

    Zgłosiłem pierwszy raz usterkę na https://connect.microsoft.com. Zgłoszenie jest tutaj. Zgłoszenie dotyczyło sytuacji gdy zapytanie ma zwrócić rezultat będący różnicą wyrażenia typu datetime2 oraz wyrażenia typu datetime. Ponieważ datetime2 ma wyższy priorytet od wyrażenia datetime to wynik powinien być typu datetime2 a szczególnie co do dokładności wyniku zapytania. Że tak nie jest wystarczy uruchomić to zapytanie:

    select cast('2009-11-20 19:03:17.4030123' as datetime2) - cast(1 as datetime)

    Paweł Potasiński uzupełnił mój wpis o usterce celną uwagą, że wynik tego zapytania wcale nie jest typu datetime2 lecz datetime co łatwo sprawdzić za pomocą tego zapytania:

    select sql_variant_property(cast('2009-11-20 19:03:17.4030123' as datetime2) - cast(1 as datetime), 'BaseType');

    Niestety ale usterka została została zamknięta ze statusem “Według projektu”. Jeżeli uważasz, że tak nie jest to zagłosuj na “Ocenione jako ważne” dla tej usterki.

  • Jaki będzie wynik zapytania: SELECT GETDATE() - GETDATE()?

    Na wss.pl pojawił się wątek dotyczący działań na typach datetime.
    Jeden z uczestników dyskusji stwierdził, że operacja odejmowania dwóch wartości datetime daje w wyniku czas.
    Jak pokazałem, zapytanie

    SELECT GETDATE() - GETDATE()

    daje w wyniku 1900-01-01 00:00:00.000 czyli nadal typ datetime (należało się tego spodziewać) a dopiero po jawnym zrzutowaniu na float widzimy numeryczną reprezentację z różnicy dat (dlaczego nie na time, o tym mała uwaga na końcu notki). Ale pomyślałem sobie, że w pewnych warunkach wynik może być dodatni lub ujemny i to niedeterministycznie. Postanowiłem sprawdzić jak jest naprawdę.

    Zrobiłem sobie funkcję:

    ALTER FUNCTION retard
    (
    )
    RETURNS datetime
    AS
    BEGIN

    DECLARE @i int = 0

    WHILE (@i < 10000000) set @i = @i+1

    RETURN '1900-01-01 00:00:00.000'

    END


    i zrobiłem zapytanie:

    select GETDATE()

    select dbo.retard()

    select GETDATE()




    Na moim komputerze opóźnienie było kilkusekundowe. Wtedy zrobiłem kolejne zapytanie:

    SELECT GETDATE() - dbo.retard() - GETDATE()


    Ale tym razem wynik był zerowy (czyli 1900-01-01 00:00:00.000).

    Hmm, dziwne. Po obejrzeniu planu zapytania widać, że dwa odwołania do funkcji getdate() generują ConstExpr1001 i ConstExpr1002 ale są one wartościowane w bliskim sobie czasie i server taktuje je jako stałe znane mu w czasie wykonania i w związku z tym wartościowane przed wartościowaniem innym składników zapytania.

    Skoro tak to opakujmy funkcję getdate() o tak:

    CREATE FUNCTION myGetdate
    (
    )
    RETURNS datetime
    AS
    BEGIN
    RETURN getdate()
    END



    i zróbmy zapytanie:
    select dbo.mygetdate() - dbo.retard() - dbo.mygetdate()


    Oooo i teraz jest efekt!
    I widać z tego, że przy prostych operatorach arytmetycznych wartościowanie jest w kolejności wystąpienia (bo wynik jest mniejszy od 1900-01-01 00:00:00.000) . Hmm, skoro tak to zróbmy tak:

    select dbo.mygetdate() - cast((dbo.retard() + dbo.mygetdate() ) as int)



    Tym razem wynik jest większy od 1900-01-01 00:00:00.000 czyli drugi człon odejmowania jest wartościowany wcześniej. Ale nie ustając w poszukiwaniach zróbmy jeszcze takie zapytanie:


    select dbo.mygetdate() - cast((dbo.retard() + dbo.mygetdate() ) as datetime)



    Ale tym razem mimo, że spodziewalibyśmy się że drugi człon odejmowania będzie wartościowany wcześniej to wynik odejmowania jest mniejszy od 1900-01-01 00:00:00.000.

    Zaglądając do planu zapytania widzimy, że mimo zastosowaliśmy jawne rzutowanie to parser zignorował je ponieważ wiedział, że nie ma takiej potrzeby skoro wszystkie składniki są typu datetime.

    Jak widać po tych rozważaniach wynik zapytania będącego treścią tego wpisu jest trudny do przewidzenia. A przy okazji widać, że XML plan prawdę Ci powie ;-)


    Rzutowanie różnicy dat na typ time.

    Typ time ma dziedzinę wartości (dla maksymalnego rozmiaru time(7)) od 00:00:00.0000000 do 23:59:59.9999999 wobec tego nie istnieje coś takiego jak ujemny czas. Rzutowanie typu datetime na time zwraca z rezultatu datetime jedynie część informującą o czasie.Wobec tego zapytanie

    SELECT cast(getdate() - (getdate()+1) AS time(7))

    Zwróci wynik 00:00:00.0000000, podobnie jak zapytanie

    SELECT cast((getdate()+1) - getdate() AS time(7))

    Ale zapytanie

    SELECT cast(getdate() - (getdate()+0.3333333333333) AS time(7))

    zwróci wynik 16:00:00.0030000 podczas gdy zapytanie

    SELECT cast((getdate()+0.3333333333333) - getdate() AS time(7))

    zwróci wynik 07:59:59.9970000.

    Dlatego operując na interwałach daty i czasu należy jednak polegać na funkcji DATEDIFF. Aby się przekonać co do następstwa dat (czyli wiedzieć czy tyle czasu upłynęło czy tyle czasu upłynie) wystarczyłoby wiedzieć jaki jest znak wyniku tej funkcji dla największej granulacji skali czasowej, czyli dla mikrosekund.

    Czyli możemy zbudować zapytanie:

    DECLARE @interval float = -0.000001
    DECLARE @d1 datetime = getdate()
    DECLARE @d2 datetime = getdate()+@interval
    DECLARE @diff int = DATEDIFF(MICROSECOND, @d1, @d2)

    IF (@diff > 0) print 'Trzeba czekać...'
    ELSE print 'Minęło...'


    I wszystko było pięknie dopóki nie ustawimy absolutnej wartości zmiennej @interwal na większą od 0.02485517 (tak wyszło z moich doświadczeń chociaż teoretycznie powinno to być więcej niż 0.0248551348032). Wówczas cała zabawa kończy się komunikatem

    Msg 535, Level 16, State 0, Line 12
    The datediff function resulted in an overflow. The number of dateparts separating two date/time instances is too large. Try to use datediff with a less precise datepart.


    Co jest oczywiste ze względu na fakt, że funkcja DATEDIFF jest typ int a zwracany wynik jest w mikrosekundach (dzień zawiera 86400000000 mikrosekund co jest poza zakresem tego typu).
  • Potrzeba matką wynalazków - skryptowanie uprawnień użytkownika

    Gdy zaistnieje potrzeba zeskryptowania uprawień określonego użytkownika to jednego można być pewnym, co już opisałem w poprzednim wpisie. Nie ma co liczyć na opcję Generate script. Bo nawet gdyby działała poprawnie to i tak niema takiej możliwości. Nic tylko pisać własny skrypt (albo jako proponował Paweł Potasiński sięgnąć po PowerShell'a, ale dla kogoś kto go nie zna jest to wyzwanie).

    A skrypt T-SQL dla tego zadania wcale nie jest trudny.

    DECLARE @user sysname = 'fajny_user'
    DECLARE @tekst varchar(max)

    SET NOCOUNT ON

    SET @tekst = (
                    SELECT    CHAR(10)+
                            dp.state_desc COLLATE Latin1_General_CI_AS+' '+
                            dp.permission_name + ' ON ' +
                            ISNULL(QUOTENAME(s.name)+'.','')+ QUOTENAME(o.name) + ' TO ' +
                            QUOTENAME(su.name)+';'
                    FROM sys.database_permissions dp
                    INNER JOIN sys.sysusers su on dp.grantee_principal_id = su.uid
                    INNER JOIN sys.objects o on dp.major_id = o.object_id
                    INNER JOIN sys.schemas s on o.schema_id = s.schema_id
                    WHERE su.name = @user
                    ORDER BY o.name
                    FOR XML PATH ('' )
                )

    PRINT @tekst


    Przy okazji można zobaczyć jak użyteczna jest klauzula "FOR XML PATH". I na zakłdace Messages mamy wszystko co trzeba :-)

    Edit:

    Po uwzględnieniu słusznej uwagi Pawła Potasińskiego kod będzie uboższy o jeden INNER JOIN:

    DECLARE @user sysname = 'fajny_user'
    DECLARE @tekst varchar(max)

    SET NOCOUNT ON

    SET @tekst = (
                    SELECT    CHAR(10)+
                            dp.state_desc COLLATE Latin1_General_CI_AS+' '+
                            dp.permission_name + ' ON ' +
                            ISNULL(QUOTENAME(OBJECT_SCHEMA_NAME(o.object_id))+'.','')+ QUOTENAME(o.name) + ' TO ' +
                            QUOTENAME(su.name)+';'
                    FROM sys.database_permissions dp
                    INNER JOIN sys.sysusers su on dp.grantee_principal_id = su.uid
                    INNER JOIN sys.objects o on dp.major_id = o.object_id
                    WHERE su.name = @user
                    ORDER BY o.name
                    FOR XML PATH ('' )
                )

    PRINT @tekst


    Teraz wystarczy dodać to do własnego SSMS addin'a i jedno skryptowanie mamy "z głowy" ;-)
  • Dziennik pokładowy MSSQL 2008 (wpis 0x0011) - Problem z Generate Script...

    Mając przed sobą bazę liczącą setki tabel oraz setki procedur składowanych postanowiłem zobaczyć ile zajmie pełny jej skrypt. Uruchomiłem więc opcję Task->Generate Scripts… z menu kontekstowego bazy danych. uruchomiłem więc tę opcję i po pewnym czasie na ekranie ukazał się komunikat:



    Po kliknięciu na link Exception type… pokazał się kolejny dramatyczny komunikat:




    Pomyślałem sobie, niemożliwe, że taki błąd przydarzył się tylko mi. Googlownica po chwili zwróciła mi kilka linków, które opisują podobny problem, z których wybrałem dwa:

    http://www.experts-exchange.com/Microsoft/Development/MS-SQL-Server/Q_24845124.html

    http://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=378085

    Jak widać drugi link jest zgłoszeniem problemu do Microsoft.

    Nie zrażając się tym problemem wziąłem na tapetę tym razem dużo mniejszą bazę. Zaznaczyłem wszystkie obiekty do generowania. Uruchomiłem, wszystko poszło OK. Uruchomiłem wygenerowany skrypt na innym serwerze i… totalna klapa. Brakowało kilku tabel! Ponowiłem więc jeszcze raz generowanie skryptu dla wszystkich obiektów (tym razem sprawdzając wszystko dwa razy) ale nic to nie zmieniło: tabel nadal brakowało. Zastanawiałem się czym się te tabele od innych różnią stwierdziłem, że zawierają triggers’y i constraints’y. Ale co to może przeszkadzać? Wygenerowałem więc małą bazę z z dwiema tabelkami “2x2” :) dodałem constrains i trigger, uruchomiłem generate scripts na tej bazie i tu kolejne zdziwienie.




    Dlaczego error? kliknąłem w link Failed to retrieve… i nie mogłem uwierzyć własnym oczom:



    MSSQL tworząc skrypt dla bazy test domagał się dostępu do innej bazy (która przez przypadek była offline, ale to już chyba raczej błąd ConnectionInfo).

    Trzy takie przypadki z jednej opcji to trochę za dużo. Gdy sobie przypomnę prostotę generowania skryptów w wersji 2000 to trudno sie oprzeć refleksji, że “lepsze jest wrogiem dobrego”. Do czasu pojawienia się jawnie określonej poprawki do tej opcji stanowczo odradzam korzystania z niej.



    opublikowano 19 listopada 2009 20:57 przez marekpow | 5 komentarzy
    Filed under: , ,
  • Service Broken – SSMS addin do zarządzania obiektami Service Broker’a

    Jakiś czas temu po spotkaniu PLSSUG poświęconym Service Broker’owi pozwoliłem sobie napisać kilka refleksji na ten temat. A ponieważ sesja skończyła się przed czasem, więc Paweł Potasiński swoją refleksję na ten temat zatytułował “Service Broken”. Jedna z moich refleksji dotyczyła braku spójnego środowiska diagnostycznego do Service Broker’a. Natomiast mnogość poleceń do zarządzania i ich stopień skomplikowania kazał się zawsze odwoływać do MSDN co znakomicie utrudniało korzystanie z niego. A ponieważ równocześnie zainteresowałem się tworzeniem wtyczek do SSMS więc postanowiłem stworzyć wtyczkę, która wypełnia tę lukę. I tak powstał Service Broken – wtyczka do SSMS do zarządzania obiektami Service Broker’a. Wtyczka jest dostępna pod adresem http://servicebroken.codeplex.com/. Zapraszam do odwiedzania i instalowania :)

  • Zrób to sam – SSMS addin (http://nextssmsaddin.codeplex.com/)

    Rama do tworzenia wtyczek do SSMS została opublikowana pod adresem http://nextssmsaddin.codeplex.com/. Zapraszam do odwiedzania, korzystania, pobierania, komentowania, krytykowania, poprawiania, ulepszania (chętni do współtworzenia mogą się do mnie zgłaszać przez kontakt na tej stronie w celu dopisania do zespołu).

    Wtyczka została napisana w Visual Studio 2005. Ale działa pod SSMS 2005 i 2008 (również w wersji Express).

    Podsumowanie wszystkich wpisów w kolejności (bez inwokacji :) )

    1. Start
    2. EventWatcher
    3. ObjectExplorer, ConnectionString, Command
    4. Menu
    5. Exec
    6. Window
    7. The end
    8. http://nextssmsaddin.codeplex.com/
  • Zrób to sam – SSMS addin (The end)

    I to już koniec serii o tworzeniu wtyczki do SSMS. W sześciu merytorycznych wpisach na moim blogu krok po kroku tworzyłem następną swoją wtyczkę do SSMS (stąd jej nazwa NextAddin). Zarysowałem ogólne schematy chyba wszystkich elementów niezbędnych do jej tworzenia. Jednak tworząc ją od początku kolejny raz przekonałem się, jak bardzo “kapryśny” może być ten proces. Dlatego jeszcze raz podkreślę szczególnie ważne sprawy, o których należy pamiętać i co na pewno zaoszczędzi nam sporo zdrowia.

    • Wtyczka powinna mieć opcję do resetowania i odinstalowania menu. Resetowanie menu jest koniczne wtedy, gdy dokonujemy zmian w menu wtyczki i chcemy aby stały się one aktywne. Odinstalowanie menu powinno być stosowane jedynie tuż przed odinstalowaniem wtyczki. Odinstalowanie menu bez odinstalowania wtyczki spowoduje, że menu wróci w następnej sesji SSMS.
    • Wszystkie niebezpieczne miejsca mogące generować wyjątki powinny być obejmowane nawiasami try-catch, w przeciwnym wypadku następne uruchomienie sesji SSMS obędzie się bez wywołania zdarzenia OnConnect wtyczki a co za tym idzie potencjalne (a i zapewne kinetyczne :))nieprawidłowe jej działanie.
    • W nazwach identyfikatorów menu wtyczki nie dodajemy kropek. Oryginalne polecenia SSMS zawierają kropki (np. “Query.Execute”) i chciałoby się zastosować to w naszej wtyczce, ale zawsze się to kończy trudnym do zrozumienia wyjątkiem.
    • Przykładowy kod wtyczki, który opisywałem zawiera zestaw metod przechwytujących różnorakie zdarzenia (opisałem to tutaj), do których są stosowne szkieletowe metody obsługi. Metody te można rozbudować o własny kod debugujący wyświetlający komunikaty w oknie Output Window z SSMS (nie działa w wersji Express, dziwne ale prawdziwe).
    • Mając kilka własnych wtyczek zainstalowanych trzeba mieć świadomość, że wpływają one na działanie naszej wtyczki. Może to być szczególnie dotkliwe, gdy debugujemy wtyczkę i nagle ni stąd ni z owąd dostajemy wyjątek kompletnie niezrozumiały, lub też (co gorsza) Visual Studio pokazuje kody źródłowe innej wtyczki, którą w tym momencie próbuje debugować. W wyniku tego nagle możemy stwierdzić, że mamy otwartych kilka plików o nazwie Connect.cs. Edytując niewłaściwy plik możemy spowodować totalne zawieszenie SSMS i tylko odinstalowanie wtyczek jest tu ratunkiem. Dlatego należy unikać pozostawienia kilku wtyczek w trybie debug. Najlepiej jednak mieć aktualnie włączoną jedną wtyczkę gdy jesteśmy w stanie debugowania jej (czyli tylko tą, nad którą obecnie pracujemy).

    I to główne problemy, z którymi się spotkałem. Ale pewnie nie wszystkie. Tak jak zaznaczyłem proces bywa bardzo kapryśny. Pisząc odcinek o Window natrafiłem na problem, że metoda CreateToolWindow2 wywodząca sie z EnvDTE kompletnie nie chciała zwracać referencji do utworzonej wewnątrz niej instancji okna wtyczki. Pomogło dodanie całego projektu obsługi okien z innego (działającego) projektu.

    Jest to końcowy wpis o tworzeniu wtyczek, ale nie koniec z wtyczkami do SSMS. Na www.codeplex.com zainicjowałem już projekt zawierający pełny kod źródłowy ramy  do tworzenia wtyczek. Muszę go trochę ogarnąć, uporządkować i skomentować. Jak to się stanie opublikuję go i na blogu podam do niego adres. Czyli ciąg dalszy nastąpi.

    Dzięki wszystkim czytelnikom tej serii. Nadzieję, że ktoś się tym jeszcze zainteresuje. Tak więc do zobaczenia na codeplex :)

    Linki:

    do następnego odcinka serii o tworzeniu wtyczki do SSMS

    do poprzedniego odcinka serii o tworzeniu wtyczki do SSMS

  • Zrób to sam - SSMS addin (Window)

    W tym odcinku zaprezentuję schemat dodawania własnych okien (jako obiektów UserControl) do wtyczki SSMS. Wykorzystam do tego celu okna, które zastosowałem w innej wtyczce do SSMS, która zarządzała obiektami Service Broker. Zawartość wyświetlana przez okna będzie zmieniała się dynamicznie przy zmianie dwóch kontekstów:

    • kontekst serwera – okno pokazujące obiekty Service Broker aktywne dla danego serwera, w kontekście którego aktualnie się znajdujemy w SSMS (z możliwością zmiany statusu danej instancji Service Broker),
    • kontekst bazy danych – okno pokazujące infrastrukturę obiektów Service Broker dla kontekstu aktualnie wybranej bazy danych w Object Explorer SSMS.

    I. Window

    Tak jak pisałem powyżej, nasze okno będzie komponentem UserControl z implementacją prostego interfejsu zapewniającego odświeżenie jego zawartości:

    using System;
    using System.Collections.Generic;
    using System.Text;

    namespace ToolWindows
    {
        public interface IRefreshData
        {
            void RefreshData();
        }
    }

    Kontrolka prezentująca obiekty Service Broker jest bardzo prosta. Składa się z kontrolki Menu oraz kontrolki DataGridView.

     

    Chociaż okienko odświeża się automatycznie opcja menu Refersh została dodana jedynie dla przykładu możliwości wstawiania własnego menu do okienka wtyczki. Metoda RefreshData dla tego okna jest bardzo prosta.

    public void RefreshData()
    {
        string connectionString = UtilitySqlTools.Current.ConnectionString();

        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            try
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine("SELECT   name AS [Database Name],");
                sb.AppendLine("         Upper(service_broker_guid) AS GUID,");
                sb.AppendLine("         is_broker_enabled [Is broker enabled?]");
                sb.AppendLine("FROM sys.databases");

                SqlDataAdapter da = new SqlDataAdapter();
                da.SelectCommand = new SqlCommand();
                da.SelectCommand.CommandText = sb.ToString();
                da.SelectCommand.CommandTimeout = 0;
                da.SelectCommand.CommandType = CommandType.Text;
                da.SelectCommand.Connection = connection;

                DataSet ds = new DataSet();
                da.Fill(ds);

                dgBrokers.DataSource = ds.Tables[0];
                dgBrokers.Columns["Is broker enabled?"].ReadOnly = false;
            }
            catch (Exception exception)
            {
                // Logger
            }

            connection.Close();
        }
    }

    W powyższym kodzie najbardziej istotne jest uzyskanie connection string do aktualnie wybranego w SSMS serwera. Sposób jego odzyskania został opisany wcześniej tutaj.

    Z punktu widzenia automatyki odświeżania zawartości tego okna przy zmianie kontekstu serwera to wszystko. Mając connection string jesteśmy w stanie w naszym okienku robić praktycznie wszystko “czego nie zabraniają pozostałe paragrafy” cytując za bohaterem książki “Paragraf 22”. W szczególności nasze okno może być bardzo rozbudowane (tak jak okno do infrastruktury obiektów Service Broker), którego nie będę tu analizować a jedynie pokażę go w działaniu na końcu tego odcinka.

    II. Jak dodać okno do SSMS?

    Mechanizm dodawania okien do SSMS jest bardzo prosty. Ogólna metoda tworzenia nowego ona na zakładce Tool Windows w SSMS jest następująca:

    private Window CreateToolWindow(    string typeName,
                                               string assemblyLocation,
                                               Guid uiTypeGuid,
                                               DTE applicationObject,
                                               AddIn addinInstance,
                                               string caption)
           {
               Windows2 win2 = applicationObject.Windows as Windows2;

               if (win2 != null)
               {
                   object controlObject = null;
                   Window toolWindow = win2.CreateToolWindow2( addinInstance,
                                                               assemblyLocation,
                                                               typeName,
                                                               caption,
                                                               "{" + uiTypeGuid.ToString() + "}",
                                                               ref controlObject);
                   toolWindow.Visible = true;
                   return toolWindow;
               }
               return null;
           }

    Parametrami tej metody są:

    • typeName – nazwy typu obiektu okna,
    • assemblyLocation – ścieżka do assembly,
    • uiTypeGuid – identyfikator obiektu,
    • applicationObject – instancja obiektu SSMS,
    • addinInstance – instancja obiektu wtyczki,
    • caption – caption naszego okienka na zakładce Tool Windows.

    Metoda CreateToolWindow2 tworzy nawą zakładkę na obszarze Tool Windows SSMS a jednocześnie w parametrze controlObject zwraca instancję obiektu typu typeName. Również właściwość toolWindow.Object zwraca referencję to tego obiektu co jest szczególnie istotne w naszych zastosowania do automatyzacji odświeżania zawartości okienek.

    Szczegółowa metoda dodawania naszego przykładowego okna z zawartością Service Brokers dla domyślnego serwera z SSMS jest niemniej prosta:

    public void CreateBrokersWindow(DTE application, AddIn addinInstance)
            {
                if (toolBrokersWindow == null)
                {
                    Assembly asm = Assembly.Load("ToolWindows");
                    Guid id = Guid.NewGuid();

                    toolBrokersWindow = CreateToolWindow(   "ToolWindows.Brokers",
                                                            asm.Location,
                                                            id,
                                                            application,
                                                            addinInstance,
                                                            brokersWindowCaption);

                    toolBrokersWindow.Height = 400;
                    toolBrokersWindow.IsFloating = false;
                    toolBrokersWindow.SetTabPicture(ToolsResource.Windows.GetHbitmap());
                    application.MainWindow.LinkedWindows.Add(toolBrokersWindow);
                    application.MainWindow.LinkedWindows.Item(brokersWindowCaption).AutoHides = true;
                }
            }

    Nasze okienko znajduje się w assembly ToolWindows i jest klasy ToolWindows.Brokers. Dodatkowo ustawiamy jego wysokość, żądamy aby nie było oknem pływającym (czyli dokowanym), Dodajemy ikonkę przy pomocy metody SetTabPicture (dodawałem obraz bitmap i metoda, którą tu zastosowałem jest skuteczna, inne metody okazały sie zawodne). Dodajemy nasze okienko do kolekcji LinkedWindows obiektu MainWindow w SSMS i żądamy aby ukrywało sie automatycznie.

    Teraz modyfikujemy metodę initializeAddin opisaną w poprzednich odcinkach dodając do niej kod:

    private void initializeAddIn()
    {
        try
        {
            if (Controller.Current.BrokersWindow == null)
                Controller.Current.CreateBrokersWindow(applicationObject, addInInstance);
        }
        catch (Exception exception)
        {
            // Logger
        }

    który tworzy i dodaje nasze okienko do SSMS.


    I to wszystko jeżeli chodzi o dodanie okienka do SSMS. Mechanizm ten można oczywiście zastosować z powodzeniem do dodawania okienek poprzez menu (tak jest w przypadku okienka prezentującego infrastrukturę obiektów Service Broker).

    III. Jak automatycznie odświeżać zawartość naszych okienek?

    Czytelnicy tej serii zapewne pamiętają, że w różnych miejscach kodu naszej wtyczki pojawiały się “zwinięte” regiony kodu o nazwach “Refresh database related windows” oraz “Refresh server related windows”. W te miejsca wstawiamy odpowiednio metody:

    • RefershBrowsers – do regionu “Refresh server related windows”,
    • RefreshInfrastructure – do regionu “Refresh database related windows”.

    Wspomniane wyżej metody wyglądają następująco:

    /// <summary>
    /// Refresh Service Brokers window
    /// </summary>
    private void RefreshBrokers()
    {
        if (Controller.Current.BrokersWindow != null)
        {
            ToolWindows.Brokers brokersWin =
                    (ToolWindows.Brokers)Controller.Current.BrokersWindow.Object;

            brokersWin.ConnectionString = dbContext.ConnectionString;

            brokersWin.RefreshData();
        }
    }

    private void RefreshInfrastructure()
    {
        if (Controller.Current.InfrastructureWindow != null)
        {
            ToolWindows.Infrastructure infraWin =
                    (ToolWindows.Infrastructure)Controller.Current.InfrastructureWindow.Object;

            infraWin.CurrentDatabase = dbContext.Database;

            infraWin.RefreshData();
        }
    }

    Jak to wygląda w działaniu? Do ObjectExplorer podłączamy dwa serewry przełączając sie między nimi zauważymy, że okno Browsers dopasowuje się dynamicznie do zmienionej sytuacji.

    W naszej wtyczce dodałem jeszcze jeden element, Show infrastructure,


    który wyzwala polecenie pokazania infrastruktury obiektów Service Broker dla aktualnie wybranej bazy danych.

    Uruchomienie go gdy w Object Explorer nie została wybrana domyślna baza danych kończy się komunikatem:


    ponieważ w metodzie obsługi tego polecenia ustawiliśmy atrybut, że wymaga ono kontekstu domyślnej bazy danych. Gdy kontekst taki określimy wybierając w Object Explorer konkretną baz danych to zobaczymy:


    Klikając na zakładkę Service Broker infrastructure zobaczymy:


    Zmieniając w Object Explorer domyślną bazę danych możemy zaobserwować dopasowywanie się tego okna do zmieniającej się struktury obiektów Service Broker w tej bazie. I o to nam chodziło :).

    Linki:

    do następnego odcinka serii o tworzeniu wtyczki do SSMS

    do poprzedniego odcinka serii o tworzeniu wtyczki do SSMS



  • Zrób to sam – SSMS addin (Exec)

     

    W poprzednim odcinku pokazałem jak stworzyć szkielet menu do SSMS. W tym odcinku pokażę, jak stworzyć uniwersalny schemat wywoływania poleceń powiązanych z menu naszej wtyczki.

    Można by od biedy zastosować taki prosty schemat:

    image

    gdzie zastosowanych zostanie tyle “if-ów” ile jest poleceń w naszym menu. Ale to rozwiązanie jest bardzo mało eleganckie.

    Aby stworzyć mechanizm automatycznie dopasowujący elementy menu z elementami wykonawczymi należy zauważyć, że:

    • polecenie może być wywołaniem metody z klasy NextAddin.Connect (czyli z klasy macierzystej) lub wywołaniem jakiejś metody z innej klasy (najczęściej byłoby to okienko dialogowe, tak więc byłaby to metoda ShowDialog),
    • polecenie może wymagać określenia domyślnej bazy danych lub może też być wykonane bez kontekstu bazy danych lub w kontekście bazy master.

    Aby sprostać pierwszemu warunkowi należałoby przyjąć, że metoda lub klasa obiektu będące przedmiotem wykonania polecenia z menu nazywają się dokładnie tak jak identyfikator menu.

    Aby sprostać drugiemu warunkowi należałoby stworzyć atrybut, który miałby zastosowanie do metod i do klas i określałby czy do wykonania polecenia konieczny jest domyślny kontekst określonej bazy danych. Domyślny kontekst bazy danych został opisany w odcinku o ObjectExplorer.

    Całość należałoby obsłużyć przy pomocy Reflection.

    Zaczniemy od atrybutu określającego, czy wykonanie polecenia z menu wymaga kontekstu domyślnej bazy danych:

    image

    Od obiektów, które będą tworzone do wykonania poleceń z menu wtyczki będziemy wymagali, że będzie implementował publiczny interfejs, zapewniający, że obiekt ten będzie realizował metodę get zwracającą polecenie T-SQL dla wykonania jako query w SSMS. Czyli:

    image

    Ostatecznie metody ResetAddin i UninstallAddin miałyby atrybut DbRequired ustawiony na false ponieważ nie wymagają do swego wykonania domyślnej bazy danych:

    image

    Natomiast okno dialogowe do utworzenia message type dla Service Broker miałoby ten atrybut ustawiony na true ponieważ wymaga określenia domyślnej bazy danych:

    image

    Aby jeszcze bardziej zautomatyzować cały proces należałoby dokonać jednej zmiany w definicji stałych do menu:

    private string CREATE_MESSAGE_TYPE_NAME = typeof(Dialogs.create_message_type).Name;

    Jak widać, CREATE_MESSAGE_TYPE_NAME zmieniło się z private const string na private string, ale dzięki temu identyfikator menu jest obecnie powiązany z nazwą klasy reprezentującej okno dialogowe. Ale ponieważ nasza wtyczka już została osadzona w SSMS, wobec tego menu nie będzie ponownie inicjowane a wobec tego ten element menu będzie miał nadal stary identyfikator, pomimo, że kod wtyczki ma już nowy identyfikator. I w tym momencie przychodzi na z pomocą opisane wcześniej polecenie resetowania menu wtyczki. Po wykonaniu tego polecenia menu ma nowy identyfikator zgodny z nazwą klasy okienka dialogowego powiązanego z elementem menu.

    Przy tych założeniach kod obsługi polecenia wymagający obsługi okna dialogowego byłby następujący:

    image

    image

    image

    Natomiast kod obsługi polecenia, które wymaga wywołania metody z klasy macierzystej wtyczki wygląda następująco:

    image

    image

    Mając taki kod możemy już wywołać z naszej wtyczki polecenie utworzenia nowego typu komunikatu w Service Broker:

    image

    A po naciśnięciu klawisza OK…

    image

    W następnym odcinku opiszę jak tworzyć dokowane okna, które dynamicznie dopasowują swoja zawartość do zmiennego kontekstu ObjectExplorer.

    Linki:

    do poprzedniego odcinka serii o tworzeniu wtyczki do SSMS

    do następnego odcinka serii o tworzeniu wtyczki do SSMS

  • Zrób to sam – SSMS addin (Menu)

     

    Po serii artykułów dotyczących internals pomocnych przy tworzeniu wtyczki do SSMS przejdziemy do tego co jest najbardziej widoczne i niezbędne do jej działania: menu.

    W artykule tym opiszę schemat tworzenia menu:

    1. Umiejscowienie menu naszej wtyczki w menu głównym SSMS.

    2. Dodanie elementu menu oraz jednego poziomu podmenu.

    3. Metod resetowania menu wtyczki oraz usuwania go z menu SSMS.

     

    I. Inicjacja menu.

    Inicjacja menu powinna się odbyć w momencie gdy UI do SSMS jest w pełni gotowe do tego aby zaakceptować nowe elementy. Tym miejscem jest obsługa zdarzenia OnStartupComplete. Umieszczanie kodu inicjującego w metodzie OnConnect zakończy się niepowodzeniem ponieważ UI do SSMS jeszcze na tym etapie nie istnieje. Zainicjowane prawidłowo menu wtyczki pozostaje trwałym elementem UI.

    image

    Dla elegancji kodowania wprowadzimy kilka stałych:

    #region Const

    private const string VS_MENUBAR_COMMANDBAR_NAME = "MenuBar";  // nazwa obiektu menu głównego SSMS
    private const string VS_TOOLS_COMMANDBAR_NAME = "Tools";  // nazwa obiektu reprezentującego element “Tools” z menu SSMS

    private const string MAIN_COMMAND_NAME = "MyAddinMenu";
    private const string MAIN_COMMAND_CAPTION = "My addin";
    private const string MAIN_COMMAND_TOOLTIP = "My own SSMS addin tooltip";

    private string REMOVE_NAME = "RemoveMyAddin";
    private const string REMOVE_CAPTION = "Reset menu";
    private const string REMOVE_TOOLTIP = "Reset menu tooltip";

    private string UNINSTALL_NAME = "UninstallMyAddin";
    private const string UNINSTALL_CAPTION = "Uninstall menu";
    private const string UNINSTALL_TOOLTIP = "Uninstall menu tooltip";

    private const string MAIN_CREATE_MENU_NAME = "ServiceBrokerCreate";
    private const string MAIN_CREATE_MENU_CAPTION = "Create";
    private const string MAIN_CREATE_MENU_TOOLTIP = "Create objects for Service Broker tooltip";

    private string CREATE_MESSAGE_TYPE_NAME = "CreateMessageType";
    private const string CREATE_MESSAGE_TYPE_CAPTION = "Message type";
    private const string CREATE_MESSAGE_TYPE_TOOLTIP = "Crete message type for Service Broker tooltip";

    private string OTHER_OPTIONS_NAME = "OtherOptions";
    private const string OTHER_OPTIONS_CAPTION = "Other options";
    private const string OTHER_OPTIONS_TOOLTIP = "Other options tooltip";

    #endregion

    Każdy element menu wtyczki ma trzy opisujące go właściwości:

    - identyfikującą go nazwę,

    - caption będące jego wizualną prezentacją w menu,

    - tooltip pozwalający na dodatkowy opis elementu menu.

    Tworząc identyfikator menu należy pamiętać aby nie zawierał kropek. W przeciwnym wypadku tworzenie menu wygeneruje wyjątek o niewiele mówiącej treści:

    image

    …i szukaj wiatru w polu.

    Elegancko tworzony kod wymaga stosowania prawidłowej obsługi wyjątków, ale w przypadku wtyczki SSMS ma to jeszcze dodatkowe znaczenie. Wtyczka, która nie obsłuży wyjątku nie wejdzie przy ponownym starcie do metody OnConnect co może spowodować utratę części funkcjonalności lub przyczynę błędnego jej dalszego działania. W takim przypadku wtyczkę należy odinstalować i zainstalować ponownie oraz zmodyfikować kod, tak aby poprawnie obsługiwał wyjątek. Każde zagrożone potencjalnym wyjątkiem miejsce otaczamy nawiasami try-catch.

    Czas odkrycie metody inicjującej menu naszej wtyczki:

     image

     

    II. Tworzenie menu

     

    Obiekt CommandBars jest indeksowaną kolekcją elementów menu. Instrukcja:

    toolsCommandBar = commandBars[VS_TOOLS_COMMANDBAR_NAME];

    odzyskuje obiekt reprezentujący menu “Tools”. Chcemy aby menu naszej wtyczki znalazło sie tuż za tym menu. Instrukcja:

    int position = ((CommandBarControl)toolsCommandBar.Parent).Index + 1;

    przypisuje indeks pozycji naszego menu w menu SSMS. Instrukcja:

    menuCommandBar = commandBars[VS_MENUBAR_COMMANDBAR_NAME];

    odzyskuje obiekt będący menu SSMS.

    Nasza wtyczka ma mieć menu pod własną kontrolą niezależną od mechanizmów wtyczek do SSMS. W tym celu potrzebna jest metoda stwierdzająca, czy nasze menu jest już włączone do menu SSMS:

    private bool isActive(CommandBar menuCommandBar)
    {
                foreach (CommandBarControl control in menuCommandBar.Controls)
                {
                    if (control.Caption == MAIN_COMMAND_CAPTION)
                        return true;
                }

                return false;
            }

    Metoda ta przegląda wszystkie kontrolki podłączone do menu SSMS i sprawdza, czy caption od jakiejś kontrolki jest równy nazwie menu naszej wtyczki. Jeżeli jest to znaczy, że nasze menu jest już zainstalowane.

    Przystąpimy teraz do budowania naszego menu. Metoda makeMenu przedstawia szkielet tworzenia menu zawierającego:

    • dodanie do menu bar SSMS elementu menu o identyfikatorze reprezentowanym przez stała MAIN_COMMAND_NAME, mającego caption reprezentowane przez stałą MAIN_COMMAND_CAPTION oraz tooltip reprezentowane przez stałą MAIN_COMMAND_TOOLTIP i występującego na pozycji określonej przez parametr position (powyżej określiliśmy, że będzie to pozycja tuż za elementem “Tools” w menu SSMS), ten element menu jest reprezentowany prze obiekt MainSbMenu,
    • dodanie do elementu głównego naszego menu dwóch menu rozwijanych (reprezentowanych przez obiekty CreateMenu oraz ToolWindowsMenu)
    • do obiektu CreateMenu zostanie dodany jeden element popup menu reprezentowany przez obiekt CreateMenuPopup,
    • do obiektu ToolWindowsMenu zostaną dodane dwa obiekty będące również elementami popup menu realizującymi dwa polecenia realizowane przez SSMS (zresetowanie menu wtyczki oraz usunięcie menu wtyczki).

     

    image 

    image

    Jak widać z powyższego kodu, tworzenie elementów menu czy będących pniami dla gałęzi menu czy liści menu zawierającymi named commands to obie metody CreatemainMenu oraz CreateSubMenu mają taką samą charakterystykę parametrów:

    • parent – jest nadrzędnym obiektem menu, do którego dodawany jest element tworzonego menu,
    • name – jest identyfikatorem tworzonego menu,
    • caption – jest wizualną reprezentacją tworzonego menu,
    • group – true oznacza, że bieżący element tworzonego menu będzie otwierał grupę menu oddzieloną separatorem, false oznacza, że separtor menu nie będzie występował,
    • position – oznacza pozycję na liście menu dodawanej do nadrzędnego menu. Stąd w przypadku dodawania do ToolWindowsMenu kolejne pozycje dodawanych do niego elementów menu są indeksowane poprzez ToolMenuPopup.Controls.Count + 1.

    Metoda CreateMainMenu nie tworzy wykonywalnego named command  dla SSMS a jedynie pień dla gałęzi menu. Metoda ta jest prosta i nie wymaga szerszego wyjaśniania.

    image

    Metoda CreateSubMenu ma jedno miejsce, które wymaga szczególnego wyjaśnienia.

    image

    Ten kod to

    try
    {
            command = applicationObject.Commands.Item(addInInstance.ProgID + "." + name, -1);
    }
    catch
    {
    }

    Kod ten jest próbą pobrania obiektu naszego menu. Jeżeli menu to nie istnieje to generowany jest wyjątek (chociaż lepiej byłoby gdyby zamiast tego zwracał po prostu null). Jeżeli wystąpi wyjątek to zmienna command będzie miała wartość null  i znaczyć to będzie, że takiego elementu menu nie ma w SSMS i należy utworzyć go oraz powiązane z nim named command. Warto zwrócić uwagę na dwa szczególne fakty:

    • tworzony named command  ma ustawione vsCommandStatusSupported  oraz          vsCommandStatusEnabled czyniąc tym samym pozwolenie na jego wykonanie.
    • tworząc element menu nadajemy mu identyfikator określony parametrem name ale SSMS zabezpiecza się przed możliwością zdublowania takich samych poleceń z różnych wtyczek lub pokrycia się ich z poleceniami SSMS i wobec tego nasze polecenie widziane jest przez SSMS jako:

    addInInstance.ProgID + "." + name

    gdzie addInInstance.ProgID to nic innego jak konstrukcja <namespace>.<class> co w przypadku naszej wtyczki i menu odinstalowania wtyczki oznacza, że SSMS pełny identyfikator dla tego menu będzie następujący: NextAddin.Connect.UninstallMyAddin. Konstrukcja ta być może jest również wyjaśnieniem dlaczego dodanie kropki w podawanej przez nas nazwie menu powoduje wyjątek. Być może SSMS przeszukuje ostatniej kropki w pełnym identyfikatorze menu i wszystko co jest na lewo od kropki traktowane jest jako <namespace>.<class>, których SSMS w tym wypadku nie jest w stanie zlokalizować.

    A tak wygląda menu naszej wtyczki dodane do SSMS:

    image

    III. Resetowanie i odinstalowanie menu wtyczki

    Rozwijając wtyczkę będziemy stawać przed problemem jak zmodyfikować menu naszej wtyczki. Należałoby zbudować metodę, która usunie wszystkie command rozpoczynające się od konstrukcji addInInstance.ProgID oraz element główny naszego menu z SSMS. Zastosowanie tej metody wraz z metodą inicjacji menu dawałoby nam efekt zresetowania menu a tym samym ujawnienia nowych oraz usunięcia starych opcji. Odinstalowanie menu wtyczki wymagałoby wywołania jedynie metody usuwania. Odinstalowanie menu powinno się wykonywać przed odinstalowaniem wtyczki, w przeciwnym wypadku elementy menu pozostaną w SSMS i próby ich wywołania będą się kończyć komunikatem błędu. Resetowanie wtyczek, które jest możliwe w Visual Studio z poziomu linii poleceń uruchamiających go nie jest możliwe w przypadku SSMS (nie ma takiej opcji w linii poleceń SSMS).

    Metoda odinstalowania wygląda następująco:

    image

    Metoda resetowania wygląda następująco:

    image

    Jak spowodować aby metody ResetMyAddin i UninstallAddin wykonały się z menu naszej wtyczki będzie przedmiotem następnego odcinka serii (Exec).

    Linki:

    do poprzedniego odcinka serii o tworzeniu wtyczki do SSMS

    do następnego odcinka serii o tworzeniu wtyczki do SSMS

  • Zrób to sam – SSMS addin (ObjectExplorer, ConnectionString, Command)

    ObjectExplorer jest kluczowym obiektem SSMS. To przy jego pomocy łączymy się do instancji SQL Server, przy jego pomocy zmieniamy kontekst bazy danych. Mając ten obiekt w naszej wtyczce jesteśmy w stanie dynamicznie reagować na zmianę domyślnej bazy danych lub serwera powodując dostosowanie widoków naszych okien do zmienionej sytuacji.

    Odzyskanie ConnectionString dla bieżącego połączenia daje nam szerokie możliwości działania na obiektach bazy danych.

    Musimy również odzyskać obiekt polecenia wykonania query execute aby podpinając się do zdarzenia after execute dać naszej wtyczce ponownie szansę na odświeżenie widoków naszych okien dostosowując je tym samym do sytuacji jaka zaistniała po jego wykonaniu.

    1. ObjectExplorer

    Do wtyczki dodajemy nowe regiony kodu i umiejscawiamy je tak jak widać na poniższym rysunku:

    image

    Rozwijamy interesujące nas regiony kodu:

    #region Object Explorer fields
     

           IObjectExplorerEventProvider provider;
            Command cmdQueryExecute = default(Command);
            CommandEvents commandEventsQueryExecute = default(CommandEvents);       

     #endregion

    #region Static fields

    private static DbContext dbContext = new DbContext();  // będzie opisana poniżej

    #endregion

    #region Object explorer init code

    IObjectExplorerService objectExplorer = ServiceCache.GetObjectExplorer();
    provider = (IObjectExplorerEventProvider)objectExplorer.GetService(typeof(IObjectExplorerEventProvider));

    provider.SelectionChanged += new NodesChangedEventHandler(provider_SelectionChanged);

    Commands commands = applicationObject.Commands;

    cmdQueryExecute = commands.Item("Query.Execute", 0);

    commandEventsQueryExecute = applicationObject.Events.get_CommandEvents(cmdQueryExecute.Guid, cmdQueryExecute.ID);
    commandEventsQueryExecute.AfterExecute+=new_dispCommandEvents_AfterExecuteEventHandler

    (commandEventsQueryExecute_AfterExecute);

    #endregion

    Kluczowymi do osiągnięcia naszego celu są handlery obsługi zdarzeń provider_SelectionChanged (zmiana kontekstu ObjectExplorer) oraz commandEventsQueryExecute_AfterExecute (zakończyło się wykonanie query).

    W uproszczeniu (nie wnikając w tej chwili co zawierają regiony Refesh…) kod tych handlerów wygląda następująco:

    image

    Zdarzenie SelectionChanged pochodzące od ObjectExplorer przekazuje w parametrze args nowy kontekst pracy SSMS.

    Przy zmianie kontekstu serwera wygląda on następująco:

    image

    A po wybraniu określonej bazy danych wygląda tak:

    image

    Ponieważ kontekst zachowuje regularną strukturę warto stworzyć klasę, która będzie go parsować  do bardziej przydatnej formy. W tym celu tworzymy projekt NextAddin.Tools:

    image

    Referencje są identyczne z tymi, które wymieniłem w odcinku Start.

    Klasa ObjectExplorerContext realizuje obsługę parsowania oraz porównywania dotychczasowego kontekstu ze zmienionym kontekstem.

    public class DbContext
    {
        public string Server = string.Empty;
        public string Database = string.Empty;
        public string ConnectionString = string.Empty;
    }

    /// <summary>
    /// Context Object Explorer
    /// </summary>
    public static class ObjectExplorerContext
    {
        public static DbContext Parse(string context)
        {
            DbContext dbContext = new DbContext();
            Regex re;

            char[] sep = new char[] { '/' };
            char[] equalSep = new char[] { '=' };
            char[] apostrSep = new char[] { '\'' };

            string[] items = context.Split(sep);
            string[] equalItems;
            string[] apostrItems;

            foreach (string item in items)
            {
                if (item.ToLower().StartsWith("server"))
                {
                    re = new Regex(".*Server\\[@Name='(.*?)']?");
                    if (re.Match(item).Success)
                    {
                        equalItems = item.Split(equalSep);
                        apostrItems = equalItems[1].Split(apostrSep);

                        dbContext.Server = apostrItems[1];

                        dbContext.ConnectionString = UtilitySqlTools.Current.ConnectionString();
                    }
                }
                else if (item.ToLower().StartsWith("database"))
                {
                    re = new Regex(".*Database\\[@Name='(.*?)']?");
                    if (re.Match(item).Success)
                    {
                        equalItems = item.Split(equalSep);
                        apostrItems = equalItems[1].Split(apostrSep);

                        dbContext.Database = apostrItems[1];
                    }
                }
            }

            return dbContext;
        }

        public static bool AreDifferent(DbContext older, DbContext newer)
        {
            if (older.Database != newer.Database || older.Server != newer.Server)
            {
                return true;
            }

            return false;
        }
    }

     

    2. ConnectionString

    Klasa UtilitySqlTools umożliwia nam odzyskanie ConnectionString dla aktualnego połączenia do serwera.

     

    public class UtilitySqlTools
    {
        static UtilitySqlTools currentInstance = new UtilitySqlTools();

        private UIConnectionInfo currentUIConnection;
        private SqlConnectionInfo currentConnection;

        public static UtilitySqlTools Current
        {
            get { return currentInstance; }
        }

        private SqlConnectionInfo CreateSqlConnectionInfo(UIConnectionInfo connectionInfo)
        {
            SqlConnectionInfo sqlConnInfo = new SqlConnectionInfo();
            sqlConnInfo.ServerName = connectionInfo.ServerName;
            sqlConnInfo.UserName = connectionInfo.UserName;
            if (string.IsNullOrEmpty(connectionInfo.Password))
            {
                sqlConnInfo.UseIntegratedSecurity = true;
            }
            else
            {
                sqlConnInfo.Password = connectionInfo.Password;
            }

            return sqlConnInfo;
        }

        public SqlConnectionInfo GetActiveWindowConnection()
        {
            SqlConnectionInfo info = null;
            try
            {
                UIConnectionInfo connInfo = null;
                if (ServiceCache.ScriptFactory.CurrentlyActiveWndConnectionInfo != null)
                {
                    connInfo = ServiceCache.ScriptFactory.CurrentlyActiveWndConnectionInfo.UIConnectionInfo;
                }
                if (connInfo != null)
                {
                    if (connInfo == currentUIConnection)
                    {
                        return currentConnection;
                    }
                    else
                    {
                        info = CreateSqlConnectionInfo(connInfo);
                        currentConnection = info;
                        currentUIConnection = connInfo;
                    }
                }
                if (info == null)
                { 
                    INodeInformation[] nodes = GetObjectExplorerSelectedNodes();
                    if (nodes.Length > 0)
                    {
                        info = nodes[0].Connection as SqlConnectionInfo;
                    }
                }
                return info;
            }
            catch (NullReferenceException)
            {
                return null;
            }
        }

        public string ConnectionString()
        {
            if (GetActiveWindowConnection() != null)
                return GetActiveWindowConnection().ConnectionString;
            else
                return null;
        }

        private INodeInformation[] GetObjectExplorerSelectedNodes()
        {
            IObjectExplorerService objExplorer = ServiceCache.GetObjectExplorer();
            int arraySize;
            INodeInformation[] nodes;
            objExplorer.GetSelectedNodes(out arraySize, out nodes);
            return nodes;
        }

        public static void EnableDisableBroker(string current_database, SqlConnection connection, bool enabled)
        {
            StringBuilder sb = new StringBuilder();

            sb.AppendLine("USE [" + current_database + "];");
            sb.Append("ALTER DATABASE [" + current_database + "]");
            if (enabled)
            {
                sb.Append(" SET ENABLE_BROKER");
            }
            else
            {
                sb.Append(" SET DISABLE_BROKER");
            }

            try
            {
                SqlCommand command = new SqlCommand();
                command.CommandText = sb.ToString();
                command.CommandTimeout = 0;
                command.CommandType = CommandType.Text;
                command.Connection = connection;
                command.ExecuteNonQuery();
            }
            catch (Exception exception)
            {
            }
        }

    }

    3. Command

    Po utworzeniu New Query  z SSMS

    image

    i uruchomieniu go program wchodzi do obsługi zdarzenia AfterExecute

    image

    Parametry obsługi tego zdarzenia nie niosą nic interesującego z punktu zastosowań do naszej wtyczki. Istotne jest jedynie to, że mamy możliwość zadziałania bezpośrednio po wykonaniu query.

    Linki:

    do poprzedniego odcinka serii o tworzeniu wtyczki do SSMS

    do następnego odcinka serii o tworzeniu wtyczki do SSMS

  • Zrób to sam – SSMS add-in (EventWatcher)

     

    Na etapie uruchamiania wtyczki istotne jest posiadanie narzędzia dającego możliwość przechwytywania i obsługi zdarzeń generowanych przez SSMS. Ujęte to zostało w regionach w kodzie naszej przykładowej wtyczki.

    Niewątpliwie pożądaną cechą naszej wtyczki byłaby cecha cichej pracy (tak aby nie straszyć użytkownika wyskakującymi okienkami) z jednoczesną rejestracją zdarzeń błędów i wyjątków do logu plikowego. Logu konfigurowalnego tak aby można było rejestrować tylko błędy lub błędy i komunikaty informacyjne przydatne na etapie wdrażania wtyczki. Nie przedstawiam tu żądnych rozwiązań Logger’a ponieważ nie jest to w zakresie tego tematu a utworzenie stosownej klasy nie jest żadnym problemem.

    Skupmy sie więc na podglądaniu zdarzeń. Prezentowany kod zawiera jedynie szkielet rozwiązania przechwytywania wszystkich zdarzeń generowanych przez SSMS (do decyzji developera pozostaje czy chce je wykorzystać czy nie). Zastosowane tutaj rozwiązanie z wykorzystaniem Output Window z SSMS nie zadziała na wersjach Express (nie jest to okno dostępne w tych wersjach). Można zastąpić je właśnie wspomnianym wyżej Logger’em.

    image

    A teraz te regiony będą rozwinięte z możliwością skopiowania ich do aplikacji (jeżeli jest ktoś, kto próbuje ze mną taką wtyczkę stworzyć :) )

    Obiekty zdarzeń

    #region EventWatcher fields
            private EnvDTE.WindowEvents _windowsEvents;
            private EnvDTE.TextEditorEvents _textEditorEvents;
            private EnvDTE.TaskListEvents _taskListEvents;
            private EnvDTE.SolutionEvents _solutionEvents;
            private EnvDTE.SelectionEvents _selectionEvents;
            private EnvDTE.OutputWindowEvents _outputWindowEvents;
            private EnvDTE.FindEvents _findEvents;
            private EnvDTE.DTEEvents _dteEvents;
            private EnvDTE.DocumentEvents _documentEvents;
            private EnvDTE.DebuggerEvents _debuggerEvents;
            private EnvDTE.CommandEvents _commandEvents;
            private EnvDTE.BuildEvents _buildEvents;
            private EnvDTE.ProjectItemsEvents _miscFilesEvents;
            private EnvDTE.ProjectItemsEvents _solutionItemsEvents;
            private EnvDTE.ProjectItemsEvents _globalProjectItemsEvents;
            private EnvDTE.ProjectsEvents _globalProjectsEvents;
            private EnvDTE80.TextDocumentKeyPressEvents _textDocumentKeyPressEvents;
            private EnvDTE80.CodeModelEvents _codeModelEvents;
            private EnvDTE80.WindowVisibilityEvents _windowVisibilityEvents;
            private EnvDTE80.DebuggerProcessEvents _debuggerProcessEvents;
            private EnvDTE80.DebuggerExpressionEvaluationEvents _debuggerExpressionEvaluationEvents;
            private EnvDTE80.PublishEvents _publishEvents;
            private OutputWindowPane _outputWindowPane;
    #endregion

    Dodawanie obsługi zdarzeń

    #region EventWatcher - Add

    if (testEvents)
    {
        EnvDTE.Events events = applicationObject.Events;
        OutputWindow outputWindow = (OutputWindow)applicationObject.Windows.Item(Constants.vsWindowKindOutput).Object;

        _outputWindowPane = outputWindow.OutputWindowPanes.Add("DTE Event Information - C# Event Watcher");

        //Retrieve the event objects from the automation model
        _windowsEvents = (EnvDTE.WindowEvents)events.get_WindowEvents(null);
        _textEditorEvents = (EnvDTE.TextEditorEvents)events.get_TextEditorEvents(null);
        _taskListEvents = (EnvDTE.TaskListEvents)events.get_TaskListEvents("");
        _solutionEvents = (EnvDTE.SolutionEvents)events.SolutionEvents;
        _selectionEvents = (EnvDTE.SelectionEvents)events.SelectionEvents;
        _outputWindowEvents = (EnvDTE.OutputWindowEvents)events.get_OutputWindowEvents("");
        _findEvents = (EnvDTE.FindEvents)events.FindEvents;
        _dteEvents = (EnvDTE.DTEEvents)events.DTEEvents;
        _documentEvents = (EnvDTE.DocumentEvents)events.get_DocumentEvents(null);
        _debuggerEvents = (EnvDTE.DebuggerEvents)events.DebuggerEvents;
        _commandEvents = (EnvDTE.CommandEvents)events.get_CommandEvents("{00000000-0000-0000-0000-000000000000}", 0);
        _buildEvents = (EnvDTE.BuildEvents)events.BuildEvents;
        _miscFilesEvents = (EnvDTE.ProjectItemsEvents)events.MiscFilesEvents;
        _solutionItemsEvents = (EnvDTE.ProjectItemsEvents)events.SolutionItemsEvents;

        _globalProjectItemsEvents = ((EnvDTE80.Events2)events).ProjectItemsEvents;
        _globalProjectsEvents = ((EnvDTE80.Events2)events).ProjectsEvents;
        _textDocumentKeyPressEvents = ((EnvDTE80.Events2)events).get_TextDocumentKeyPressEvents(null);
        _codeModelEvents = ((EnvDTE80.Events2)events).get_CodeModelEvents(null);
        _windowVisibilityEvents = ((EnvDTE80.Events2)events).get_WindowVisibilityEvents(null);
        _debuggerProcessEvents = ((EnvDTE80.Events2)events).DebuggerProcessEvents;
        _debuggerExpressionEvaluationEvents = ((EnvDTE80.Events2)events).DebuggerExpressionEvaluationEvents;
        _publishEvents = ((EnvDTE80.Events2)events).PublishEvents;

        //Connect to each delegate exposed from each object retrieved above
        _windowsEvents.WindowActivated += new _dispWindowEvents_WindowActivatedEventHandler(this.WindowActivated);
        _windowsEvents.WindowClosing += new _dispWindowEvents_WindowClosingEventHandler(this.WindowClosing);
        _windowsEvents.WindowCreated += new _dispWindowEvents_WindowCreatedEventHandler(this.WindowCreated);
        _windowsEvents.WindowMoved += new _dispWindowEvents_WindowMovedEventHandler(this.WindowMoved);
        _textEditorEvents.LineChanged += new _dispTextEditorEvents_LineChangedEventHandler(this.LineChanged);
        _taskListEvents.TaskAdded += new _dispTaskListEvents_TaskAddedEventHandler(this.TaskAdded);
        _taskListEvents.TaskModified += new _dispTaskListEvents_TaskModifiedEventHandler(this.TaskModified);
        _taskListEvents.TaskNavigated += new _dispTaskListEvents_TaskNavigatedEventHandler(this.TaskNavigated);
        _taskListEvents.TaskRemoved += new _dispTaskListEvents_TaskRemovedEventHandler(this.TaskRemoved);
        _solutionEvents.AfterClosing += new _dispSolutionEvents_AfterClosingEventHandler(this.AfterClosing);
        _solutionEvents.BeforeClosing += new _dispSolutionEvents_BeforeClosingEventHandler(this.BeforeClosing);
        _solutionEvents.Opened += new _dispSolutionEvents_OpenedEventHandler(this.Opened);
        _solutionEvents.ProjectAdded += new _dispSolutionEvents_ProjectAddedEventHandler(this.ProjectAdded);
        _solutionEvents.ProjectRemoved += new _dispSolutionEvents_ProjectRemovedEventHandler(this.ProjectRemoved);
        _solutionEvents.ProjectRenamed += new _dispSolutionEvents_ProjectRenamedEventHandler(this.ProjectRenamed);
        _solutionEvents.QueryCloseSolution += new _dispSolutionEvents_QueryCloseSolutionEventHandler(this.QueryCloseSolution);
        _solutionEvents.Renamed += new _dispSolutionEvents_RenamedEventHandler(this.Renamed);
        _selectionEvents.OnChange += new _dispSelectionEvents_OnChangeEventHandler(this.OnChange);
        _outputWindowEvents.PaneAdded += new _dispOutputWindowEvents_PaneAddedEventHandler(this.PaneAdded);
        _outputWindowEvents.PaneClearing += new _dispOutputWindowEvents_PaneClearingEventHandler(this.PaneClearing);
        _outputWindowEvents.PaneUpdated += new _dispOutputWindowEvents_PaneUpdatedEventHandler(this.PaneUpdated);
        _findEvents.FindDone += new _dispFindEvents_FindDoneEventHandler(this.FindDone);
        _dteEvents.ModeChanged += new _dispDTEEvents_ModeChangedEventHandler(this.ModeChanged);
        _dteEvents.OnBeginShutdown += new _dispDTEEvents_OnBeginShutdownEventHandler(this.OnBeginShutdown);
        _dteEvents.OnMacrosRuntimeReset += new _dispDTEEvents_OnMacrosRuntimeResetEventHandler(this.OnMacrosRuntimeReset);
        _dteEvents.OnStartupComplete += new _dispDTEEvents_OnStartupCompleteEventHandler(this.OnStartupComplete);
        _documentEvents.DocumentClosing += new _dispDocumentEvents_DocumentClosingEventHandler(this.DocumentClosing);
        _documentEvents.DocumentOpened += new _dispDocumentEvents_DocumentOpenedEventHandler(this.DocumentOpened);
        _documentEvents.DocumentOpening += new _dispDocumentEvents_DocumentOpeningEventHandler(this.DocumentOpening);
        _documentEvents.DocumentSaved += new _dispDocumentEvents_DocumentSavedEventHandler(this.DocumentSaved);
        _debuggerEvents.OnContextChanged += new _dispDebuggerEvents_OnContextChangedEventHandler(this.OnContextChanged);
        _debuggerEvents.OnEnterBreakMode += new _dispDebuggerEvents_OnEnterBreakModeEventHandler(this.OnEnterBreakMode);
        _debuggerEvents.OnEnterDesignMode += new _dispDebuggerEvents_OnEnterDesignModeEventHandler(this.OnEnterDesignMode);
        _debuggerEvents.OnEnterRunMode += new _dispDebuggerEvents_OnEnterRunModeEventHandler(this.OnEnterRunMode);
        _debuggerEvents.OnExceptionNotHandled += new _dispDebuggerEvents_OnExceptionNotHandledEventHandler(this.OnExceptionNotHandled);
        _debuggerEvents.OnExceptionThrown += new _dispDebuggerEvents_OnExceptionThrownEventHandler(this.OnExceptionThrown);
        _commandEvents.AfterExecute += new _dispCommandEvents_AfterExecuteEventHandler(this.AfterExecute);
        _commandEvents.BeforeExecute += new _dispCommandEvents_BeforeExecuteEventHandler(this.BeforeExecute);
        _buildEvents.OnBuildBegin += new _dispBuildEvents_OnBuildBeginEventHandler(this.OnBuildBegin);
        _buildEvents.OnBuildDone += new _dispBuildEvents_OnBuildDoneEventHandler(this.OnBuildDone);
        _buildEvents.OnBuildProjConfigBegin += new _dispBuildEvents_OnBuildProjConfigBeginEventHandler(this.OnBuildProjConfigBegin);
        _buildEvents.OnBuildProjConfigDone += new _dispBuildEvents_OnBuildProjConfigDoneEventHandler(this.OnBuildProjConfigDone);
        _miscFilesEvents.ItemAdded += new _dispProjectItemsEvents_ItemAddedEventHandler(this.MiscFilesEvents_ItemAdded);
        _miscFilesEvents.ItemRemoved += new _dispProjectItemsEvents_ItemRemovedEventHandler(this.MiscFilesEvents_ItemRemoved);
        _miscFilesEvents.ItemRenamed += new _dispProjectItemsEvents_ItemRenamedEventHandler(this.MiscFilesEvents_ItemRenamed);
        _solutionItemsEvents.ItemAdded += new _dispProjectItemsEvents_ItemAddedEventHandler(this.SolutionItemsEvents_ItemAdded);
        _solutionItemsEvents.ItemRemoved += new _dispProjectItemsEvents_ItemRemovedEventHandler(this.SolutionItemsEvents_ItemRemoved);
        _solutionItemsEvents.ItemRenamed += new _dispProjectItemsEvents_ItemRenamedEventHandler(this.SolutionItemsEvents_ItemRenamed);
        _globalProjectItemsEvents.ItemAdded += new _dispProjectItemsEvents_ItemAddedEventHandler(GlobalProjectItemsEvents_ItemAdded);
        _globalProjectItemsEvents.ItemRemoved += new _dispProjectItemsEvents_ItemRemovedEventHandler(GlobalProjectItemsEvents_ItemRemoved);
        _globalProjectItemsEvents.ItemRenamed += new _dispProjectItemsEvents_ItemRenamedEventHandler(GlobalProjectItemsEvents_ItemRenamed);
        _globalProjectsEvents.ItemAdded += new _dispProjectsEvents_ItemAddedEventHandler(GlobalProjectsEvents_ItemAdded);
        _globalProjectsEvents.ItemRemoved += new _dispProjectsEvents_ItemRemovedEventHandler(GlobalProjectsEvents_ItemRemoved);
        _globalProjectsEvents.ItemRenamed += new _dispProjectsEvents_ItemRenamedEventHandler(GlobalProjectsEvents_ItemRenamed);
        _textDocumentKeyPressEvents.AfterKeyPress += new _dispTextDocumentKeyPressEvents_AfterKeyPressEventHandler(AfterKeyPress);
        _textDocumentKeyPressEvents.BeforeKeyPress += new _dispTextDocumentKeyPressEvents_BeforeKeyPressEventHandler(BeforeKeyPress);
        _codeModelEvents.ElementAdded += new _dispCodeModelEvents_ElementAddedEventHandler(ElementAdded);
        _codeModelEvents.ElementChanged += new _dispCodeModelEvents_ElementChangedEventHandler(ElementChanged);
        _codeModelEvents.ElementDeleted += new _dispCodeModelEvents_ElementDeletedEventHandler(ElementDeleted);
        _windowVisibilityEvents.WindowHiding += new _dispWindowVisibilityEvents_WindowHidingEventHandler(WindowHiding);
        _windowVisibilityEvents.WindowShowing += new _dispWindowVisibilityEvents_WindowShowingEventHandler(WindowShowing);
        _debuggerExpressionEvaluationEvents.OnExpressionEvaluation += new _dispDebuggerExpressionEvaluationEvents_OnExpressionEvaluationEventHandler(OnExpressionEvaluation);
        _debuggerProcessEvents.OnProcessStateChanged += new _dispDebuggerProcessEvents_OnProcessStateChangedEventHandler(OnProcessStateChanged);
        _publishEvents.OnPublishBegin += new _dispPublishEvents_OnPublishBeginEventHandler(OnPublishBegin);
        _publishEvents.OnPublishDone += new _dispPublishEvents_OnPublishDoneEventHandler(OnPublishDone);
    }

    #endregion

    Metody do obsługi zdarzeń

     #region EventWatcher - Methods
            //WindowEvents
            public void WindowClosing(EnvDTE.Window closingWindow)
            {
                _outputWindowPane.OutputString("WindowEvents, WindowClosing\n");
                _outputWindowPane.OutputString("\tWindow: " + closingWindow.Caption + "\n");
            }

            public void WindowActivated(EnvDTE.Window gotFocus, EnvDTE.Window lostFocus)
            {
                _outputWindowPane.OutputString("WindowEvents, WindowActivated\n");
                _outputWindowPane.OutputString("\tWindow receiving focus: " + gotFocus.Caption + "\n");
                _outputWindowPane.OutputString("\tWindow that lost focus: " + lostFocus.Caption + "\n");
            }

            public void WindowCreated(EnvDTE.Window window)
            {
                _outputWindowPane.OutputString("WindowEvents, WindowCreated\n");
                _outputWindowPane.OutputString("\tWindow: " + window.Caption + "\n");
            }

            public void WindowMoved(EnvDTE.Window window, int top, int left, int width, int height)
            {
                _outputWindowPane.OutputString("WindowEvents, WindowMoved\n");
                _outputWindowPane.OutputString("\tWindow: " + window.Caption + "\n");
                _outputWindowPane.OutputString("\tLocation: (" + top.ToString() + " , " + left.ToString() + " , " + width.ToString() + " , " + height.ToString() + ")\n");
            }

            //TextEditorEvents
            public void LineChanged(EnvDTE.TextPoint startPoint, EnvDTE.TextPoint endPoint, int hint)
            {
                vsTextChanged textChangedHint = (vsTextChanged)hint;

                _outputWindowPane.OutputString("TextEditorEvents, LineChanged\n");
                _outputWindowPane.OutputString("\tDocument: " + startPoint.Parent.Parent.Name + "\n");
                _outputWindowPane.OutputString("\tChange hint: " + textChangedHint.ToString() + "\n");
            }

            //TaskListEvents
            public void TaskAdded(EnvDTE.TaskItem taskItem)
            {
                _outputWindowPane.OutputString("TaskListEvents, TaskAdded\n");
                _outputWindowPane.OutputString("\tTask description: " + taskItem.Description + "\n");
            }

            public void TaskModified(EnvDTE.TaskItem taskItem, EnvDTE.vsTaskListColumn columnModified)
            {
                _outputWindowPane.OutputString("TaskListEvents, TaskModified\n");
                _outputWindowPane.OutputString("\tTask description: " + taskItem.Description + "\n");
            }

            public void TaskNavigated(EnvDTE.TaskItem taskItem, ref bool navigateHandled)
            {
                _outputWindowPane.OutputString("TaskListEvents, TaskNavigated\n");
                _outputWindowPane.OutputString("\tTask description: " + taskItem.Description + "\n");
            }

            public void TaskRemoved(EnvDTE.TaskItem taskItem)
            {
                _outputWindowPane.OutputString("TaskListEvents, TaskRemoved\n");
                _outputWindowPane.OutputString("\tTask description: " + taskItem.Description + "\n");
            }

            //SolutionEvents
            public void AfterClosing()
            {
                _outputWindowPane.OutputString("SolutionEvents, AfterClosing\n");
            }

            public void BeforeClosing()
            {
                _outputWindowPane.OutputString("SolutionEvents, BeforeClosing\n");
            }

            public void Opened()
            {
                _outputWindowPane.OutputString("SolutionEvents, Opened\n");
            }

            public void ProjectAdded(EnvDTE.Project project)
            {
                _outputWindowPane.OutputString("SolutionEvents, ProjectAdded\n");
                _outputWindowPane.OutputString("\tProject: " + project.UniqueName + "\n");
            }

            public void ProjectRemoved(EnvDTE.Project project)
            {
                _outputWindowPane.OutputString("SolutionEvents, ProjectRemoved\n");
                _outputWindowPane.OutputString("\tProject: " + project.UniqueName + "\n");
            }

            public void ProjectRenamed(EnvDTE.Project project, string oldName)
            {
                _outputWindowPane.OutputString("SolutionEvents, ProjectRenamed\n");
                _outputWindowPane.OutputString("\tProject: " + project.UniqueName + "\n");
            }

            public void QueryCloseSolution(ref bool cancel)
            {
                _outputWindowPane.OutputString("SolutionEvents, QueryCloseSolution\n");
            }

            public void Renamed(string oldName)
            {
                _outputWindowPane.OutputString("SolutionEvents, Renamed\n");
            }

            //SelectionEvents
            public void OnChange()
            {
                _outputWindowPane.OutputString("SelectionEvents, OnChange\n");

                int count = applicationObject.SelectedItems.Count;

                for (int i = 1; i <= applicationObject.SelectedItems.Count; i++)
                {
                    _outputWindowPane.OutputString("Item name: " + applicationObject.SelectedItems.Item(i).Name + "\n");
                }
            }

            //OutputWindowEvents
            public void PaneAdded(EnvDTE.OutputWindowPane pane)
            {
                _outputWindowPane.OutputString("OutputWindowEvents, PaneAdded\n");
                _outputWindowPane.OutputString("\tPane: " + pane.Name + "\n");
            }

            public void PaneClearing(EnvDTE.OutputWindowPane pane)
            {
                _outputWindowPane.OutputString("OutputWindowEvents, PaneClearing\n");
                _outputWindowPane.OutputString("\tPane: " + pane.Name + "\n");
            }

            public void PaneUpdated(EnvDTE.OutputWindowPane pane)
            {
                //Dont want to do this one, or we will end up in a recursive call:
                //outputWindowPane.OutputString("OutputWindowEvents, PaneUpdated\n");
                //outputWindowPane.OutputString("\tPane: " + pane.Name + "\n");
            }

            //FindEvents
            public void FindDone(EnvDTE.vsFindResult result, bool cancelled)
            {
                _outputWindowPane.OutputString("FindEvents, FindDone\n");
            }

            //DTEEvents
            public void ModeChanged(EnvDTE.vsIDEMode LastMode)
            {
                _outputWindowPane.OutputString("DTEEvents, ModeChanged\n");
            }

            public void OnBeginShutdown()
            {
                _outputWindowPane.OutputString("DTEEvents, OnBeginShutdown\n");
            }

            public void OnMacrosRuntimeReset()
            {
                _outputWindowPane.OutputString("DTEEvents, OnMacrosRuntimeReset\n");
            }

            public void OnStartupComplete()
            {
                _outputWindowPane.OutputString("DTEEvents, OnStartupComplete\n");
            }

            //DocumentEvents
            public void DocumentClosing(EnvDTE.Document document)
            {
                _outputWindowPane.OutputString("DocumentEvents, DocumentClosing\n");
                _outputWindowPane.OutputString("\tDocument: " + document.Name + "\n");
            }

            public void DocumentOpened(EnvDTE.Document document)
            {
                _outputWindowPane.OutputString("DocumentEvents, DocumentOpened\n");
                _outputWindowPane.OutputString("\tDocument: " + document.Name + "\n");
            }

            public void DocumentOpening(string documentPath, bool ReadOnly)
            {
                _outputWindowPane.OutputString("DocumentEvents, DocumentOpening\n");
                _outputWindowPane.OutputString("\tPath: " + documentPath + "\n");
            }

            public void DocumentSaved(EnvDTE.Document document)
            {
                _outputWindowPane.OutputString("DocumentEvents, DocumentSaved\n");
                _outputWindowPane.OutputString("\tDocument: " + document.Name + "\n");
            }

            //DebuggerEvents
            public void OnContextChanged(EnvDTE.Process NewProcess, EnvDTE.Program NewProgram, EnvDTE.Thread NewThread, EnvDTE.StackFrame NewStackFrame)
            {
                _outputWindowPane.OutputString("DebuggerEvents, OnContextChanged\n");
            }

            public void OnEnterBreakMode(EnvDTE.dbgEventReason reason, ref EnvDTE.dbgExecutionAction executionAction)
            {
                executionAction = EnvDTE.dbgExecutionAction.dbgExecutionActionDefault;
                _outputWindowPane.OutputString("DebuggerEvents, OnEnterBreakMode\n");
            }

            public void OnEnterDesignMode(EnvDTE.dbgEventReason Reason)
            {
                _outputWindowPane.OutputString("DebuggerEvents, OnEnterDesignMode\n");
            }

            public void OnEnterRunMode(EnvDTE.dbgEventReason Reason)
            {
                _outputWindowPane.OutputString("DebuggerEvents, OnEnterRunMode\n");
            }

            public void OnExceptionNotHandled(string exceptionType, string name, int code, string description, ref EnvDTE.dbgExceptionAction exceptionAction)
            {
                exceptionAction = EnvDTE.dbgExceptionAction.dbgExceptionActionDefault;
                _outputWindowPane.OutputString("DebuggerEvents, OnExceptionNotHandled\n");
            }

            public void OnExceptionThrown(string exceptionType, string name, int code, string description, ref EnvDTE.dbgExceptionAction exceptionAction)
            {
                exceptionAction = EnvDTE.dbgExceptionAction.dbgExceptionActionDefault;
                _outputWindowPane.OutputString("DebuggerEvents, OnExceptionThrown\n");
            }

            //CommandEvents
            public void AfterExecute(string Guid, int ID, object CustomIn, object CustomOut)
            {
                string commandName = "";

                try
                {
                    commandName = applicationObject.Commands.Item(Guid, ID).Name;
                }
                catch (System.Exception exception)
                {
                }
                _outputWindowPane.OutputString("CommandEvents, AfterExecute\n");
                if (commandName != "")
                    _outputWindowPane.OutputString("\tCommand name: " + commandName + "\n");

                _outputWindowPane.OutputString("\tCommand GUID/ID: " + Guid + ", " + ID.ToString() + "\n");
            }

            public void BeforeExecute(string Guid, int ID, object CustomIn, object CustomOut, ref bool CancelDefault)
            {
                string commandName = "";

                try
                {
                    commandName = applicationObject.Commands.Item(Guid, ID).Name;
                }
                catch (System.Exception exception)
                {
                }
                _outputWindowPane.OutputString("CommandEvents, BeforeExecute\n");
                if (commandName != "")
                    _outputWindowPane.OutputString("\tCommand name: " + commandName + "\n");

                _outputWindowPane.OutputString("\tCommand GUID/ID: " + Guid + ", " + ID.ToString() + "\n");
            }

            //BuildEvents
            public void OnBuildBegin(EnvDTE.vsBuildScope Scope, EnvDTE.vsBuildAction Action)
            {
                _outputWindowPane.OutputString("BuildEvents, OnBuildBegin\n");
            }

            public void OnBuildDone(EnvDTE.vsBuildScope Scope, EnvDTE.vsBuildAction Action)
            {
                _outputWindowPane.OutputString("BuildEvents, OnBuildDone\n");
            }

            public void OnBuildProjConfigBegin(string project, string projectConfig, string platform, string solutionConfig)
            {
                _outputWindowPane.OutputString("BuildEvents, OnBuildProjConfigBegin\n");
                _outputWindowPane.OutputString("\tProject: " + project + "\n");
                _outputWindowPane.OutputString("\tProject Configuration: " + projectConfig + "\n");
                _outputWindowPane.OutputString("\tPlatform: " + platform + "\n");
                _outputWindowPane.OutputString("\tSolution Configuration: " + solutionConfig + "\n");
            }

            public void OnBuildProjConfigDone(string project, string projectConfig, string platform, string solutionConfig, bool success)
            {
                _outputWindowPane.OutputString("BuildEvents, OnBuildProjConfigDone\n");
                _outputWindowPane.OutputString("\tProject: " + project + "\n");
                _outputWindowPane.OutputString("\tProject Configuration: " + projectConfig + "\n");
                _outputWindowPane.OutputString("\tPlatform: " + platform + "\n");
                _outputWindowPane.OutputString("\tSolution Configuration: " + solutionConfig + "\n");
                _outputWindowPane.OutputString("\tBuild success: " + success.ToString() + "\n");
            }

            //MiscFilesEvents
            public void MiscFilesEvents_ItemAdded(EnvDTE.ProjectItem projectItem)
            {
                _outputWindowPane.OutputString("MiscFilesEvents, ItemAdded\n");
                _outputWindowPane.OutputString("\tProject Item: " + projectItem.Name + "\n");
            }

            public void MiscFilesEvents_ItemRemoved(EnvDTE.ProjectItem projectItem)
            {
                _outputWindowPane.OutputString("MiscFilesEvents, ItemRemoved\n");
                _outputWindowPane.OutputString("\tProject Item: " + projectItem.Name + "\n");
            }

            public void MiscFilesEvents_ItemRenamed(EnvDTE.ProjectItem projectItem, string OldName)
            {
                _outputWindowPane.OutputString("MiscFilesEvents, ItemRenamed\n");
                _outputWindowPane.OutputString("\tProject Item: " + projectItem.Name + "\n");
            }

            //SolutionItemsEvents
            public void SolutionItemsEvents_ItemAdded(EnvDTE.ProjectItem projectItem)
            {
                _outputWindowPane.OutputString("SolutionItemsEvents, ItemAdded\n");
                _outputWindowPane.OutputString("\tProject Item: " + projectItem.Name + "\n");
            }

            public void SolutionItemsEvents_ItemRemoved(EnvDTE.ProjectItem projectItem)
            {
                _outputWindowPane.OutputString("SolutionItemsEvents, ItemRemoved\n");
                _outputWindowPane.OutputString("\tProject Item: " + projectItem.Name + "\n");
            }

            public void SolutionItemsEvents_ItemRenamed(EnvDTE.ProjectItem projectItem, string OldName)
            {
                _outputWindowPane.OutputString("SolutionItemsEvents, ItemRenamed\n");
                _outputWindowPane.OutputString("\tProject Item: " + projectItem.Name + "\n");
            }

            //Global ProjectItemsEvents
            public void GlobalProjectItemsEvents_ItemAdded(EnvDTE.ProjectItem projectItem)
            {
                _outputWindowPane.OutputString("GlobalProjectItemsEvents, ItemAdded\n");
                _outputWindowPane.OutputString("\tProject Item: " + projectItem.Name + "\n");
            }

            public void GlobalProjectItemsEvents_ItemRemoved(EnvDTE.ProjectItem projectItem)
            {
                _outputWindowPane.OutputString("GlobalProjectItemsEvents, ItemRemoved\n");
                _outputWindowPane.OutputString("\tProject Item: " + projectItem.Name + "\n");
            }

            public void GlobalProjectItemsEvents_ItemRenamed(EnvDTE.ProjectItem projectItem, string OldName)
            {
                _outputWindowPane.OutputString("GlobalProjectItemsEvents, ItemRenamed\n");
                _outputWindowPane.OutputString("\tProject Item: " + projectItem.Name + "\n");
            }

            //Global ProjectsEvents
            public void GlobalProjectsEvents_ItemAdded(EnvDTE.Project project)
            {
                _outputWindowPane.OutputString("GlobalProjectsEvents, ItemAdded\n");
                _outputWindowPane.OutputString("\tProject: " + project.Name + "\n");
            }

            public void GlobalProjectsEvents_ItemRemoved(EnvDTE.Project project)
            {
                _outputWindowPane.OutputString("GlobalProjectsEvents, ItemRemoved\n");
                _outputWindowPane.OutputString("\tProject: " + project.Name + "\n");
            }

            public void GlobalProjectsEvents_ItemRenamed(EnvDTE.Project project, string OldName)
            {
                _outputWindowPane.OutputString("GlobalProjectsEvents, ItemRenamed\n");
                _outputWindowPane.OutputString("\tProject: " + project.Name + "\n");
            }

            //TextDocumentKeyPressEvents
            public void AfterKeyPress(string Keypress, EnvDTE.TextSelection Selection, bool InStatementCompletion)
            {
                _outputWindowPane.OutputString("TextDocumentKeyPressEvents, AfterKeyPress\n");
                _outputWindowPane.OutputString("\tKey: " + Keypress + "\n");
                _outputWindowPane.OutputString("\tSelection: " + Selection.Text + "\n");
                _outputWindowPane.OutputString("\tInStatementCompletion: " + InStatementCompletion.ToString() + "\n");
            }

            public void BeforeKeyPress(string Keypress, EnvDTE.TextSelection Selection, bool InStatementCompletion, ref bool CancelKeypress)
            {
                _outputWindowPane.OutputString("TextDocumentKeyPressEvents, BeforeKeyPress\n");
                _outputWindowPane.OutputString("\tKey: " + Keypress + "\n");
                _outputWindowPane.OutputString("\tSelection: " + Selection.Text + "\n");
                _outputWindowPane.OutputString("\tInStatementCompletion: " + InStatementCompletion.ToString() + "\n");
            }

            //CodeModelEvents
            public void ElementAdded(EnvDTE.CodeElement Element)
            {
                _outputWindowPane.OutputString("CodeModelEvents, ElementAdded\n");
                _outputWindowPane.OutputString("\tElement: " + Element.FullName + "\n");
            }

            public void ElementChanged(EnvDTE.CodeElement Element, EnvDTE80.vsCMChangeKind Change)
            {
                _outputWindowPane.OutputString("CodeModelEvents, ElementChanged\n");
                _outputWindowPane.OutputString("\tElement: " + Element.FullName + "\n");
                _outputWindowPane.OutputString("\tChange: " + Change.ToString() + "\n");
            }

            public void ElementDeleted(object Parent, EnvDTE.CodeElement Element)
            {
                _outputWindowPane.OutputString("CodeModelEvents, ElementDeleted\n");
                _outputWindowPane.OutputString("\tElement: " + Element.FullName + "\n");
                if (Parent is CodeElement)
                {
                    _outputWindowPane.OutputString("\tParent: " + ((CodeElement)Parent).FullName + "\n");
                }
                else if (Parent is ProjectItem)
                {
                    _outputWindowPane.OutputString("\tParent: " + ((ProjectItem)Parent).get_FileNames(0) + "\n");
                }
            }

            //WindowVisibilityEvents
            public void WindowHiding(EnvDTE.Window pWindow)
            {
                _outputWindowPane.OutputString("WindowVisibilityEvents, WindowHiding\n");
                _outputWindowPane.OutputString("\tWindow: " + pWindow.Caption + "\n");
            }

            public void WindowShowing(EnvDTE.Window pWindow)
            {
                _outputWindowPane.OutputString("WindowVisibilityEvents, WindowShowing\n");
                _outputWindowPane.OutputString("\tWindow: " + pWindow.Caption + "\n");
            }

            //DebuggerProcessEvents
            public void OnProcessStateChanged(EnvDTE.Process NewProcess, EnvDTE80.dbgProcessState processState)
            {
                _outputWindowPane.OutputString("DebuggerProcessEvents, OnProcessStateChanged\n");
                _outputWindowPane.OutputString("\tNew Process: " + NewProcess.Name + "\n");
                _outputWindowPane.OutputString("\tProcess State: " + processState.ToString() + "\n");
            }

            //DebuggerExpressionEvaluationEvents
            public void OnExpressionEvaluation(EnvDTE.Process pProcess, EnvDTE.Thread thread, EnvDTE80.dbgExpressionEvaluationState processState)
            {
                _outputWindowPane.OutputString("DebuggerExpressionEvaluationEvents, OnExpressionEvaluation\n");
                _outputWindowPane.OutputString("\tProcess: " + pProcess.Name + "\n");
                _outputWindowPane.OutputString("\tThread: " + thread.Name + "\n");
                _outputWindowPane.OutputString("\tExpression Evaluation State: " + processState.ToString() + "\n");
            }

            //PublishEvents
            public void OnPublishBegin(ref bool Continue)
            {
                _outputWindowPane.OutputString("PublishEvents, OnPublishBegin\n");
                Continue = true;
            }

            public void OnPublishDone(bool Success)
            {
                _outputWindowPane.OutputString("PublishEvents, OnPublishDone\n");
                _outputWindowPane.OutputString("\tSuccess: " + Success.ToString() + "\n");
            }

            #endregion

    Usuwanie handlerów zdarzeń

    #region EventWatcher - Sub

    if (testEvents)
    {
        //If the delegate handlers have been connected, then disconnect them here.
        //    This needs to be done, otherwise the handler may still fire since they
        //    have not been garbage collected.
        if (_windowsEvents != null)
        {
            _windowsEvents.WindowActivated -= new _dispWindowEvents_WindowActivatedEventHandler(this.WindowActivated);
            _windowsEvents.WindowClosing -= new _dispWindowEvents_WindowClosingEventHandler(this.WindowClosing);
            _windowsEvents.WindowCreated -= new _dispWindowEvents_WindowCreatedEventHandler(this.WindowCreated);
            _windowsEvents.WindowMoved -= new _dispWindowEvents_WindowMovedEventHandler(this.WindowMoved);
        }

        if (_textEditorEvents != null)
            _textEditorEvents.LineChanged -= new _dispTextEditorEvents_LineChangedEventHandler(this.LineChanged);

        if (_taskListEvents != null)
        {
            _taskListEvents.TaskAdded -= new _dispTaskListEvents_TaskAddedEventHandler(this.TaskAdded);
            _taskListEvents.TaskModified -= new _dispTaskListEvents_TaskModifiedEventHandler(this.TaskModified);
            _taskListEvents.TaskNavigated -= new _dispTaskListEvents_TaskNavigatedEventHandler(this.TaskNavigated);
            _taskListEvents.TaskRemoved -= new _dispTaskListEvents_TaskRemovedEventHandler(this.TaskRemoved);
        }

        if (_solutionEvents != null)
        {
            _solutionEvents.AfterClosing -= new _dispSolutionEvents_AfterClosingEventHandler(this.AfterClosing);
            _solutionEvents.BeforeClosing -= new _dispSolutionEvents_BeforeClosingEventHandler(this.BeforeClosing);
            _solutionEvents.Opened -= new _dispSolutionEvents_OpenedEventHandler(this.Opened);
            _solutionEvents.ProjectAdded -= new _dispSolutionEvents_ProjectAddedEventHandler(this.ProjectAdded);
            _solutionEvents.ProjectRemoved -= new _dispSolutionEvents_ProjectRemovedEventHandler(this.ProjectRemoved);
            _solutionEvents.ProjectRenamed -= new _dispSolutionEvents_ProjectRenamedEventHandler(this.ProjectRenamed);
            _solutionEvents.QueryCloseSolution -= new _dispSolutionEvents_QueryCloseSolutionEventHandler(this.QueryCloseSolution);
            _solutionEvents.Renamed -= new _dispSolutionEvents_RenamedEventHandler(this.Renamed);
        }

        if (_selectionEvents != null)
            _selectionEvents.OnChange -= new _dispSelectionEvents_OnChangeEventHandler(this.OnChange);

        if (_outputWindowEvents != null)
        {
            _outputWindowEvents.PaneAdded -= new _dispOutputWindowEvents_PaneAddedEventHandler(this.PaneAdded);
            _outputWindowEvents.PaneClearing -= new _dispOutputWindowEvents_PaneClearingEventHandler(this.PaneClearing);
            _outputWindowEvents.PaneUpdated -= new _dispOutputWindowEvents_PaneUpdatedEventHandler(this.PaneUpdated);
        }

        if (_findEvents != null)
            _findEvents.FindDone -= new _dispFindEvents_FindDoneEventHandler(this.FindDone);

        if (_dteEvents != null)
        {
            _dteEvents.ModeChanged -= new _dispDTEEvents_ModeChangedEventHandler(this.ModeChanged);
            _dteEvents.OnBeginShutdown -= new _dispDTEEvents_OnBeginShutdownEventHandler(this.OnBeginShutdown);
            _dteEvents.OnMacrosRuntimeReset -= new _dispDTEEvents_OnMacrosRuntimeResetEventHandler(this.OnMacrosRuntimeReset);
            _dteEvents.OnStartupComplete -= new _dispDTEEvents_OnStartupCompleteEventHandler(this.OnStartupComplete);
        }

        if (_documentEvents != null)
        {
            _documentEvents.DocumentClosing -= new _dispDocumentEvents_DocumentClosingEventHandler(this.DocumentClosing);
            _documentEvents.DocumentOpened -= new _dispDocumentEvents_DocumentOpenedEventHandler(this.DocumentOpened);
            _documentEvents.DocumentOpening -= new _dispDocumentEvents_DocumentOpeningEventHandler(this.DocumentOpening);
            _documentEvents.DocumentSaved -= new _dispDocumentEvents_DocumentSavedEventHandler(this.DocumentSaved);
        }

        if (_debuggerEvents != null)
        {
            _debuggerEvents.OnContextChanged -= new _dispDebuggerEvents_OnContextChangedEventHandler(this.OnContextChanged);
            _debuggerEvents.OnEnterBreakMode -= new _dispDebuggerEvents_OnEnterBreakModeEventHandler(this.OnEnterBreakMode);
            _debuggerEvents.OnEnterDesignMode -= new _dispDebuggerEvents_OnEnterDesignModeEventHandler(this.OnEnterDesignMode);
            _debuggerEvents.OnEnterRunMode -= new _dispDebuggerEvents_OnEnterRunModeEventHandler(this.OnEnterRunMode);
            _debuggerEvents.OnExceptionNotHandled -= new _dispDebuggerEvents_OnExceptionNotHandledEventHandler(this.OnExceptionNotHandled);
            _debuggerEvents.OnExceptionThrown -= new _dispDebuggerEvents_OnExceptionThrownEventHandler(this.OnExceptionThrown);
        }

        if (_commandEvents != null)
        {
            _commandEvents.AfterExecute -= new _dispCommandEvents_AfterExecuteEventHandler(this.AfterExecute);
            _commandEvents.BeforeExecute -= new _dispCommandEvents_BeforeExecuteEventHandler(this.BeforeExecute);
        }

        if (_buildEvents != null)
        {
            _buildEvents.OnBuildBegin -= new _dispBuildEvents_OnBuildBeginEventHandler(this.OnBuildBegin);
            _buildEvents.OnBuildDone -= new _dispBuildEvents_OnBuildDoneEventHandler(this.OnBuildDone);
            _buildEvents.OnBuildProjConfigBegin -= new _dispBuildEvents_OnBuildProjConfigBeginEventHandler(this.OnBuildProjConfigBegin);
            _buildEvents.OnBuildProjConfigDone -= new _dispBuildEvents_OnBuildProjConfigDoneEventHandler(this.OnBuildProjConfigDone);
        }

        if (_miscFilesEvents != null)
        {
            _miscFilesEvents.ItemAdded -= new _dispProjectItemsEvents_ItemAddedEventHandler(this.MiscFilesEvents_ItemAdded);
            _miscFilesEvents.ItemRemoved -= new _dispProjectItemsEvents_ItemRemovedEventHandler(this.MiscFilesEvents_ItemRemoved);
            _miscFilesEvents.ItemRenamed -= new _dispProjectItemsEvents_ItemRenamedEventHandler(this.MiscFilesEvents_ItemRenamed);
        }

        if (_solutionItemsEvents != null)
        {
            _solutionItemsEvents.ItemAdded -= new _dispProjectItemsEvents_ItemAddedEventHandler(this.SolutionItemsEvents_ItemAdded);
            _solutionItemsEvents.ItemRemoved -= new _dispProjectItemsEvents_ItemRemovedEventHandler(this.SolutionItemsEvents_ItemRemoved);
            _solutionItemsEvents.ItemRenamed -= new _dispProjectItemsEvents_ItemRenamedEventHandler(this.SolutionItemsEvents_ItemRenamed);
        }

        if (_globalProjectItemsEvents != null)
        {
            _globalProjectItemsEvents.ItemAdded -= new _dispProjectItemsEvents_ItemAddedEventHandler(GlobalProjectItemsEvents_ItemAdded);
            _globalProjectItemsEvents.ItemRemoved -= new _dispProjectItemsEvents_ItemRemovedEventHandler(GlobalProjectItemsEvents_ItemRemoved);
            _globalProjectItemsEvents.ItemRenamed -= new _dispProjectItemsEvents_ItemRenamedEventHandler(GlobalProjectItemsEvents_ItemRenamed);
        }

        if (_globalProjectsEvents != null)
        {
            _globalProjectsEvents.ItemAdded -= new _dispProjectsEvents_ItemAddedEventHandler(GlobalProjectsEvents_ItemAdded);
            _globalProjectsEvents.ItemRemoved -= new _dispProjectsEvents_ItemRemovedEventHandler(GlobalProjectsEvents_ItemRemoved);
            _globalProjectsEvents.ItemRenamed -= new _dispProjectsEvents_ItemRenamedEventHandler(GlobalProjectsEvents_ItemRenamed);
        }

        if (_textDocumentKeyPressEvents != null)
        {
            _textDocumentKeyPressEvents.AfterKeyPress -= new _dispTextDocumentKeyPressEvents_AfterKeyPressEventHandler(AfterKeyPress);
            _textDocumentKeyPressEvents.BeforeKeyPress -= new _dispTextDocumentKeyPressEvents_BeforeKeyPressEventHandler(BeforeKeyPress);
        }

        if (_codeModelEvents != null)
        {
            _codeModelEvents.ElementAdded -= new _dispCodeModelEvents_ElementAddedEventHandler(ElementAdded);
            _codeModelEvents.ElementChanged -= new _dispCodeModelEvents_ElementChangedEventHandler(ElementChanged);
            _codeModelEvents.ElementDeleted -= new _dispCodeModelEvents_ElementDeletedEventHandler(ElementDeleted);
        }

        if (_windowVisibilityEvents != null)
        {
            _windowVisibilityEvents.WindowHiding -= new _dispWindowVisibilityEvents_WindowHidingEventHandler(WindowHiding);
            _windowVisibilityEvents.WindowShowing -= new _dispWindowVisibilityEvents_WindowShowingEventHandler(WindowShowing);
        }

        if (_debuggerExpressionEvaluationEvents != null)
        {
            _debuggerExpressionEvaluationEvents.OnExpressionEvaluation -= new _dispDebuggerExpressionEvaluationEvents_OnExpressionEvaluationEventHandler(OnExpressionEvaluation);
        }

        if (_debuggerProcessEvents != null)
        {
            _debuggerProcessEvents.OnProcessStateChanged -= new _dispDebuggerProcessEvents_OnProcessStateChangedEventHandler(OnProcessStateChanged);
        }

        if (_publishEvents != null)
        {
            _publishEvents.OnPublishBegin -= new _dispPublishEvents_OnPublishBeginEventHandler(OnPublishBegin);
            _publishEvents.OnPublishDone -= new _dispPublishEvents_OnPublishDoneEventHandler(OnPublishDone);
        }

    }

    #endregion

    Efekt…

    I ostatecznie w działającej wtyczce możemy obserwować “odpalanie” zdarzeń w SSMS.

    image

    Linki:

    do poprzedniego odcinka serii o tworzeniu wtyczki do SSMS

    do następnego odcinka serii o tworzeniu wtyczki do SSMS.

  • Dziennik pokładowy MSSQL 2008 (wpis nr 0x0010) – Wyszło zupełnie przypadkiem…

    Stworzyłem procedurę składowaną z błędem przypisania domyślnego parametru:

    image

    I jak widać zostało to “kupione”.

    Ale próba uruchomienia takiej procedury kończy sie ciekawym błędem:

    image

    Prawdę mówiąc to spodziewałem się komunikatu w stylu “Undefined object bla, bla, bla….” ale, że będzie to w locie konwertowane na nvarchar to się nie spodziewałem :).

    Nieco jaśniej zrobi sie gdy przejdziemy do definicji parametru procedury składowanej:

    image

    oraz definicji default

    default

    Is a default value for the parameter. If a default is defined, the procedure can be executed without specifying a value for that parameter. The default must be a constant or it can be NULL. It can include wildcard characters (%, _, [], and [^]) if the procedure uses the parameter with the LIKE keyword.

    Czyli moje NUL zostało zinterpretowane jako stała typu nvarchar (dlaczego nie varchar, nchar lub char pozostanie tajemnicą, chociaż z typów char-owych ma najwyższy priorytet jak wynika z tabeli Data Type Precedence).

    Stała w parametrze musi być pojedynczym tokenem, nie przejdzie coś takiego jak poniżej chociaż jest to również stały napis i można by go interpretować na analogicznych zasadach:

    image

    Pierwszy przypadek jakoś z trudem “łyknę” ale drugi siałby zamęt w kodzie więc może i lepiej, że tego nie puszcza.

    opublikowano 7 października 2009 19:31 przez marekpow | 0 komentarzy
    Filed under: ,
W oparciu o Community Server (Personal Edition), Telligent Systems