<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;DU4ER3o7fSp7ImA9WhRaFE4.&quot;"><id>tag:blogger.com,1999:blog-14793170</id><updated>2012-02-17T01:18:26.405+01:00</updated><category term="Code" /><category term="ReplicationExplorer" /><category term="SQL Server 2008" /><category term="English" /><category term="Trigger" /><category term="Statystyka" /><category term="SQL Server" /><category term="SharePoint" /><category term="Virtual" /><category term="XML" /><category term="Replikacja" /><category term="SQL Tipsy" /><category term="Documentation" /><category term="Windows" /><category term="SQLCLR" /><category term="WPF" /><category term="Weather.com" /><category term=".NET" /><category term="ADO.NET" /><title>Blog Maksymiliana Mulawy</title><subtitle type="html">Żadne pytanie nie jest głupie dopóki jest zadawane po raz pierwszy.</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://mmulawa.blogspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>52</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/BlogMaksymilianaMulawy" /><feedburner:info uri="blogmaksymilianamulawy" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;A08FSHo6eip7ImA9WhRVFE4.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-783580598736201607</id><published>2012-01-11T22:12:00.002+01:00</published><updated>2012-01-13T08:30:19.412+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-13T08:30:19.412+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Code" /><title>Code review, lubię to!</title><content type="html">&lt;h4&gt;Czy na pewno code review to strata czasu?&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-NOyrpYbqDP0/Tw34s5RM0xI/AAAAAAAAKKI/_lHWgQLdNug/s1600/review.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-NOyrpYbqDP0/Tw34s5RM0xI/AAAAAAAAKKI/_lHWgQLdNug/s1600/review.png" /&gt;&lt;/a&gt;&lt;/div&gt;Cóż może być nudniejsze i bardziej kosztowne niż przeglądanie cudzego kodu źródłowego? Chyba już tylko poprawnianie w nim błędów. Jeszcze kilka lat temu koncepcja przeglądania mojego kodu przez kolegów z zespołu była dla mnie totalną abstrakcją. Aczkolwiek, z czasem zrozumiałem, że w tym szaleństwie jest metoda. W kilku punktach chciałbym przedstawić benefity, które może wprowadzić Code Review do Twojego ekosystemu projektowego drogi Czytelniku.  &lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Edukacja&lt;/h4&gt;&lt;br /&gt;
Mam w zespole kolegę, które właśnie przez code review nauczył mnie na nowo pisać obiektowo. Było to trochę karkołomne, gdyż po pierwszym review dostałem ~30 komentarzy do kilku plików kodu, ale opłacało się. W ten sposób przekazał mi swoją wiedzę, cenne uwagi, które inaczej zatrzymałby dla siebie, a kod źródłowy pozostałby w ciężkim stanie. Edukacja w obrębie zespołu jest potrzebna, ale nie dokońca wiadomo jak bardziej "ogarnięci" programiści mogą dzielić się swoim doświadczeniem ze swoimi "młodszymi" kolegami (np. inną opcją jest programowanie w parach Junior-Senior). Code review stwarza takie możliwości, wspomniani Juniorzy to mogą być też nowe osoby w projekcie, które nie poznały jeszcze wszystkich meandrów kodu z którym pracują.   &lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Standardy i jakość&lt;/h4&gt;&lt;br /&gt;
Przy większych, a nawet kilku osobowych zespołach wymuszenie pewnych kowencji jest bardzo trudne, chyba, że wszystko zautomatyzujemy StyleCop'em i będziemy wymuszać pewne reguły na poziomie kompilacji. Nie zawsze to może być praktycznym rozwiązaniem, a review może być tutaj dobrym narzędziem do "nadzoru", ponieważ wtedy jest to idealny moment na "zwrócenie uwagi" swojemu koledze z zespołu, że w tym miejscu "dał ciała". &lt;br /&gt;
&lt;br /&gt;
Inną sprawą, że dobry reviewer w pewnym sensie bierze częściową odpowiedzialność za jakość kodu źródłowego. Pomimo, że nie jest on autorem, podpisuje się imieniem i nazwiskiem, że kod, który widział jest "produkcyjny". &lt;br /&gt;
&lt;br /&gt;
Często code review może odkryć więcej błędów, niż proces testowania. Jest to też ważny atut, który można w łatwy sposób przetłumaczyć na gotówkę. &lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Kanał komunikacyjny&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-C9HCKdfWy0M/Tw35KdofHcI/AAAAAAAAKKQ/v2ETm1n0w8A/s1600/communicate.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-C9HCKdfWy0M/Tw35KdofHcI/AAAAAAAAKKQ/v2ETm1n0w8A/s1600/communicate.png" /&gt;&lt;/a&gt;&lt;/div&gt;W rozproszonych zespołach, np. nie pracujących razem w jednym pokoju, budynku, mieście, kraju komunikacja między-developerska jest znacząco utrudniona. Code review może być okazją, żeby przedyskutować pewne rozwiązania, ewentualnie zaproponawać bardziej eleganckie rozwiązanie danego problemu.&amp;nbsp; &lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Narzędzia&lt;/h4&gt;&lt;br /&gt;
Codre review możę być nieformalne i uwagi mogą być przesłane email'em, lub zapisane w systemie do rejestracji zmian lub bug'ów. Mi zdarzyło się używać &lt;a href="http://www.atlassian.com/software/crucible/overview"&gt;Crucible'a&lt;/a&gt;, aczkolwiek nie jest on bezpłatny, ale warty swojej ceny, jest też trochę rozwiązań open-source, ale nie miałem z nimi żadnego doświadczenia. &lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Podsumowanie&lt;/h4&gt;&lt;br /&gt;
Jeżeli masz w zespole spore zróżnicowanie umiejętności, problemy z wprowadzeniem pewnych konwencji, standardów to może na próbę trzeba dać szansę code review. Na koniec kilka wskazówek:&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;Code Review powinno być wykonane przez doświadczonego programistę, mogą być też rotacje dyżurujących reviewer'ów, lub można ustalić pary programistów, które sprawdzają sobie kod.&amp;nbsp; &lt;/li&gt;
&lt;li&gt;Code Review można wykonać po zakończonym zadaniu, lub po jakimś etapie zadania&lt;/li&gt;
&lt;li&gt;Najlepiej jak jedna osoba przegląda modyfikacje wykonane przez tylko jedną osobę, żeby nie wprowadzić zamieszania (tutaj przydaje się system do zarządzania code review i zmianami w repozytorium kodu).&lt;/li&gt;
&lt;li&gt;Każda zmiana, nawet drobna nadaje się do weryfikacji przez inną osobę, gdyż może to zapobiec potencjalnym błędom, które w przyszłości będą już trudniejsze i bardziej kosztowne do naprawienia.&lt;/li&gt;
&lt;/ol&gt;Jestem ciekawy, czy code review jest już standardem na naszej polskiej ziemi, i jak ono funkcjonuje w Waszych zespołach. &lt;br /&gt;
&lt;br /&gt;
Hope this helps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-783580598736201607?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/gHyWXnjIp1M" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/783580598736201607/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2012/01/code-review-lubie-to.html#comment-form" title="Komentarze (4)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/783580598736201607?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/783580598736201607?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/gHyWXnjIp1M/code-review-lubie-to.html" title="Code review, lubię to!" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-NOyrpYbqDP0/Tw34s5RM0xI/AAAAAAAAKKI/_lHWgQLdNug/s72-c/review.png" height="72" width="72" /><thr:total>4</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2012/01/code-review-lubie-to.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEMER3o5fyp7ImA9WhRRFUk.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-1998864305480494790</id><published>2011-11-29T07:00:00.014+01:00</published><updated>2011-11-29T07:00:06.427+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-29T07:00:06.427+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SQL Server 2008" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL Tipsy" /><title>SQL Tipsy (3) – deklaracja i inicjalizacja zmiennych w MS SQL Server 2008</title><content type="html">&lt;h4&gt;Można prościej&lt;/h4&gt;&lt;br /&gt;
Pewnie większość programistów SQL Server'a już zapoznała się ze zmianami w sposobie deklarowania i inicjalizacji zmiennych w wersji MS SQL Server 2008. Aczkolwiek, dla tych którzy jeszcze o tym nie czytali przygotowałem krótkie demo (dzisiaj 289 sekund, więc trochę długo), w którym przepisuje kod napisany w stylu T-SQL'a z MSSQL 2000 na wersję 2008. &lt;br /&gt;
&lt;br /&gt;
&lt;iframe allowfullscreen="" frameborder="0" height="430" src="http://www.youtube.com/embed/WH2xBAlWOyc" width="620"&gt;&lt;/iframe&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;Kod przed modyfikacjami:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;IF OBJECT_ID('dbo.temp','U') IS NOT NULL DROP TABLE dbo.temp
CREATE TABLE temp(id int)

IF OBJECT_ID('dbo.temp2','U') IS NOT NULL DROP TABLE dbo.temp2
CREATE TABLE temp2(id int)

DECLARE @i int 
      , @concatenated VARCHAR(100)
   , @myDate DATETIME
SET @i = 0
SET @concatenated = ''
SET @myDate = GETDATE()
WHILE (@i &amp;lt; 10)
BEGIN
 INSERT INTO temp(id)
 SELECT 1
 UNION
 SELECT 2

 INSERT INTO temp2(id)
 SELECT a.id
   FROM (
  SELECT 3 id
  UNION
  SELECT 4
  ) a
 SET @i = @i + 1
 SET @concatenated = @concatenated + CAST(@i AS VARCHAR)
 SET @myDate = @myDate + 1
END

SELECT (SELECT COUNT(*) FROM temp) AS TempCount
     , (SELECT COUNT(*) FROM temp2) AS Temp2Count
     , @concatenated AS Concatenated
     , @myDate AS MyDate
 
&lt;/pre&gt;&lt;br /&gt;
Kod po modyfikacjach:  &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;IF EXISTS(SELECT * FROM sys.types WHERE name = 'temptables') DROP TYPE dbo.temptables
CREATE TYPE dbo.temptables AS TABLE ( Id int );
GO
DECLARE @temp AS dbo.temptables
     , @temp2 AS dbo.temptables;
DECLARE @i int = 0 
      , @concatenated VARCHAR(100) = ''
   , @myDate DATETIME = GETDATE();
WHILE (@i &amp;lt; 10)
BEGIN
 INSERT INTO @temp(id)
 VALUES (1), (2)

 INSERT INTO @temp2(id)
 SELECT a.id
   FROM (
  VALUES( 3 ) , (4)
  ) a (id)
 SET @i += 1
 SET @concatenated += CAST(@i AS VARCHAR)
 SET @myDate += 1
END

SELECT (SELECT COUNT(*) FROM @temp) AS TempCount
     , (SELECT COUNT(*) FROM @temp2) AS Temp2Count
     , @concatenated AS Concatenated
     , @myDate AS MyDate
&lt;/pre&gt;&lt;br /&gt;
Hope this helps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-1998864305480494790?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/Zf6EdvyUbvg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/1998864305480494790/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/11/sql-tipsy-3-deklaracja-i-inicjalizacja.html#comment-form" title="Komentarze (1)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/1998864305480494790?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/1998864305480494790?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/Zf6EdvyUbvg/sql-tipsy-3-deklaracja-i-inicjalizacja.html" title="SQL Tipsy (3) – deklaracja i inicjalizacja zmiennych w MS SQL Server 2008" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://img.youtube.com/vi/WH2xBAlWOyc/default.jpg" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/11/sql-tipsy-3-deklaracja-i-inicjalizacja.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEIFRHY8cSp7ImA9WhRREkk.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-3239216575056202469</id><published>2011-11-25T19:41:00.001+01:00</published><updated>2011-11-25T19:41:55.879+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-25T19:41:55.879+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SharePoint" /><title>Dwie instancje webpart'ów oraz IsClosed</title><content type="html">&lt;h4&gt;Tańcowały dwa webpart'y&lt;/h4&gt;&lt;br /&gt;
Praca z webpart'ami od strony SharePoint API może być czasami frustrująca. Kilkukrotnie zdarzyło mi się exportować konfigurację, usuwać instancję, zmieniać właściwości webpartów. Przynajmniej trzy razy byłem zaskoczony tym, że strona wyświetla np. jednego webpart'a a kolekcja &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.webpartpages.splimitedwebpartmanager.webparts.aspx"&gt;Webparts&lt;/a&gt; z obiektu &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.webpartpages.splimitedwebpartmanager.aspx"&gt;SPLimitedWebPartManager&lt;/a&gt; zwraca więcej instancji, często tego samego typu. &lt;br /&gt;
&lt;br /&gt;
Pewnie domyślacie się, że te dodatkowe instancje webpart'ów to zamknięte, a nie usunięte webparty'y. Rezydują sobie one szczęśliwe w kontentowej bazie danych, aczkolwiek nie są wyświetlane w UI.&lt;br /&gt;
&lt;br /&gt;
Wszystko byłoby wporządku, gdybym już trzeci raz o tym nie zapomniał, że jeżeli modyfikuje widoczne webpart'y, to muszę wziąć pod uwagę też te niewidoczne instancje. &lt;br /&gt;
&lt;br /&gt;
Moim zadaniem było usunięcie duplikatów webpartów, niestety z "jakiegoś" powodu zawsze usunwałem tylko widocznego webpart'a, a niewidoczny zostawał na stronie. Nie mogłem też zrozumieć skąd w kolekcji WebParts jest tyle elementów. &lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Podglądam webpart'a&lt;/h4&gt;&lt;br /&gt;
W celu wizualizacji mojego problemu, utworzyłem nową witrynę przy użyciu szablonu 'Blank'. Wybrałem tryb edycji strony i dodałem dwa takie same webparty.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-DOnqe3pGRp0/Ts9k_ey5rMI/AAAAAAAAJ4E/eujzSOtD5KQ/s1600/AddWebPart.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="384" src="http://4.bp.blogspot.com/-DOnqe3pGRp0/Ts9k_ey5rMI/AAAAAAAAJ4E/eujzSOtD5KQ/s640/AddWebPart.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Poniżej widać sztuk dwie "Html Form WebPart".&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/--WPU22BAkDQ/Ts9lYzwI-JI/AAAAAAAAJ4M/oCFo0vNE-Qc/s1600/TwoWebParts.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="100" src="http://2.bp.blogspot.com/--WPU22BAkDQ/Ts9lYzwI-JI/AAAAAAAAJ4M/oCFo0vNE-Qc/s640/TwoWebParts.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Zamykamy drugą instancję webpart'a.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-vHWihbmRvew/Ts9mEM_RstI/AAAAAAAAJ4U/UaFXaJS7eBw/s1600/ClosedWebpart.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-vHWihbmRvew/Ts9mEM_RstI/AAAAAAAAJ4U/UaFXaJS7eBw/s1600/ClosedWebpart.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
I wtedy jako użytkownik widzę tylko jednego webparta. I niestety ciągle zapominam, że webpart'y mogą żyć w najlepsze będąc zamknięte. Teraz krótko o tym, jak sobie uświadomiłem, że użytkownik może zamykać webpart'y, a ich instancje są dalej dostępne z poziomu API, no i oczywiście z poziomu edytora webpart'ów w UI (pojawia się wtedy nowa kategoria Closed Web Parts).&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-kUo19EnNmVg/Ts97jSMbRMI/AAAAAAAAJ4c/3gTZ68GiEj4/s1600/ClosedWebaprtCategory.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-kUo19EnNmVg/Ts97jSMbRMI/AAAAAAAAJ4c/3gTZ68GiEj4/s1600/ClosedWebaprtCategory.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;h4&gt;Odkrycie zamkniętego webpart'a&lt;/h4&gt;&lt;br /&gt;
W końcu z braku pomysłów napisałem skrypt w Powershell'u który wyświetlił wszystkie instancje webpart'ów wraz z ich właściwościami. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;#DumpWebpartContent.ps1
$site = Get-SPSite "http://webapp/sites/myteam"
$web = $site.RootWeb;
$wpManager = $web.GetLimitedWebPartManager("Default.aspx", [System.Web.UI.WebControls.WebParts.PersonalizationScope]::Shared)
foreach($webpart in $wpManager.Webparts)
{
 #this will dump webpart members content
 $webpart
}
$wpManager.Web.Dispose();
$wpManager.Dispose();
&lt;/pre&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-13psNmT7yZw/Ts9-aQCGJ1I/AAAAAAAAJ4k/ucs-8eQLMnU/s1600/WebPartDump.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="167" src="http://3.bp.blogspot.com/-13psNmT7yZw/Ts9-aQCGJ1I/AAAAAAAAJ4k/ucs-8eQLMnU/s640/WebPartDump.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
Zapisałem informacje zwrócone przez skypt i uruchomiłem &lt;a href="http://winmerge.org/"&gt;WinMerge&lt;/a&gt;, żeby porównać dwie instancje webaprtów. &lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-B9ZzTO-Udjg/Ts9-nKHcMWI/AAAAAAAAJ4s/Do4gadokGBI/s1600/WebpartIsClosed.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="161" src="http://2.bp.blogspot.com/-B9ZzTO-Udjg/Ts9-nKHcMWI/AAAAAAAAJ4s/Do4gadokGBI/s640/WebpartIsClosed.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
Okazało się, że jedną z właściwości, którymi się różnią sie jest IsClosed. Widoczne webparty mają ją ustawioną na FALSE, a te które są "zamknięte" przez użytkownika mają tą właściwość ustawioną na TRUE. Nagle mnie oświeciło, że ten dodatkowy webpart w kolekcji WebParts to właśnie zamknięte cudo. &lt;br /&gt;
&lt;br /&gt;
Może zamiast uruchamiania tych skrytów Powershell'a powinienem pójść na kawę do kuchni, sam już nie wiem. Decyzja należy do czytelnika.&lt;br /&gt;
&lt;br /&gt;
Hope this helps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-3239216575056202469?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/-COLMlPg-Bs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/3239216575056202469/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/11/dwie-instancje-webpartow-oraz-isclosed.html#comment-form" title="Komentarze (0)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/3239216575056202469?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/3239216575056202469?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/-COLMlPg-Bs/dwie-instancje-webpartow-oraz-isclosed.html" title="Dwie instancje webpart'ów oraz IsClosed" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-DOnqe3pGRp0/Ts9k_ey5rMI/AAAAAAAAJ4E/eujzSOtD5KQ/s72-c/AddWebPart.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/11/dwie-instancje-webpartow-oraz-isclosed.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0cASXszeSp7ImA9WhRREUs.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-1919586448797394762</id><published>2011-11-24T19:57:00.001+01:00</published><updated>2011-11-24T19:57:28.581+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-24T19:57:28.581+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SharePoint" /><title>Automatyzacja SharePoint'a to konieczna konieczność</title><content type="html">&lt;h4&gt;Automatyzacja, automatyzacja, automatyzacja&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://upload.wikimedia.org/wikipedia/commons/9/93/Typing_monkey_768px.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="204" src="http://upload.wikimedia.org/wikipedia/commons/9/93/Typing_monkey_768px.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;Często zdarza mi się klikać jak przysłowiowa "małpa". Wielokrotnie powtarzać pewne akcje zamiast daną czynność zautomatyzować. Wynika to chyba z głupoty, ponieważ człowiek leniwy by to po prostu zrobił, żeby się nie narobić.   &lt;br /&gt;
&lt;br /&gt;
Kilka dni temu musiałem naprawić za pomocą skrytpu kilka witryn (chyba tak się tłumaczy site collection?), które na środowisku produkcyjnym przeszły jakieś niebezpieczne mutacje. Otrzymałem od zespołu wsparcia trzy backup'y wybranych witryn i zacząłem testowanie.&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;&lt;div&gt;Przygotowałem pustą bazę kontentową&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;Dodałem ją do wybranej aplikacji&amp;nbsp; webowej &lt;/li&gt;
&lt;li&gt;Odtworzyłem skryptem backupy witryn do nowej bazy kontentowej&lt;/li&gt;
&lt;li&gt;Zmieniłem administratorów witryn na lokalne konta&lt;/li&gt;
&lt;li&gt;Uruchamiałem skrypt naprawiający &lt;a href="http://pl.wiktionary.org/wiki/bajzel"&gt;bajzel&lt;/a&gt; na witrynach&lt;/li&gt;
&lt;li&gt;i tak w kółko (przynajmniej z 5 iteracji).&lt;/li&gt;
&lt;/ol&gt;&lt;br /&gt;
Pózniej jeszcze z 3 iteracje na środowisku testowych i w sumie 8 iteracji powyższej listy daje klikania przynajmniej na godzinę. &lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Polak mądry po szkodzie&lt;/h4&gt;&lt;br /&gt;
Po napisaniu "fix-bajzelskrypt.ps1" pozostał kac moralny. Takie ilości klikania pozostawiają uszczerbek na psychice. W ramach terapii w 15 MINUT napisałem skrypt, który to wszystko zrobi poprawnie i szybko za mnie.  &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;$contentDabaseName = "WSS_Content_001"
$webApplicationUrl = "http://webapplication"
$siteCollectionBackupsDir = "c:\backup\sitecollections\"
$siteCollectionPrefix = "http://webapplication/sites/"
$primarySiteOwner = "Domain\User1"
$secondarySiteOwner = "Domain\User2"

Remove-SPContentDatabase $contentDabaseName
New-SPContentDatabase -Name $contentDabaseName -WebApplication $webApplicationUrl &amp;gt; $out-null
$backupFiles = dir $siteCollectionBackupsDir -Filter *.bak
foreach($backup in $backupFiles)
{
 $siteCollectionUrl = $siteCollectionPrefix + $backup.Name.Replace(".bak","");
 $siteCollectionBackupFile = $siteCollectionBackupsDir+$backup.Name
 Restore-SPSite -Identity $siteCollectionUrl -Path $siteCollectionBackupFile -force -confirm:$false -DatabaseName $contentDabaseName
 Set-SPSite –identity $siteCollectionUrl -Owner $primarySiteOwner –SecondaryOwner $secondarySiteOwner
}
&lt;/pre&gt;&lt;br /&gt;
Zanim napisałem ten skrypt wydawało mi się, że kilka kliknięć to chwila, a skrypt będę pisał godzinę. Nie doceniłem rozszerzeń PowerShell'a dla SharePoint'a.&lt;br /&gt;
&lt;br /&gt;
Co mógłbym automatyzować, a czego czasami nie robię:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;div&gt;Operacje w Visual Studio (np. &lt;a href="http://www.andrewconnell.com/blog/archive/2007/01/25/5855.aspx"&gt;process attach&lt;/a&gt;)&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;Operacje kopiowania na plikach&lt;/li&gt;
&lt;li&gt;Skrypty SQL i zmienne w skryptach&lt;/li&gt;
&lt;li&gt;Operacje konfiguracji SharePoint'a&lt;/li&gt;
&lt;li&gt;itd&lt;/li&gt;
&lt;/ul&gt;Hope this helps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-1919586448797394762?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/6Z1_7JsDWOY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/1919586448797394762/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/11/automatyzacja-sharepointa-to-konieczna.html#comment-form" title="Komentarze (0)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/1919586448797394762?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/1919586448797394762?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/6Z1_7JsDWOY/automatyzacja-sharepointa-to-konieczna.html" title="Automatyzacja SharePoint'a to konieczna konieczność" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><thr:total>0</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/11/automatyzacja-sharepointa-to-konieczna.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C04EQXo_eSp7ImA9WhRSGUk.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-5561347922173948035</id><published>2011-11-22T07:05:00.013+01:00</published><updated>2011-11-22T07:05:00.441+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-22T07:05:00.441+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SQL Tipsy" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL Server" /><title>SQL Tipsy (2) – skróty klawiaturowe w SSMS</title><content type="html">&lt;h4&gt;Produktywność i wygoda&lt;/h4&gt;&lt;br /&gt;
W drugim odcinku SQL Tipsów opisuję jak można wykorzystać skróty klawiaturowe w SQL Server Management Studio. Zapraszam do oglądania Tipsa, tylko 163 sekundy, zwrot produktywności prawie gwarantowany.&lt;br /&gt;
&lt;br /&gt;
&lt;iframe allowfullscreen="" frameborder="0" height="430" src="http://www.youtube.com/embed/71ih8JXEF3w" width="620"&gt;&lt;/iframe&gt;&lt;br /&gt;
&lt;br /&gt;
Hope this helps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-5561347922173948035?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/sKF0QxCnnKo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/5561347922173948035/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/11/sql-tipsy-2-skroty-klawiaturowe-w-ssms.html#comment-form" title="Komentarze (0)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/5561347922173948035?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/5561347922173948035?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/sKF0QxCnnKo/sql-tipsy-2-skroty-klawiaturowe-w-ssms.html" title="SQL Tipsy (2) – skróty klawiaturowe w SSMS" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://img.youtube.com/vi/71ih8JXEF3w/default.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/11/sql-tipsy-2-skroty-klawiaturowe-w-ssms.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkUMSX4zeCp7ImA9WhRSE0w.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-4085612126557301891</id><published>2011-11-14T23:38:00.000+01:00</published><updated>2011-11-14T23:38:08.080+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-14T23:38:08.080+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SQL Tipsy" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL Server" /><title>SQL Tipsy (1) – zapytanie na wielu serwerach</title><content type="html">&lt;h4&gt;Debiut&lt;/h4&gt;&lt;br /&gt;
Chciałbym zapoczątkować nową świecką tradycję na moim blogu. Pokusiłem się o nagranie 2 minutowego filmiku na którym prezentuje jak uruchamiać jedno zapytanie na wielu serwerach przy użyciu SQL Server Management Studio. Moim celem jest dzielenie się prostymi, aczkolwiek mam nadzieję, że przydatnymi "pomysłami" w jak najkrótszym czasie antenowym. Poniżej przykładowy filmik. Zachęcam do poświęcenia 140 sekund na obejżenie tego filmiku i oczywiście proszę o komentarze na temat takiej formy knowledge sharing.  &lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;iframe allowfullscreen="" frameborder="0" height="409" src="http://www.youtube.com/embed/gm4CVsdQ15M?hl=en&amp;amp;fs=1" width="515"&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;br /&gt;
Hope this helps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-4085612126557301891?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/K9hfQGXDUNY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/4085612126557301891/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/11/sql-tipsy-1-zapytanie-na-wielu.html#comment-form" title="Komentarze (9)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/4085612126557301891?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/4085612126557301891?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/K9hfQGXDUNY/sql-tipsy-1-zapytanie-na-wielu.html" title="SQL Tipsy (1) – zapytanie na wielu serwerach" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://img.youtube.com/vi/gm4CVsdQ15M/default.jpg" height="72" width="72" /><thr:total>9</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/11/sql-tipsy-1-zapytanie-na-wielu.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0QBSH4yfyp7ImA9WhRSEEk.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-5571528682323841373</id><published>2011-11-11T22:01:00.001+01:00</published><updated>2011-11-11T22:02:39.097+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-11T22:02:39.097+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SQL Server" /><title>SQL Server Profiler dla początkujących</title><content type="html">&lt;h4&gt;Czy mój system jest zdrowy?&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-IJeKHpaP8VI/TrucsuUG51I/AAAAAAAAJ3Y/OV_iev04fBY/s1600/Health.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-IJeKHpaP8VI/TrucsuUG51I/AAAAAAAAJ3Y/OV_iev04fBY/s1600/Health.png" /&gt;&lt;/a&gt;&lt;/div&gt;Pewnego słonecznego poranka, z braku laku uruchomiłem SQL Server Profiler'a. Bez konkretnego celu, postanowiłem przejrzeć rodzaj i ilości zapytań SQL które są generowane podczas jednego wejścia na stronę pewnego systemu z którym pracowałem. Ku mojemu zakoczeniu, web'owa aplikacja okazała się bardzo skuteczna w produkcji dużej liczby zapytań do pobrania małej ilości danych. Trace w Profiler'rze wyglądał jak świąteczna lista życzeń.&lt;br /&gt;
&lt;br /&gt;
We wspomnianym przypadku, jednym z powodów nadmiernej ilości  zapytań, było nieumiejętne wykorzystanie mechanizmów NHibernate'a (w tym  wypadku lazy load). Nie chodziło nawet o niezrozumienie działania użytych narzędzi, raczej była to kwestia braku monitorowania tworzonych dynamicznie zapytań.&amp;nbsp; Oczywiste jest, że pisząc systemu oparte o silnik baz danych istotne jest aby unikanąć pisania "małpiego" kodu odczytywania i zapisywania danych. Z wielkim entuzjazmem środowisko .NET przyjeło &lt;a href="http://nhforge.org/"&gt;jedynego słusznego OR Mapper'a&lt;/a&gt;, który odciążył "statystycznego" programistę formatek od klepania kolejnych linijek kodu w ADO.NET. Niestety, jak z każdą technologią nie można popadać w zbyt wielki optymizm i czasami należy zachować zimną krew. Wszystkim używającym NHib'a polecam przejrzenie &lt;a href="http://nhprof.com/Learn/Alerts"&gt;listy problemów&lt;/a&gt; jakie mogą pojawić się przy pracy z jedynym słusznym OR Mapperem. Koniec dygresji na temat NHib'a, przechodzę do sedna sprawy, czyli monitorowania i analizy zapytań generowanych przez zewnętrzną aplikację. &lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;h4&gt;Szukając winnego&lt;/h4&gt;&lt;br /&gt;
Dla niewtajemniczonych, &lt;a href="http://msdn.microsoft.com/en-us/library/ms187929.aspx"&gt;SQL Server Profiler&lt;/a&gt; jest to oprogramowanie, które pozwala przeglądać/filtrować/zapisywać zdarzenia lub zapytania wykonywane na danej instancji MS SQL Server'a. Jest to bardzo przydatne narzędzie, jeżeli pracujemy z SQL Server'em warto spędzić trochę czasu by poznać je lepiej.&lt;br /&gt;
&lt;br /&gt;
W tym poście skupię się na funkcjonalności SQL Server Profiler'a związanej z filtrowaniem zapytań dla danej bazy, zapisywaniem tzw. trace'u w tabeli bazy danych i "analizie" ilościowej zapytań. Analiza powinna pomóc w znalezieniu potencialnych problemów ukrytych za setkami/tysiącami rekordów trace'u. &amp;nbsp; &lt;br /&gt;
&lt;br /&gt;
Przykładowy kod aplikacji konsolowej będzie odpytywał bazę danych AdwentureWorks dla MS SQL Server 2008 R2. W szczególności tabele Sales.Customer, Sales.SalesOrderHeader i Sales.SalesOrderDetail. Poniżej kod naszej testowej aplikacji. Na uwagę zasługuję atrybut Application Name w connection stringu. Implementacja jest dosyć kulawa, pobiera najpierw wszystkich klientów, później oddzielnie dla każdego klienta pobiera listę zamówień i listę pozycji dla danego zamówienia. Taki kod obrazuje jak można wygenerować dziesiątki zapytań, które równie dobrze można obsłużyć jednym lub dwoma zapytaniami. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;using System;
using System.Data.SqlClient;

class Program
{
    static void Main(string[] args)
    {
        using (var conn = new SqlConnection(@"Server=HOME\SQL2008R2;
                    Database=AdventureWorks2008R2;
                    Application Name=MyTestConsoleApp;
                    MultipleActiveResultSets=true;
                    Trusted_Connection=true"))
        {
            conn.Open();
            using(var cmd = new SqlCommand(
                    @"SELECT TOP 10 cust.CustomerID, p.LastName 
                        FROM Sales.Customer cust
                        JOIN Person.Person p
                        ON cust.PersonID = p.BusinessEntityID
                    WHERE p.LastName LIKE 'A%'", conn))
            using (var customerReader = cmd.ExecuteReader())
            {
                while (customerReader.Read())
                {
                    Console.WriteLine(customerReader["LastName"]);

                    GetSalesOrders(conn, customerReader);
                }
            }
        }

    }

    private static void GetSalesOrders(SqlConnection conn, SqlDataReader customerReader)
    {
        using (var cmdSalesOrder = new SqlCommand(
            @"SELECT sod.SalesOrderID, sod.OrderDate, sod.CustomerID
                FROM Sales.SalesOrderHeader sod
                WHERE sod.CustomerID = @customerId", conn))
        {
            cmdSalesOrder.Parameters.AddWithValue("@customerId", customerReader["CustomerID"]);
            using (var sodReader = cmdSalesOrder.ExecuteReader())
            {
                while (sodReader.Read())
                {
                    Console.WriteLine(sodReader["OrderDate"]);
                    GetSalesOrderDetails(conn, sodReader);
                }
            }
        }
    }

    private static void GetSalesOrderDetails(SqlConnection conn, SqlDataReader sodReader)
    {
        using (var cmdSalesOD = new SqlCommand(
            @"SELECT SalesOrderID
                ,SalesOrderDetailID
                ,OrderQty
                ,ProductID
                ,LineTotal
            FROM Sales.SalesOrderDetail
            WHERE SalesOrderID = @SalesOrderID", conn))
        {
            cmdSalesOD.Parameters.AddWithValue("@SalesOrderID", sodReader["SalesOrderID"]);
            using (var deatailReader = cmdSalesOD.ExecuteReader())
            {
                while (deatailReader.Read())
                {
                    Console.WriteLine(deatailReader["ProductID"] + " " + deatailReader["LineTotal"]);


                }
            }
        }
    }
}


&lt;/pre&gt;&lt;br /&gt;
&lt;h4&gt;Filtry i kolumny&lt;/h4&gt;&lt;br /&gt;
Uruchamiamy SQL Server Profiler i podłączamy się do instancji. Możemy podłączyć się zdalnie do instancji serwera jeżeli nasze konto ma uprawnienia ALTER TRACE. SQL Server Profiler możemy odpalić z SSMS lub przez Menu Start (aka Perłę od Windows Vista).&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-UR00-2W0zgU/TruShI2FV5I/AAAAAAAAJ2w/cwkz71ngCKI/s1600/SQLProfilerMenu.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-UR00-2W0zgU/TruShI2FV5I/AAAAAAAAJ2w/cwkz71ngCKI/s1600/SQLProfilerMenu.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-jzDLq14cNeM/TruS8vNSLsI/AAAAAAAAJ24/zSJy0_1KRMU/s1600/SQLProfilerMenuSSMS.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-jzDLq14cNeM/TruS8vNSLsI/AAAAAAAAJ24/zSJy0_1KRMU/s1600/SQLProfilerMenuSSMS.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Jednym z bardziej istotnym aspektem korzystania z SQL Profiler jest ogracznienie zbioru danych jakie będziemy otrzymywać.Do przechowywania trace'u zakładamy bazę danych TraceStore na lokalnym serwerze. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;CREATE DATABASE TraceStore;
&lt;/pre&gt;&lt;br /&gt;
Podłączam się do instancji MS SQL Server'a.Na zakładce General dla nowego trace'u konfiguruję opcje 'Save to table', wybieram rodzaj template'u i wpisuję nazwę sesji.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-Rd6P7cs5znE/TruVnL9t4ZI/AAAAAAAAJ3A/7cI4zHGSgI0/s1600/SqlProfilerGeneralTab.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="403" src="http://2.bp.blogspot.com/-Rd6P7cs5znE/TruVnL9t4ZI/AAAAAAAAJ3A/7cI4zHGSgI0/s640/SqlProfilerGeneralTab.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
Przełączam się na zakłądkę 'Events Selection'. W pierwszym kroku wybieram kolumny, które zapełnią się danymi ze zdarzeń: &lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;div&gt;TextData - zapytanie&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;ApplicationName - nazwa aplikacji, można ja podać w connection string'u aplikacji (atrybut &lt;a href="http://johnnycoder.com/blog/2006/10/24/take-advantage-of-application-name/"&gt;Application Name&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;CPU - czas CPU w milisekundach wykorzystany przez dane zdarzenie&lt;/li&gt;
&lt;li&gt;Reads - liczba odczytów stron danych przez zdarzenie&lt;/li&gt;
&lt;li&gt;Writes - &amp;nbsp; liczba zapisów stron danych przez zdarzenie&lt;/li&gt;
&lt;li&gt;Duration - czas w microsekundach potrzebny na wykonanie zdarzenia&lt;/li&gt;
&lt;li&gt;SPID - Server Process Identifier&lt;/li&gt;
&lt;li&gt;EndTime - czas zakończenia wykonywania zdarzenia&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Wybieram następujące zdarzenia:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt; RPC:Completed - procedura wywołana zdalnie zakończyła swoje działanie &lt;/li&gt;
&lt;li&gt;SQL:BatchCompleted -batch zakończył swoje działanie&lt;/li&gt;
&lt;/ul&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-_Gw1FF3d9FQ/Trw4Hl0n6fI/AAAAAAAAJ3o/3tNeBViOCBw/s1600/SqlProfilerEventsSelection.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="408" src="http://1.bp.blogspot.com/-_Gw1FF3d9FQ/Trw4Hl0n6fI/AAAAAAAAJ3o/3tNeBViOCBw/s640/SqlProfilerEventsSelection.PNG" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;a href="http://4.bp.blogspot.com/-f3kpbSJ7trE/TruZTn43W6I/AAAAAAAAJ3Q/5TNVwdx1AFI/s1600/SqlProfilerColumnAndEvents.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;br /&gt;
&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Na zakładce 'Events Selection' wybieram przycisk 'Column Filters'. Ustawiam filtr na nazwę aplikacji (Application Name), ponieważ tylko zdarzenia z mojej konsolowej aplikacji są w kręgu moich zainteresowań.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-vPMGn7V8a64/Trw35mTTAuI/AAAAAAAAJ3g/z1o5Er4zXRA/s1600/SqlProfilerEditFilter.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-vPMGn7V8a64/Trw35mTTAuI/AAAAAAAAJ3g/z1o5Er4zXRA/s1600/SqlProfilerEditFilter.PNG" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-c9rwlCPPwx0/TruXQaSqX2I/AAAAAAAAJ3I/w8OLF4oceXg/s1600/SQLProfilerFilterByDatabaseName.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;br /&gt;
&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Filtrujemy po Application Name, jeżeli korzystamy z tej opcji w connection string'u. Może to być przydatne, jeżeli inne aplikacje korzystają z tej samej bazy danych, lub baza jest np. subskrybentem replikacji. Zapobiega to zaśmiecaniu trace'a zapytaniami, które mnie w tym momencie nie interesują.&lt;br /&gt;
&lt;br /&gt;
Wybieramy 'Run' i zaczynamy śledzenie trace'a, który jest zapisywany również w tabeli TraceStore.dbo.AdwentureWorks_2011Nov10. Uruchamiamy testową aplikację DemoProfiler (kod podany powyżej) i czekamy na pojawienie się wpisów w tabeli. W oknie SQL Profiler'a wygląda to następująco. &lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-mEuf1Q04mX8/Tr2IemxzDzI/AAAAAAAAJ3w/CEPYwbP-hsE/s1600/SqlProfilerResults.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="257" src="http://1.bp.blogspot.com/-mEuf1Q04mX8/Tr2IemxzDzI/AAAAAAAAJ3w/CEPYwbP-hsE/s640/SqlProfilerResults.PNG" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Sprawdzam teraz jak przedstawiają się wyniki dla najbardziej popularnego zapytania w zgromadzonym trace'ie. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;SELECT  SPID, SUBSTRING(CAST(TextData AS NVarchar(max)), 1, 150),COUNT(*) QueryCount
FROM TraceStore.dbo.AdwentureWorks_2011Nov10
 GROUP BY  SPID, SUBSTRING(CAST(TextData AS NVarchar(max)), 1, 150)
 ORDER BY SPID, QueryCount DESC
&lt;/pre&gt;&lt;br /&gt;
Powyższe zapytanie daje następujące rezultaty.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-_vYY2ORPAjg/Tr2InZv0BcI/AAAAAAAAJ34/_XAZr1XLpCA/s1600/SqlProfilerSqlResults.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="118" src="http://1.bp.blogspot.com/-_vYY2ORPAjg/Tr2InZv0BcI/AAAAAAAAJ34/_XAZr1XLpCA/s640/SqlProfilerSqlResults.PNG" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Widać, że zapytanie o listę pozycji w zamówieniu było najczęściej wykonywane. Nie zawsze musi oznaczać to problemy, ale warto sprawdzić kilka zapytań z górnej części listy. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;exec sp_executesql N'SELECT SalesOrderID
                  ,SalesOrderDetailID
                  ,OrderQty
                  ,ProductID
                  ,LineTotal
                FROM Sales.SalesOrderDetail
                WHERE SalesOrderID = @SalesOrderID',N'@SalesOrderID int',@SalesOrderID
&lt;/pre&gt;&lt;br /&gt;
&lt;h4&gt;Jak to można stosować?&lt;/h4&gt;&lt;br /&gt;
Zachęcam wszystkich do uruchomienia SQL Profiler'a na swoim systemie (oczywiście nie na produkcji, bo to obciąży znacząco i niepotrzebnie SQL Server'a). Może się okazać, że to co zobaczymy przerośnie nasze najśmielsze oczekiwania. W moim zespole używamy namiętnie NHibernate'a, kiedyś zaproponowałem, żeby używać SQL Profiler'a jako część code review, ale zostałem wyśmiany :). Zapewne byłaby to strata czasu, ale osobiście zawsze darzyłem sympatią SQL Profiler'a. Tylko ten program prawdę ci powie, nie jakaś tam cyganka.  &lt;br /&gt;
&lt;br /&gt;
Hope this helps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-5571528682323841373?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/7woFF96XQMs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/5571528682323841373/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/11/sql-server-profiler-dla-poczatkujacych.html#comment-form" title="Komentarze (1)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/5571528682323841373?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/5571528682323841373?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/7woFF96XQMs/sql-server-profiler-dla-poczatkujacych.html" title="SQL Server Profiler dla początkujących" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-IJeKHpaP8VI/TrucsuUG51I/AAAAAAAAJ3Y/OV_iev04fBY/s72-c/Health.png" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/11/sql-server-profiler-dla-poczatkujacych.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkcARnc5fip7ImA9WhRTEko.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-2802851307089895058</id><published>2011-11-02T23:47:00.000+01:00</published><updated>2011-11-02T23:47:27.926+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-02T23:47:27.926+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Replikacja" /><title>Czy warto być przygotowanym na najgorsze?</title><content type="html">&lt;h4&gt;Dlaczego?&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-OVLtk5_lRXU/TrG_LQBf0dI/AAAAAAAAJjk/Dck3Yvoxcdo/s1600/Disaster.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-OVLtk5_lRXU/TrG_LQBf0dI/AAAAAAAAJjk/Dck3Yvoxcdo/s1600/Disaster.png" /&gt;&lt;/a&gt;&lt;/div&gt;Dzisiejsze systemy to nie tylko kod źródłowy, to przede wszystkim  niezawodność i dostępność. Programiści/projektanci odpowiadają w pewnej  mierze za te atrybuty systemów, które implementują. Muszę przyznać, że zagadnienie planu ciągłości działania (dalej DR,&amp;nbsp; ang. &lt;a href="http://en.wikipedia.org/wiki/Disaster_recovery"&gt;disaster recovery plan&lt;/a&gt;) było dla mnie czymś zupełnie nowym i abstrakcyjnym. Jakiś czas temu miałem okazję pracować z moim zespołem nad przygotowaniem planu DR. Chciałbym wytłumaczyć się co nas skłoniło do przygotowania i przetestowania takiej procedury i jakie korzyści odnieśliśmy z takiej inwestycji.&lt;br /&gt;
&lt;br /&gt;
Do rozwiązania problemu biznesowego zaproponowaliśmy architekturę, która ze swej natury (używała replikacji MS SQL Server'a) była bardzo podatna na błąd użytkownika. Użytkownikiem mógłbyć niewyspany administrator, nadgorliwy programista czy zmęczony inżynier wsparcia. Najbardziej obawialiśmy się destabilizacji całego produkcyjnego systemu, którą można było osiągnąć np. poprzez usunięcie jednego wiersza w jednej z 10 baz danych. Jeżeli rozwiązanie oparte o mechanizm replikacji okazałoby się zbyt mało bullet-proof, to mogliśmy w pewnym sensie wydać na siebie wyrok skazujący.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Przygotowanie Disaster Recovery&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-7mSjgdhWsko/TrG_mWK35fI/AAAAAAAAJjs/mt7jWhSd3rQ/s1600/DisasterPlan.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-7mSjgdhWsko/TrG_mWK35fI/AAAAAAAAJjs/mt7jWhSd3rQ/s1600/DisasterPlan.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Wydawałoby się, że administrator, ludzie od infrastruktury będą w całości odpowiedzialni za przygotowanie planu "gaszenia pożarów". Taki scenariusz nie zawsze ma racje bytu, spora część wiedzy o meandrach systemu jest w posiadaniu programistów. W naszym przypadku najwięcej pracy wykonaliśmy wspólnie z zespołem administratorów baz danych.&lt;br /&gt;
&lt;br /&gt;
Na wstępie wyodrębniliśmy dwa scenariusze "katastrof", które w sumie pokryły większość kombinacji nieszczęśliwych wypadków. Wypisaliśmy też przykładowe kroki, które miały służyć do przywrócenia systemu do działania. Po zakończeniu testowania, z oryginalnej listy kroków, został tylko jej ogólny zarys. W zderzeniu z rzeczywistością teoretyzowanie nie miało szans. Dlatego plan DR bez testowania jest jak babcia bez wąsów. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Testowanie planu DR&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-_wS-zteQm6g/TrHAqZHx_nI/AAAAAAAAJj0/bOnwNCj7fQ4/s1600/Clock.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-_wS-zteQm6g/TrHAqZHx_nI/AAAAAAAAJj0/bOnwNCj7fQ4/s1600/Clock.png" /&gt;&lt;/a&gt;&lt;/div&gt;W czasie testowania planu DR okazało się, że system wcale nie jest taki odporny na wszelkie możliwe kombinacje backup'ów, modyfikacji itd. Na bieżąco naprawialiśmy pojawiające się problemy i notowaliśmy co ważniejsze zmiany które należało zaimplementować.&lt;br /&gt;
&lt;br /&gt;
Istotnym elementem był całkowity czas przywrócenia systemu. Mierzyliśmy czas wykonania dla każdego kroku.&amp;nbsp; Skrupulatnie zapisywaliśmy czasy kopiowania backup'ów, wykonywania czynności administracyjnych itd.&lt;br /&gt;
&lt;br /&gt;
Wymiana doświadczeń z administratorem bazy danych okazała się bardzo cennym doświadczeniem. Wzajemnie wymyślaliśmy scenariusze, które trzeba obsłużyć. Obecność osoby spoza zespołu zapobiegała "zamiataniu pod dywan" pojawiających się problemów i wymuszała szybkie ich rozwiązywanie.&lt;br /&gt;
&lt;br /&gt;
Po przygotowaniu i przetestowaniu&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;posiadaliśmy solidne przekonanie, że nowa wersja systemu działa&lt;/li&gt;
&lt;li&gt;mogliśmy spać spokojnie, gdyż wiedza o przywróceniu systemu do działania została spisana i przetestowana&lt;/li&gt;
&lt;li&gt;naprawiliśmy kilka błędów&lt;/li&gt;
&lt;li&gt;zastosowaliśmy kilka cennych modyfikacji, które przyczyniły się do większej stabilności systemu&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Czy warto?&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-JfnF3bWcCm0/TrHCOnoJQeI/AAAAAAAAJj8/x-0y3EyzLAk/s1600/Cherry.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-JfnF3bWcCm0/TrHCOnoJQeI/AAAAAAAAJj8/x-0y3EyzLAk/s1600/Cherry.png" /&gt;&lt;/a&gt;&lt;/div&gt;Decyzję czy warto przygotować przygotować plan ciągłości działania dla systemu, który rozwijacie lub utrzymujecie pozostawiam Wam drogim czytelnikom. W przypadku mojego zespołu miało to znaczący wpływ na komfort psychiczny przy wdrażaniu nowej wersji systemu opartego na MS SQL Server replikacji. &lt;br /&gt;
&lt;br /&gt;
Któregoś dnia wróciłem z urlopu, podszedł do mnie szef i mówi, &lt;i&gt;że w czasie weekend'u padł dysk na serwerze i coś tam z systemem IO poszło nie tak&lt;/i&gt;. System zastygł, aczkolwiek ze strony szefa pełen spokój. Wyciągnął z szuflady plan DR, spojrzał, który scenariusz pasuje do zainstniałej sytuacji i rozesłał dokumenty do ludzi z insrastruktury. Wszystko poszło jak po maśle :). Ta sytuacja, IHMO, była dla mnie namacalnym dowodem potwierdzającym sensowność spędzonego czasu nad planowanie ciągłości :).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-2802851307089895058?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/2Aoe6Zn-4h8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/2802851307089895058/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/11/czy-warto-byc-przygotowanym-na.html#comment-form" title="Komentarze (2)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/2802851307089895058?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/2802851307089895058?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/2Aoe6Zn-4h8/czy-warto-byc-przygotowanym-na.html" title="Czy warto być przygotowanym na najgorsze?" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-OVLtk5_lRXU/TrG_LQBf0dI/AAAAAAAAJjk/Dck3Yvoxcdo/s72-c/Disaster.png" height="72" width="72" /><thr:total>2</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/11/czy-warto-byc-przygotowanym-na.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEYERHs9cSp7ImA9WhdaE0w.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-6568110630684631349</id><published>2011-10-22T21:34:00.001+02:00</published><updated>2011-10-22T21:35:05.569+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-22T21:35:05.569+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SQL Server" /><title>Wymienię IF/EXISTS/SELECT/UPDATE/INSERT na MERGE</title><content type="html">&lt;h4&gt;Ignorancja to słabość&lt;/h4&gt;&lt;br /&gt;
Ostatnio przeglądałem T-SQL kod w którym została użyta pewna siermiężna acz użyteczna konstrukcja (użyteczna w poprzednich wersjach SQL Server'a, tak od 2005 w dół). Zapytałem się autora kodu, czy przypadkiem nie używają MS SQL Server 2008, otrzymałem pozytywną odpowiedź "nawet w wersji R2". Kod który prezentuje ogólny zarys problemu znajduje się poniżej. Przykłądowa implementacja wstawia rekord do tabeli jeśli jeszcze on nie istnieje, w innym wypadku aktualizuje wartości wybranych kolumn przy pomocy UPDATE'u.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;IF EXISTS(SELECT * FROM Table1 WHERE Id = @Id)
BEGIN
  INSERT INTO Table1(Id, Name) VALUES(@Id, @Name)
END
ELSE
BEGIN
  UPDATE Table1
     SET Name = @Name
   WHERE Id = @Id
END
&lt;/pre&gt;&lt;br /&gt;
Niestety i na szczęście każde triki, schematy ulegają w końcu przedawnieniu. I tak się stało z powyższym IF/EXISTS/SELECT/UPDATE/INSERT.&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;h4&gt;MERGE na ratunek&lt;/h4&gt;&lt;br /&gt;
W MS SQL Server 2008 pojawiło słowo kluczowe &lt;a href="http://technet.microsoft.com/en-us/library/bb510625.aspx"&gt;MERGE&lt;/a&gt;. Nigdy nie miałem okazji używać MERGE produkcyjnie. Aczkolwiek warto śledzić jak się rozwija Transact-SQL, ponieważ nie znasz dnia ani godziny kiedy taka zdawkowa wiedza może zostać wykorzystana (np.warto wiedzieć, że w wersji MS SQL Server 2012 wprowadzono wsparcie dla &lt;a href="http://msdn.microsoft.com/en-us/library/ms188385%28SQL.110%29.aspx#Offset"&gt;"paginacji"&lt;/a&gt; ). &lt;br /&gt;
&lt;br /&gt;
Wracając do tematu. Powyższe zapytanie (EXISTS .... UPDATE) można w prosty sposób przepisać przy użyciu MERGE.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;IF OBJECT_ID('dbo.Table1','U') IS NOT NULL DROP TABLE dbo.Table1

CREATE TABLE dbo.Table1(
  Id Int NOT NULL PRIMARY KEY,
  MyName Varchar(10) NOT NULL
)

IF OBJECT_ID('dbo.SmartUpsert','P') IS NOT NULL DROP PROC dbo.SmartUpsert
GO
CREATE PROCEDURE dbo.SmartUpsert(
  @Id Int
, @MyName VARCHAR(10)
)
AS
BEGIN
    MERGE dbo.Table1 AS target
    USING (SELECT @Id, @MyName) AS source (Id, MyName)
    ON (target.Id = source.Id)
    WHEN MATCHED THEN 
        UPDATE SET MyName = source.MyName
 WHEN NOT MATCHED THEN 
     INSERT (Id, MyName)
     VALUES (source.Id, source.MyName)
OUTPUT deleted.MyName OldMyName, inserted.MyName as NewMyName ;   
END
GO
DECLARE @Id1 Int = 1
, @MyName1 VARCHAR(10) = 'Max'
, @Id2 Int = 2
, @MyName2 VARCHAR(10) = 'Joanna'

&lt;/pre&gt;&lt;pre class="brush:sql"&gt;EXEC dbo.SmartUpsert @Id1, @MyName1
EXEC dbo.SmartUpsert @Id2, @MyName2
EXEC dbo.SmartUpsert @Id1, @MyName2

&lt;/pre&gt;Wyniki zgodne z oczekiwaniami, tabele deleted i inserted zachowują się jak w triggerach DML.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-HanS9s9_SaI/TqMZMzBy-PI/AAAAAAAAJjM/QLie18lL2is/s1600/MergeResults.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-HanS9s9_SaI/TqMZMzBy-PI/AAAAAAAAJjM/QLie18lL2is/s1600/MergeResults.PNG" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Dla MERGE otrzymujemy następujący plan wykonania.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-CKJSUKcFrqc/TqMYv4DznnI/AAAAAAAAJjE/AIrwGO_HZYk/s1600/MergeExecutionPlan.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="90" src="http://2.bp.blogspot.com/-CKJSUKcFrqc/TqMYv4DznnI/AAAAAAAAJjE/AIrwGO_HZYk/s640/MergeExecutionPlan.PNG" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;a href="http://pl.wikipedia.org/wiki/Heraklit"&gt;Panta rei&lt;/a&gt;, trzeba ciągle poszerzać swoje horyzonty. &lt;br /&gt;
&lt;br /&gt;
Hope this helps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-6568110630684631349?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/aJ8ahsu2o3c" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/6568110630684631349/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/10/wymienie-ifexistsselectupdateinsert-na.html#comment-form" title="Komentarze (2)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/6568110630684631349?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/6568110630684631349?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/aJ8ahsu2o3c/wymienie-ifexistsselectupdateinsert-na.html" title="Wymienię IF/EXISTS/SELECT/UPDATE/INSERT na MERGE" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-HanS9s9_SaI/TqMZMzBy-PI/AAAAAAAAJjM/QLie18lL2is/s72-c/MergeResults.PNG" height="72" width="72" /><thr:total>2</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/10/wymienie-ifexistsselectupdateinsert-na.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkYNR384eyp7ImA9WhdaEUk.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-5644005638419892209</id><published>2011-10-20T22:56:00.000+02:00</published><updated>2011-10-20T22:56:36.133+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-20T22:56:36.133+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SQL Server" /><title>Linked Server w SQL Server dla początkujących</title><content type="html">&lt;h4&gt;Linked Server jako dobry wujek&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-kIWXwr09Bpw/Tp9BqIJx0-I/AAAAAAAAJiM/2DPeTFsODBY/s1600/Wujek.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-kIWXwr09Bpw/Tp9BqIJx0-I/AAAAAAAAJiM/2DPeTFsODBY/s1600/Wujek.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;Linked Server w SQL Serwerze oferuje prosty mechanizm udostępnienia danych ze zdalnego serwera (MS SQL Server, Oracle, DB2, itd). Z mojego doświadczenia wyniki, że jest to jeden z bardziej nadużywanych komponentów MS SQL Server'a. Korzystanie z Linked Server'ów jest na tyle proste, że większość systemów opartych na silniku baz danych MSSQL używa ich do przesyłania danych pomiędzy systemami. &lt;br /&gt;
&lt;br /&gt;
Łatwość używania Linked Server'ów spowodowała, że stały się one jakby "standardem" wykorzystywanym do "integracji" systemów. W przypadku jednorazowego przesłania pewnej ilości danych użycie Linked Server'a jest jak najbardziej zrozumiałe. Jednak tworzenie w ten sposób rozwiązania integrującego np. dwa kluczowe systemy w firmie jest dla mnie pewnym nieporozumieniem.&lt;br /&gt;
&lt;br /&gt;
Często połączenie dwóch systemów zaczyna się jako niewinne udostępnienie jednego widoku, tak na próbę. Po jakims czasie zaczynamy zaciągać kolejne tabele, nawet wykonywać złożone zapytania z JOIN'ami pomiędzy widokami/tableami zdalnymi i lokalnymi. Apogeum takiej sytuacji jest udostępnianie procedur składowanych wywoływanych przez zewnętrzne systemy. &lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Primo, żeby działało czyli architektura gwoździa i młotka&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-Zjewh__HoP8/Tp9DwDcqwrI/AAAAAAAAJiU/AEJ9FpkExkk/s1600/Hammer.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-Zjewh__HoP8/Tp9DwDcqwrI/AAAAAAAAJiU/AEJ9FpkExkk/s1600/Hammer.png" /&gt;&lt;/a&gt;&lt;/div&gt;Użycie Linked Server jest często drogą na skróty, zdarza się, że integracja z innymi systemami to ostatni etap projektu. Wtedy nie ma czasu na implementację zestawu pakietów &lt;a href="http://msdn.microsoft.com/en-us/library/ms141026.aspx"&gt;SQL Server Integration Services(SSIS)&lt;/a&gt;, czy zaprojektowanie usług do pobierania danych, już nie mówię o SOA (przede wszystkim dlatego, że się na tym nie znam:)).SSIS zapewnia duży worek kompnentów np. do kowersji typów z którą mogą pojawić się problemy jeżeli wyciągamy dane z np. Oracle'a. SSIS jest też bardziej elastyczny, pozwala skalować te rozwiązania, które z czasem mogą zrobić się bardzo skomplikowane.&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Secundo, dane są przesyłane po sieci&lt;/h4&gt;&lt;br /&gt;
Inny problemem jest to, że z jakiegoś magicznego powodu, programiści MSSQL'a zapominają o jednej ważnej kwestii. Podczas wykonywania zapytań na Linked Server dane muszą zostać przesłane przez sieć zanim dotrą do docelowej instancji MSSQL'a. Ostatnio widziałem rozwiązanie oparte na Linked Server'ach, które wywoływały zdalne zapytania z poziomu aplikacji klienta. Okazało się, że aplikacja pomimo, że była używana na razie tylko przez kilku użytkowników już przeżywała problemy wydajnościowe. Ciężko, też było zrozumieć architektom systemu, że przesyłanie &lt;b&gt;1 miliona&lt;/b&gt; wierszy pomiędzy serwerami dla każdego wywołania zapytania nie jest dobrym pomysłem. &lt;br /&gt;
&lt;br /&gt;
Mechanizm Linked Server'a umożliwia wywoływanie zdalnych procedur (jeżeli źródło danych to instancja MSSQL'a). Nie znaczy to, że koniecznie trzeba go używać. Niech przestrogą jak to może być nadużywane będzie następująca historia. W pewnym systemie z którym pracowałem ten mechanizm został "użyty" w następujący sposób. Poniżej jak wyglądała implementacja z użyciem Linked Server'a i wywołań procedur na zdalnych serwerach. &lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;Aplikacja intranetowa wywoływała procedurę &lt;b&gt;ProcA&lt;/b&gt; na serwerze ServerA&lt;/li&gt;
&lt;li&gt;Procedura &lt;b&gt;ProcA&lt;/b&gt; na ServerA wywoływała procedurę &lt;b&gt;ProcB&lt;/b&gt; na serwerze ServerB (via Linked Server)&lt;/li&gt;
&lt;li&gt;Procedura &lt;b&gt;ProcB&lt;/b&gt; na ServerB wywoływała procedurę &lt;b&gt;ProcC&lt;/b&gt; na serwerze ServerC&amp;nbsp; (via Linked Server) &lt;/li&gt;
&lt;li&gt;Procedure &lt;b&gt;ProcC&lt;/b&gt; na ServerzeC wywoływała (korzystając z Linked Servera wskazującego na lokalną instancje) wywoływała &lt;b&gt;ProcC2&lt;/b&gt; na ServerC&lt;/li&gt;
&lt;/ol&gt;Rozwiązania to w pewnym momencie działało (chyba było to nawet kilka lat), aczkolwiek ilość danych i użytkowników w systemach A, B, C systematycznie rosła, i zaczęły pojawiać się timeout'y, blokowanie itd.&amp;nbsp; Rozwizanie tego problemu, bez przepisania połowy systemu A i B, okazało się praktycznie niemożliwe, nawet hardware upgrade nie pomógł.&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Przykład użycia&lt;/h4&gt;&lt;br /&gt;
Chciałbym nie pozostać gołosłownym i zademonstrować dwa przykłady użycia Linked Server'a. Na dwóch serwerach HOME i  HOME\SQL2008R2 mam po jednej instancji bazy danych AdventureWorks.W pierszym przykładzie prezentuję zapytanie skonstruowane z dwóch tabel znajdujących się na zdalnym serwerze HOME.&lt;br /&gt;
&lt;br /&gt;
Tworzymy login &lt;i&gt;linkedaccount&lt;/i&gt; na serwerze HOME (zdalny) i użytkownika dla bazy AdventureWorks z uprawnieniami do odczytu danych. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;IF  EXISTS (SELECT * FROM sys.server_principals WHERE name = N'linkedaccount')
BEGIN
 DROP LOGIN [linkedaccount]
END
GO
CREATE LOGIN [linkedaccount] WITH PASSWORD=N'P@ssw0rd', DEFAULT_DATABASE=[AdventureWorks]
GO
USE [AdventureWorks]
GO
IF  EXISTS (SELECT * FROM sys.database_principals WHERE name = N'linkedaccount')
BEGIN
 DROP USER [linkedaccount]
END
GO
CREATE USER [linkedaccount] FOR LOGIN [linkedaccount] WITH DEFAULT_SCHEMA=[dbo]
GO
EXEC sp_addrolemember db_datareader, [linkedaccount] 
&lt;/pre&gt;&lt;br /&gt;
Na serwerze  HOME\SQL2008R2 tworzymy Linked Server o nazwie HOME i konfigurujemy login &lt;i&gt;linkedaccount&lt;/i&gt; jako konto z dostępem do danych zdalnego serwera HOME.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;IF  EXISTS (SELECT srv.name FROM sys.servers srv WHERE srv.server_id != 0 AND srv.name = N'.\WE2005')
BEGIN
 EXEC master.dbo.sp_dropserver @server=N'HOME', @droplogins='droplogins'
END
GO
EXEC master.dbo.sp_addlinkedserver @server = N'HOME', @srvproduct=N'SQL Server'
EXEC master.dbo.sp_addlinkedsrvlogin @rmtsrvname=N'HOME',@useself=N'False',@locallogin=NULL,@rmtuser=N'linkedaccount',@rmtpassword='P@ssw0rd'
&lt;/pre&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-V64JeQ5TnIk/TqB4bOBeT2I/AAAAAAAAJic/szUF0R4yM6g/s1600/LinkedServers1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-V64JeQ5TnIk/TqB4bOBeT2I/AAAAAAAAJic/szUF0R4yM6g/s1600/LinkedServers1.PNG" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Uruchamiamy pierwsze zapytanie na serwerze   HOME\SQL2008R2.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;use AdventureWorks;

SELECT empRemote.LoginID
     , empHistoryRemote.StartDate
     , empHistoryRemote.DepartmentID
  FROM HOME.AdventureWorks.HumanResources.Employee empRemote
  JOIN HOME.AdventureWorks.HumanResources.EmployeeDepartmentHistory empHistoryRemote
    ON empRemote.EmployeeID = empHistoryRemote.EmployeeID
 WHERE empHistoryRemote.StartDate &amp;lt; '1998-12-12'
&lt;/pre&gt;&lt;br /&gt;
Powyższe zapytanie do dwóch zdalnych tabel znajdujących się na serwerze HOME jest uruchamiane z poniższym planem wykonania. Widać na nim, że JOIN został wykonany na serwerze zdalnym i tylko 19 wierszy zostało zwróconych do serwera HOME\SQL2008R2. &lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/--mIwxu5q1KI/TqB7SkMeEfI/AAAAAAAAJik/CUTjBW1GSKM/s1600/RemoteQuery1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/--mIwxu5q1KI/TqB7SkMeEfI/AAAAAAAAJik/CUTjBW1GSKM/s1600/RemoteQuery1.PNG" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Drugi przykład prezentuje plan zapytania przy użyciu trzech  tabel "zdalnych" i jednej lokalnej znajdującej się na serwerze  HOME\SQL2008R2. Po dodaniu do zapytania pierszego jednej tabeli zdalnej HumanResources.EmployeePayHistory i jednej lokalnej HumanResources.Employee otrzymujemy całkiem inny plan zapytania. Zdalne zapytanie (&lt;b&gt;Remote Query&lt;/b&gt; na planie) jest wykonywane dwukrotnie, ponieważ SQL Server w ten sposób radzi sobie z optymalizacją (mniej lub bardziej udaną).&amp;nbsp;  &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;use AdventureWorks;

SELECT empRemote.LoginID
     , empHistoryRemote.StartDate
     , empHistoryRemote.DepartmentID
     , empPayRemote.Rate
     , empLocal.Gender
  FROM HOME.AdventureWorks.HumanResources.Employee empRemote
  JOIN HOME.AdventureWorks.HumanResources.EmployeePayHistory empPayRemote
    ON empRemote.EmployeeID = empPayRemote.EmployeeID 
  JOIN HumanResources.Employee empLocal
    ON empRemote.EmployeeID = empLocal.EmployeeID 
  JOIN HOME.AdventureWorks.HumanResources.EmployeeDepartmentHistory empHistoryRemote
    ON empLocal.EmployeeID = empHistoryRemote.EmployeeID
 WHERE empHistoryRemote.StartDate &amp;lt; '1998-12-12'
&lt;/pre&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/--mIwxu5q1KI/TqB7SkMeEfI/AAAAAAAAJik/CUTjBW1GSKM/s1600/RemoteQuery1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;br /&gt;
&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/--5BxJl32ukw/TqCDU-pClkI/AAAAAAAAJis/JLvuMe7y32c/s1600/RemoteQuery2.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="241" src="http://2.bp.blogspot.com/--5BxJl32ukw/TqCDU-pClkI/AAAAAAAAJis/JLvuMe7y32c/s640/RemoteQuery2.PNG" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Nie doszukiwałbym się jakiejś szczególnej logiki w powyższym zapytaniu. Chodziło mi tylko o pokazanie jak plan zapytania z użyciem Linked Server może ulec zmianie. W pewnym momencie może się okazać, że pobieramy ze zdalnego serwera całą dużą tabelę, żeby tylko ją lokalnie odfiltrować.&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Podsumowanie&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-NA0XhbkkrW0/TqCFqZGcK3I/AAAAAAAAJi8/ca6Od44jm5I/s1600/Scales.PNG" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-NA0XhbkkrW0/TqCFqZGcK3I/AAAAAAAAJi8/ca6Od44jm5I/s1600/Scales.PNG" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;a href="http://1.bp.blogspot.com/-6eCqn5uy8mQ/TqCFfqPG-MI/AAAAAAAAJi0/O1B3kisgqNk/s1600/Scales.PNG" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;br /&gt;
&lt;/a&gt;&lt;br /&gt;
Nie jestem w stanie udowodnić w tak krótkim poście, że rozwiązania na Linked Serverach są trudne do utrzymania i mogą doprowadzić do problemów wydajnościowych. Mogę tylko zachęcić do wnikliwej ewaluacji potencjalnych pomysłów, które używają mechanizmu Linked Server. Pewnie, jest on wygodny, prosty w  użyciu i w administracji, aczkolwiek jak ze wszystkim trzeba zachować pewną dozę rozsądku.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-5644005638419892209?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/ePUAj7Q_m_I" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/5644005638419892209/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/10/linked-server-w-sql-server-dla.html#comment-form" title="Komentarze (0)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/5644005638419892209?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/5644005638419892209?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/ePUAj7Q_m_I/linked-server-w-sql-server-dla.html" title="Linked Server w SQL Server dla początkujących" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-kIWXwr09Bpw/Tp9BqIJx0-I/AAAAAAAAJiM/2DPeTFsODBY/s72-c/Wujek.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/10/linked-server-w-sql-server-dla.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUYCQ3c-fSp7ImA9WhdbGEU.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-7474722812034832284</id><published>2011-10-17T23:32:00.000+02:00</published><updated>2011-10-17T23:32:42.955+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-17T23:32:42.955+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SharePoint" /><title>Obudziłem się z ręką w piaskownicy SharePoint'a</title><content type="html">&lt;h4&gt;Kontakty trzeciego stopnia z Sandbox Solutions&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-U1mLKvCG20c/TpyCXIhIIRI/AAAAAAAAJh8/XriQ8I7VSnI/s1600/Piaskownica.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-U1mLKvCG20c/TpyCXIhIIRI/AAAAAAAAJh8/XriQ8I7VSnI/s1600/Piaskownica.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;Microsoft w SharePoint 2010 zaproponował nowy sposób wdrażania i uruchamiania komponentów SharePoint'a, który nazywa się &lt;a href="http://msdn.microsoft.com/en-us/sharepoint/gg637002.aspx"&gt;Sandbox Solutions&lt;/a&gt; (z angielskiego Sandbox to piaskownica). Sandbox Solutions są to "normalne" pliki CAB z rozszerzeniem .wsp, tak jak stare dobre Farm Solutions. Zasadnicza różnica polega na tym, że Sandbox Solutions (SS) mogą być wdrażane na poziomie Site Collection i na poziomie farmy, a Farm Solutions mogą być wdrażane tylko na poziomie farmy SharePoint'a. Docelowo najbardziej pożądanym miejscem dla&amp;nbsp; SS jest Site Collection. &lt;br /&gt;
&lt;br /&gt;
SharePoint udostępnia oddzielne środowisko uruchomieniowe dla kodu z Sandbox Solutions w postaci oddzielnego procesu (Sandbox Worker Process). Całe środowisko jest tak skonstruowane, że udostępnia tylko część SharePoint API do którego ograniczono dostęp poprzez wprowadzenie Sandbox Proxy Worker Process.&amp;nbsp; Sandbox Woker Process komunikuje się z Proxy w momencie gdy chce wywołać jakąś metodę lub klasę z Microsoft.SharePoint.dll.&amp;nbsp; Ładny obrazek architektury SS można zobaczyć &lt;a href="http://i.msdn.microsoft.com/Ee539417.323a5d7f-64eb-4f8d-bddb-1968fb0a5f4d%28en-us,office.14%29.gif"&gt;tutaj&lt;/a&gt;. &lt;br /&gt;
&lt;br /&gt;
Taka architektura ma swoje następstwa. Ograniczono możliwość korzystania praktycznie ze wszystkich pozostały bibliotek SharePoint'a. Powód tego jest prostym, często wymagają one dostępu poza Site Collection np. do Web Application i jest to sprzeczne z architekturą Piaskownicy.&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;h4&gt;TypeLoadException&lt;/h4&gt;&lt;br /&gt;
Wyobraźmy sobie WebPart, który jest częścią Sandbox Solution. Nie może on skorzystać w  kodzie zarządzalnym z Shared Service Applications (np. Manage Metadata  Service odpada). Co więcej nie może pobrać nawet kontektu klasy &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spservicecontext.aspx"&gt;SPServiceContext&lt;/a&gt;, gdyż otrzyma wyjątek TypeLoadException. Trudno sobie to wyborazić, ale pozostaje korzystanie z podzbioru Microsoft.SharePoint.dll. Ograniczenia są ładnie opisane w poniższych artykułach, ale aż trudno w nie uwierzyć do momentu aktywacji solution i wyświetlenia np. webpart'a z błędem. Dla przykładu Microsoft.SharePoint.Taxonomy.dll nie jest oznaczony &lt;a href="http://msdn.microsoft.com/en-us/library/system.security.allowpartiallytrustedcallersattribute.aspx"&gt;AllowPartiallyTrustedCallers&lt;/a&gt; atrybutem i nie można go załadować do Piaskownicy.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://msdn.microsoft.com/en-us/library/ee537860.aspx"&gt;Microsoft.SharePoint.dll APIs That Are Available from Sandboxed Solutions&lt;/a&gt;&amp;nbsp; &lt;br /&gt;
&lt;a href="http://msdn.microsoft.com/library/gg615464.aspx%20"&gt;What Can Be Implemented in Sandboxed Solutions in SharePoint 2010&lt;/a&gt; &lt;br /&gt;
&lt;a href="http://msdn.microsoft.com/en-us/library/gg615451.aspx%20"&gt;Available and Unavailable .NET Assemblies from Sandboxed Solutions&lt;/a&gt;&amp;nbsp; &lt;br /&gt;
&lt;a href="http://msdn.microsoft.com/en-us/library/gg615456.aspx%20"&gt;Available and Unavailable SharePoint Assemblies from Sandboxed Solutions&lt;/a&gt;&lt;br /&gt;
&lt;a href="http://msdn.microsoft.com/library/gg615454.aspx%20"&gt;Restrictions on Sandboxed Solutions in SharePoint 2010&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Innym scenariuszem gdzie znacząco odczujemy ograniczenia Piaskownicy jest logowanie w sensie zapisu trace'u.&amp;nbsp; API Developer Dashboard'u, ULS (Unified Logging Service) są niedostępne, czyli w Piaskownicy zapomnij o tym co przeczytałeś o &lt;a href="http://msdn.microsoft.com/en-us/library/gg512103.aspx"&gt;logowaniu&lt;/a&gt;. Pozstaje &lt;a href="http://spsl.codeplex.com/"&gt;SharePoint Sandbox Logging&lt;/a&gt;, ale to rozwiązanie pozostawiam bez komentarza. &lt;br /&gt;
&lt;h4&gt;&amp;nbsp;&lt;/h4&gt;&lt;h4&gt;Obejścia, czyli drugie oblicze Piaskownicy&lt;/h4&gt;&lt;br /&gt;
Pierwszą możliwością objeścia ograniczeń SS jest użycie Full-trust proxy. Polaga ono na implementacji klasy, która udostępnia bardzo prymitywny interfejs. Bazuje on na metodzie przyjmującej nazwę operacji oraz argument i zwracającej obiekt. Rejestracja proxy wymaga dostępu administracyjnego do farmy SharePoint'a, czyli odpada np. w większości planów SharePoint Online. &lt;br /&gt;
&lt;br /&gt;
Innym sposobem jest korzystanie z Client Object Model, pod każdą możliwą postacią, czyli JavaScript, Silverlight, Managed code. W praktyce sprowadza się to do kodowanie w JavaScript'cie, użyciu jQuery i intensywnym reverse enginnering Web Service'ów SharePoint'a (polecam zapoznać się z projektem &lt;a href="http://spservices.codeplex.com/"&gt;jQuery Library for SharePoint Web Services&lt;/a&gt;). &lt;br /&gt;
&lt;br /&gt;
Microsoft tylnymi drzwiami wprowadził &lt;a href="http://visualstudiogallery.msdn.microsoft.com/8e602a8c-6714-4549-9e95-f3700344b0d9/%20"&gt;Visual Studio 2010 SharePoint Power Tools&lt;/a&gt;. Jest to bardzo ubogi zestaw narzędzi do walki z Sandbox Solutions, umożliwia on tworzenia np. Visual WebPart dla SS (nie wspominałem, że nie można nic dodawać do katalogu &lt;b&gt;14-hive&lt;/b&gt; zatem odpadają User Control i nie tylko). Z tym zestawem "toolsów" miałem pewną przygodę. Pisałem pierwszego w życiu WebPart'a, który miał być częścią Sandbox Solution. W momencie gdy chciałem zaimplementować jakąś funkcjonalność, to kończyło się pisaniem JavaScript'u, w pewnym momencie dostałem błąd przy kompilacji paktycznie pustego code behind.&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote style="color: red;"&gt;Microsoft.VisualStudio.SharePoint.Commands.CommandParameter. The maximum  string content length quota (8192) has been exceeded while reading XML  data. This quota may be increased by changing the MaxStringContentLength  property on the XmlDictionaryReaderQuotas object used when creating the  XML reader. )&lt;/blockquote&gt;I myślę sobie, &lt;a href="http://thedailywtf.com/"&gt;kurka &lt;/a&gt;... okazało się, że mój plik ascx przekroczył 8Kb tekstu i dostałem coś takiego.&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://2.bp.blogspot.com/-YcZLNtNbXpA/TpaslVH-F8I/AAAAAAAAJdA/r6smSW-mR8c/s1600/VisualWebPartIsTooBig.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="98" src="http://2.bp.blogspot.com/-YcZLNtNbXpA/TpaslVH-F8I/AAAAAAAAJdA/r6smSW-mR8c/s640/VisualWebPartIsTooBig.png" width="640" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Warnining, który powoduje błąd kompilacji. Po przerzuceniu kilku linijek JavaScript'u do odzielnego pliku wszystko wróciło do normy. Spodziewam się więcej tego typu przygód w przyszłości. &lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;What's hot, what's not? I dlaczego Piaskownica może być hot? &lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-lgtmfACBtOk/Tpybrtg3qCI/AAAAAAAAJiE/ckqvNnL6fFA/s1600/hotgirl.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-lgtmfACBtOk/Tpybrtg3qCI/AAAAAAAAJiE/ckqvNnL6fFA/s1600/hotgirl.png" /&gt;&lt;/a&gt;&lt;/div&gt;Piaskownica została wymyślona na potrzeby Office 365 i żeby administratorom żyło się lepiej. SharePoint Online na chmurze daje spore możliwości zaistnienia na &lt;a href="http://www.sharepointreviews.com/"&gt;rynku produktów&lt;/a&gt;. W tym momencie jak ktoś ma pomysł, to można wykorzystać tą jeszcze "przez moment niszę" i wyprodukować jakieś cudo polskiej myśli inżynierskiej. &lt;br /&gt;
&lt;br /&gt;
Poza tym Sandbox wymusza pisanie bezpiecznego oprogramowanie, dlatego, że &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spsecurity.runwithelevatedprivileges.aspx"&gt;RunWithElevatedPrivileges&lt;/a&gt; odeszło do lamusa. Cały czas działamy w kontekście użytkownika, czyli musimy pisać oprogramowanie działające z różnymi zestawami uprawnień, a nie tylko &lt;i&gt;Elevate&lt;/i&gt; i do domu.... &lt;br /&gt;
&lt;br /&gt;
Pewnym problemem z adaptacją SharePoint'a Online może być to, że w aktualnej wersji SO Microsoft zapomniał napisać provider'a do PowerShell'a (a do Exchange'a napisał). Konsekwencją tego jest to, że wdrożenie pliku wsp na Site Collection będzie zautomatyzowane ręczną aktywacją :).&lt;br /&gt;
&lt;br /&gt;
Ogólnie nie jest łatwo w Piaskownicy, ale w dzieciństwie też nie było łatwo. Zatem trzeba zakasać rękawy i do roboty.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-7474722812034832284?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/0Dqb_OANG-Y" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/7474722812034832284/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/10/obudziem-sie-z-reka-w-piaskownicy.html#comment-form" title="Komentarze (0)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/7474722812034832284?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/7474722812034832284?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/0Dqb_OANG-Y/obudziem-sie-z-reka-w-piaskownicy.html" title="Obudziłem się z ręką w piaskownicy SharePoint'a" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-U1mLKvCG20c/TpyCXIhIIRI/AAAAAAAAJh8/XriQ8I7VSnI/s72-c/Piaskownica.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/10/obudziem-sie-z-reka-w-piaskownicy.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkcEQXg6fip7ImA9WhdUF0g.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-8407776905352633607</id><published>2011-10-04T19:40:00.010+02:00</published><updated>2011-10-04T19:40:00.616+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-04T19:40:00.616+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SQL Server" /><title>Przygód kilka Pana NULL'a</title><content type="html">&lt;h4&gt;Znam się z NULL'em od lat&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-8Rnczcw61po/Toq97t0SCfI/AAAAAAAAJco/2DMwPIWyWL0/s1600/DB1.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-8Rnczcw61po/Toq97t0SCfI/AAAAAAAAJco/2DMwPIWyWL0/s1600/DB1.png" /&gt;&lt;/a&gt;&lt;/div&gt;Oglądając materiały z &lt;a href="http://openclassroom.stanford.edu/MainFolder/CoursePage.php?course=IntroToDatabases"&gt;Introduction to Databases&lt;/a&gt;, przypomniały mi się koszmary z dzieciństwa.&amp;nbsp; Zdarzyło mi się wspierać jako programista bądź inżynier support'u kilka systemów używających MS SQL Server'a (każdy z nich był rozwijany przynajmniej 5 lat). Miały one kilka cech  wspólnych  , jedna z nich sprawiła mi sporo problemów przy rozwijaniu i utrzymywaniu tych systemów. Posiadały one dużo tabel w których definicje kolumn umożliwiały wstawianie wartości NULL. Poniższa definicja tabeli FooTable prezetuje ten sposób definiowania kolumn.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;CREATE TABLE FooTable(
....
FooName VARCHAR(20) NULL
....
)
&lt;/pre&gt;&lt;br /&gt;
Ogólnie zachęcam do definiowania explicite czy dana kolumna w tabeli jest NULL czy NOT NULL, ale wracając do tematu. W tym poście chciałbym podsumować w kilku słowach konsekwencje jakie niesie ze sobą tworzenie dużej ilości kolumn z możliwością wstawienia NULL'a. Moim celem jest przekonanie szanownego czytelnika do prostej zasady, która brzmi następująco.&lt;br /&gt;
&lt;blockquote&gt;Jeżeli ktoś nie przystawia wam towarzyszu "pistoletu do głowy" to proszę nie definiujcie kolumn, które umożliwiają wstawianie wartości NULL. Używajcie wartości domyślnych, stałych wartości lub pustych ciągów znakowych.&amp;nbsp;&lt;/blockquote&gt;&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;NULL i operator NOT IN&amp;nbsp;&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-eXk_UffOgTw/Toq-D5vU93I/AAAAAAAAJcs/GmKLf6hCCbs/s1600/DB2_IN.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-eXk_UffOgTw/Toq-D5vU93I/AAAAAAAAJcs/GmKLf6hCCbs/s1600/DB2_IN.png" /&gt;&lt;/a&gt;&lt;/div&gt;Pewnego dnia po wdrożeniu nowej wersji systemu w raportach zaczęło "brakować" danych. Oskarżenia padły na programistów, którzy na pewno coś pozmieniali w kodzie. Po dogłębnym przeanalizowaniu problemu, okazało się, że przyczyną problemu było pojawienie się wartości NULL w klauzuli operatora NOT IN. Ta sytuacja nie miała nic wspólnego z nową wersją, po prostu dane z zewnętrzengo systemu zawierały te wartości NULL.&lt;br /&gt;
&lt;br /&gt;
Zatem na czym polegał kwas? Załóżmy, że mamy dwie tabele FooTable i BooTable.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;CREATE TABLE dbo.FooTable
(
 FooId Int NOT NULL,
 FooName Varchar(10) NULL
)

CREATE TABLE dbo.BooTable
(
 BooId Int NOT NULL,
 FooName Varchar(10) NOT NULL
)


&lt;/pre&gt;&lt;br /&gt;
Do tabeli FooTable wstawiamy wartość NULL do kolumny FooName.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;INSERT INTO dbo.FooTable(FooId, FooName)
VALUES (1, 'Foo 1'), (2, NULL), (3, 'Foo 3')

INSERT INTO dbo.BooTable(BooId, FooName)
VALUES (10, 'Foo 1'), (11, 'Foo 2' ), (12, 'Foo 3')
&lt;/pre&gt;&lt;br /&gt;
Spróbujmy wyciągnąć te rekordy z BooTable, które wartość FooName nie ma odpowiednika w tabeli FooTable. Zakładam, że powinien pojawić się jeden wiersz(BooId=11, FooName='Foo 2'), niestety otrzymujemy pusty zestaw wyników. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;SELECT b.*
  FROM BooTable b
  WHERE b.FooName NOT IN (SELECT FooName FROM FooTable)
&lt;/pre&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-crNXnM8Lh6c/TorDfU-gELI/AAAAAAAAJc0/ykBIsFXBNmg/s1600/DB_Foo1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-crNXnM8Lh6c/TorDfU-gELI/AAAAAAAAJc0/ykBIsFXBNmg/s1600/DB_Foo1.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Powodem takiego zachowania silnika baz danych jest NULL w zbiorze wynikowym zapytania 'SELECT FooName FROM FooTable' i wynik porównywania wartości do NULL'a. Operator NOT IN można przetłumaczyć na b.FooName &amp;lt;&amp;gt; FooName1 AND b.FooName &amp;lt;&amp;gt; FooName2 etc. Ponieważ warunek b.FooName &amp;lt;&amp;gt; NULL zawsze zwróci NULL, niezależnie od wartości w kolumnie FooName. Zatem klauzula WHERE  w naszych przypadku dla każdego wiersza zwróci NULL. To wyjaśnia kwestię pustego zestawu danych.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;NULL w klauzuli WHERE&lt;/h4&gt;&lt;br /&gt;
Inny problemem który powstaje w wyniku definiowania 'NULLowalnych' kolumn, jest konieczność używania funkcji &lt;a href="http://msdn.microsoft.com/en-us/library/ms184325.aspx"&gt;ISNULL&lt;/a&gt; oraz operatora &lt;a href="http://msdn.microsoft.com/en-us/library/ms188795.aspx"&gt;IS NULL&lt;/a&gt;. Jeżeli chcielibyśmy naprawić powyższe zapytanie, bez zmiany schematu danych możemy to zrobić na dwa sposoby. &lt;br /&gt;
&lt;br /&gt;
Przykład z użyciem operatora IS NOT NULL &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;SELECT b.*
  FROM BooTable b
  WHERE b.FooName NOT IN (SELECT FooName FROM FooTable WHERE FooName IS NOT NULL)
&lt;/pre&gt;&lt;br /&gt;
Przykład z użyciem funkcji &lt;a href="http://msdn.microsoft.com/en-us/library/ms184325.aspx"&gt;ISNULL&lt;/a&gt;, która zwraca wartość drugiego argumetu (w tym przypadku '') jeżeli FooName będzie równa NULL. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;SELECT b.*
  FROM BooTable b
  WHERE b.FooName NOT IN (SELECT ISNULL(FooName,'') FooName FROM FooTable)
&lt;/pre&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-j86k6x0oYGw/TorDNsjL0lI/AAAAAAAAJcw/IcXYgkie_to/s1600/DB_Foo2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-j86k6x0oYGw/TorDNsjL0lI/AAAAAAAAJcw/IcXYgkie_to/s1600/DB_Foo2.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
Rozwiązania nie są równoważne, ponieważ w przykładzie z ISNULL do zbioru wynikowego podzapytania dołożymy wiersz z wartością ''. Aczkolwiek, daje nam obraz jak kod może się komplikować z powodu występowania wartości  NULL. &lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;NULL i JOIN dwa bratanki&lt;/h4&gt;&lt;br /&gt;
Moim największym zarzutem wobec NULL'a jest to, że często wymusza stosowanie pokrętnych konstrukcji z użyciem ISNULL, IS NULL lub COALESCE. &amp;nbsp;Powoduje to znaczące problemy z utrzymanie kodu. Przykładem może być ewolucja następującego zapytania.&lt;br /&gt;
&lt;br /&gt;
Etap 1)  &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;SELECT b.*
  FROM BooTable b
 INNER JOIN FooTable f
    ON b.FooName = f.FooName
&lt;/pre&gt;&lt;br /&gt;
Zapytanie zwraca następujący wynik:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-t9BXtpfPMkg/TorKKD4KKTI/AAAAAAAAJc4/ofZHZWkWpNc/s1600/DB_Foo3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-t9BXtpfPMkg/TorKKD4KKTI/AAAAAAAAJc4/ofZHZWkWpNc/s1600/DB_Foo3.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-t9BXtpfPMkg/TorKKD4KKTI/AAAAAAAAJc4/ofZHZWkWpNc/s1600/DB_Foo3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;br /&gt;
&lt;/a&gt;&lt;/div&gt;Etap 2) Załużmy, że użytkownik zgłosi programiście, że barkuje mu rekordu (11, Foo 2) i wtedy po zmianie kodu mamy już taki twór.&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;SELECT b.*
  FROM BooTable b
 INNER JOIN FooTable f
    ON b.FooName = ISNULL(f.FooName,'Foo 2')
&lt;/pre&gt;&lt;br /&gt;
Powyższe zapytanie zwraca następujący wyniki:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-u13lcZHGznE/TorL-b8QEzI/AAAAAAAAJc8/sSl9qTSv49o/s1600/DB_Foo4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-u13lcZHGznE/TorL-b8QEzI/AAAAAAAAJc8/sSl9qTSv49o/s1600/DB_Foo4.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
I tam możemy bez końca, w powyższym zapytaniu mamy tylko dwie tabele. A jak to będzie wyglądało przy np. 7 tabelach i wielu kolumnach w warunku JOIN'a.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Mam nadzieję, że te kilka przykładów zachęci czytelnika do refleksji czy zawsze warto pójść na kompromis i zadeklarować Column1 VARCHAR(20) NULL, czy jednak lepiej być twardym, nie miętkim i wybrać NOT NULL.&lt;br /&gt;
&lt;br /&gt;
Hope this helps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-8407776905352633607?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/g8n7g5lPLm0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/8407776905352633607/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/10/przygod-kilka-pana-nulla.html#comment-form" title="Komentarze (5)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/8407776905352633607?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/8407776905352633607?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/g8n7g5lPLm0/przygod-kilka-pana-nulla.html" title="Przygód kilka Pana NULL'a" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-8Rnczcw61po/Toq97t0SCfI/AAAAAAAAJco/2DMwPIWyWL0/s72-c/DB1.png" height="72" width="72" /><thr:total>5</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/10/przygod-kilka-pana-nulla.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ak8GQns4fCp7ImA9WhdUGE0.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-4848631678794465680</id><published>2011-10-03T20:22:00.024+02:00</published><updated>2011-10-05T12:00:23.534+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-05T12:00:23.534+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term=".NET" /><title>Triumwirat czyli podatki, dynamic i tuple</title><content type="html">&lt;h4&gt;Progresywny podatek dochodowy w Izraelu&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-Q9N0E5FtBCQ/Tom-6kWeIhI/AAAAAAAAJcY/yxOE_veulQc/s1600/Money.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;br /&gt;
&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-70vis8G5DHA/Tom_keevO8I/AAAAAAAAJcg/564y5L2Ar_8/s1600/Money2.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-70vis8G5DHA/Tom_keevO8I/AAAAAAAAJcg/564y5L2Ar_8/s1600/Money2.png" /&gt;&lt;/a&gt;&lt;/div&gt;Zbliżają się wybory, zatem podatki to teraz gorący temat. Tak się złożyło, że &lt;a href="http://ayende.com/blog/"&gt;Ayende Rahien&lt;/a&gt; na swoim blogu opublikował jedno z &lt;a href="http://ayende.com/blog/108545/the-tax-calculation-challenge"&gt;zadań rekrutacyjnych&lt;/a&gt;, które wykorzystywał do weryfikacji umiejętności potencjalnych kandydatów na programistę. Problem polegał na obliczeniu podatku dochodowego od danej kwoty, zakładając, że stawki podatku &amp;nbsp;i progi podatkowe w Izraelu  są następujące.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;    &lt;td&gt;&lt;b&gt;Progi podatkowe&lt;/b&gt;&lt;/td&gt;    &lt;td&gt;&lt;b&gt;Podatek&lt;/b&gt;&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;    &lt;td&gt;do kwoty 5,070 ILS&lt;/td&gt;    &lt;td&gt;10%&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;    &lt;td&gt;od 5,071&amp;nbsp; do 8,660&amp;nbsp;&lt;/td&gt;    &lt;td&gt;14%&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;    &lt;td&gt;od 8,661 do 14,070&lt;/td&gt;    &lt;td&gt;23%&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;    &lt;td&gt;od 14,071 do 21,240&lt;/td&gt;    &lt;td&gt;30%&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;    &lt;td&gt;od 21,241 do 40,230&lt;/td&gt;    &lt;td&gt;33%&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;    &lt;td&gt;powyżej 40,230&lt;/td&gt;    &lt;td&gt;45%&lt;/td&gt;  &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;
Przykładowe odpowiedzi:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;5,000&amp;nbsp; ILS  –&amp;gt; 500&lt;/li&gt;
&lt;li&gt;5,800&amp;nbsp; ILS  –&amp;gt; 609.2&lt;/li&gt;
&lt;li&gt;9,000 &amp;nbsp; ILS –&amp;gt; 1087.8&lt;/li&gt;
&lt;li&gt;15,000&amp;nbsp; ILS  –&amp;gt; 2532.9&lt;/li&gt;
&lt;li&gt;50,000&amp;nbsp; ILS  –&amp;gt; 15,068.1&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Tuple w akcji&lt;/h4&gt;&lt;br /&gt;
Problem nie jest skomplikowany, ale można użyć go do wypróbowania nowych mechanizmów, klas dostępnych w .NET Framework 4. Pierwsze rozwiązanie używa klasy &lt;a href="http://msdn.microsoft.com/en-us/library/system.tuple.aspx"&gt;Tuple&lt;/a&gt;, która umożliwia definiowania &lt;a href="http://en.wikipedia.org/wiki/Tuple"&gt;krotek&lt;/a&gt; z wieloma uporządkowanymi atrybutami. Wykorzystanie Tuple umożliwia proste zdefiniowanie progów podatkowych i procentu podatku.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;public class TaxCalculator
{
    readonly List&amp;lt;Tuple&amp;lt;decimal, decimal, decimal&amp;gt;&amp;gt; taxRanges = new List&amp;lt;Tuple&amp;lt;decimal, decimal, decimal&amp;gt;&amp;gt;
        {
            new Tuple&amp;lt;decimal, decimal, decimal&amp;gt;(0m, 5070m, .10m),
            new Tuple&amp;lt;decimal, decimal, decimal&amp;gt;(5070m, 8660m, .14m),
            new Tuple&amp;lt;decimal, decimal, decimal&amp;gt;(8660m, 14070m, .23m),
            new Tuple&amp;lt;decimal, decimal, decimal&amp;gt;(14070m, 21240m, .30m),
            new Tuple&amp;lt;decimal, decimal, decimal&amp;gt;(21240m, 40230m, 0.33m),
            new Tuple&amp;lt;decimal, decimal, decimal&amp;gt;(40230m, decimal.MaxValue, .45m)
        };

    public decimal CalculateTax(decimal salary)
    {
        return taxRanges.Sum(tr=&amp;gt;CalculateTaxValuePerRange(salary, tr.Item1, tr.Item2, tr.Item3));
    }

    private decimal CalculateTaxValuePerRange(decimal salary, decimal bottomLimit, decimal upperLimit, decimal taxRate)
    {
        decimal calculatedTax = 0m;
        if(salary - upperLimit &amp;gt;= 0)
        {
            calculatedTax = (upperLimit - bottomLimit)*taxRate;
        }
        else if (salary - bottomLimit &amp;gt;= 0)
        {
            calculatedTax = (salary - bottomLimit)*taxRate;
        }

        return calculatedTax;
    }
}
   

&lt;/pre&gt;&lt;br /&gt;
&lt;h4&gt;Wersja z wzorcami i dynamic&lt;/h4&gt;&lt;br /&gt;
Załóżmy, że przy obliczaniu podatku chcielibyśmy mieć możliwość wykorzystania typu decimal, double, lub własnego typu obliczeniowego. Możemy do tego wykorzystać wzorce, aczkolwiek nie umozliwiają one wykorzystywania operatorów artymetycznych. Do obejścia tego ograniczenia można wykorzystać typ &lt;a href="http://msdn.microsoft.com/en-us/library/dd264736.aspx"&gt;dynamic&lt;/a&gt; (jest to alias typu &lt;a href="http://msdn.microsoft.com/en-us/library/system.object.aspx"&gt;Object&lt;/a&gt;), który umożliwia dzięki wsparciu kompilatora wywoływanie metod, operacji które będą dostępne dopiero w czasie wykonywania kodu (late binding).  &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;public class TaxRange&amp;lt;T&amp;gt;
{
    public TaxRange(T bottomLimit, T upperLimit, T taxRate)
    {
        BottomLimit = bottomLimit;
        UpperLimit = upperLimit;
        TaxRate = taxRate;
    }

    public T BottomLimit { get; set; }

    public T UpperLimit { get; set; }

    public T TaxRate { get; set; }
}

public class TaxCalculator2&amp;lt;T&amp;gt;
{
    private readonly List&amp;lt;TaxRange&amp;lt;T&amp;gt;&amp;gt; _taxRanges;

    public TaxCalculator2(List&amp;lt;TaxRange&amp;lt;T&amp;gt;&amp;gt; taxRanges)
    {
        _taxRanges = taxRanges;
    }

    public T CalculateTax(T salary)
    {
        dynamic totalTaxAmount = 0;
        foreach (TaxRange&amp;lt;T&amp;gt; tr in _taxRanges)
        {
            totalTaxAmount += CalculateTaxValuePerRange(salary, tr);
        }
        return totalTaxAmount;
    }

    private T CalculateTaxValuePerRange(T salary, TaxRange&amp;lt;T&amp;gt; taxRange)
    {
        dynamic calculatedTax = 0;
        if ((dynamic)salary - taxRange.UpperLimit &amp;gt;= 0)
        {
            calculatedTax = ((dynamic)taxRange.UpperLimit - taxRange.BottomLimit) * taxRange.TaxRate;
        }
        else if ((dynamic)salary - taxRange.BottomLimit &amp;gt;= 0)
        {
            calculatedTax = ((dynamic)salary - taxRange.BottomLimit) * taxRange.TaxRate;
        }

        return calculatedTax;
    }
}

var taxRanges2 = new List&amp;lt;TaxRange&amp;lt;double&amp;gt;&amp;gt;
                                        {
    new TaxRange&amp;lt;double&amp;gt;(0, 5070, .10),
    new TaxRange&amp;lt;double&amp;gt;(5070, 8660, .14),
    new TaxRange&amp;lt;double&amp;gt;(8660, 14070, .23),
    new TaxRange&amp;lt;double&amp;gt;(14070, 21240, .30),
    new TaxRange&amp;lt;double&amp;gt;(21240, 40230, 0.33),
    new TaxRange&amp;lt;double&amp;gt;(40230, double.MaxValue, .45)
};

var taxCalculator2 = new TaxCalculator2&amp;lt;double&amp;gt;(taxRanges2);
Console.WriteLine(taxCalculator2.CalculateTax(50000));
&lt;/pre&gt;&lt;br /&gt;
&lt;h4&gt;Na koniec deserek, czyli podatek dochodowych w Polsce&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-Y4e4y_Z7w_U/Tom_oG_L62I/AAAAAAAAJck/PoiDetBlOOY/s1600/Money.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-Y4e4y_Z7w_U/Tom_oG_L62I/AAAAAAAAJck/PoiDetBlOOY/s1600/Money.png" /&gt;&lt;/a&gt;&lt;/div&gt;W pewnym uproszczeniu (mamy ulgi, kwotę wolną od podatku, ZUS, składkę rentową, składkę zdrowotną) skala naszego &lt;a href="http://pl.wikipedia.org/wiki/Podatek_dochodowy_od_os%C3%B3b_fizycznych"&gt;podatku dochodowego&lt;/a&gt; wygląda następująco :&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;    &lt;td&gt;&lt;b&gt;Progi podatkowe&lt;/b&gt;&lt;/td&gt;    &lt;td&gt;&lt;b&gt;Podatek&lt;/b&gt;&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;    &lt;td&gt;do kwoty 85 528zł&lt;/td&gt;    &lt;td&gt;18%&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;    &lt;td&gt;powyżej 85 528zł&lt;/td&gt;    &lt;td&gt;32%&lt;/td&gt;  &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;
&lt;a href="http://www.xe.com/ucc/convert/?Amount=50000&amp;amp;From=ILS&amp;amp;To=PLN"&gt;50,000 ILS&lt;/a&gt; (Izraelskich Szekli) jest to równowartość  44,022 PLN na dzień 03 października 2011 roku. Definicja progów podatkowych w Polsce wygląda następująco.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;var polishTaxRanges = new List&amp;lt;TaxRange&amp;lt;double&amp;gt;&amp;gt;
{
    new TaxRange&amp;lt;double&amp;gt;(0, 85528, .18),
    new TaxRange&amp;lt;double&amp;gt;(85528, double.MaxValue, .32)
};
&lt;/pre&gt;&lt;br /&gt;
Podsumowując, z moich obliczeń wynika, że dla kwoty 44200 PLN w Polsce płacimy niższy podatek dochodowy niż ludzie mieszkający w Izraelu. Niestety nie świadczy to o tym, że płacimy niskie podatki.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;var taxCalculator = new TaxCalculator();
var taxCalculator2 = new TaxCalculator2&amp;lt;double&amp;gt;(taxRanges2);
var taxCalculator3 = new TaxCalculator2&amp;lt;double&amp;gt;(polishTaxRanges);

Console.WriteLine("Tax for Izraeli salary 50,000 ILS is {0} PLN", taxCalculator.CalculateTax(50000) * .88m);
Console.WriteLine("Tax for Izraeli salary 50,000 ILS is {0} PLN", taxCalculator2.CalculateTax(50000) * 0.88);
Console.WriteLine("Tax for Polish salary 44,200 PLN is {0} PLN",taxCalculator3.CalculateTax(44200));


&lt;/pre&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-znx_e4dcYGY/Tom2l9DfjaI/AAAAAAAAJcU/fWxxMABAvLw/s1600/Podatki.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-znx_e4dcYGY/Tom2l9DfjaI/AAAAAAAAJcU/fWxxMABAvLw/s1600/Podatki.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Hope this helps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-4848631678794465680?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/VAgmvwnrCiA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/4848631678794465680/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/10/triumwirat-czyli-podatki-dynamic-i.html#comment-form" title="Komentarze (1)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/4848631678794465680?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/4848631678794465680?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/VAgmvwnrCiA/triumwirat-czyli-podatki-dynamic-i.html" title="Triumwirat czyli podatki, dynamic i tuple" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-70vis8G5DHA/Tom_keevO8I/AAAAAAAAJcg/564y5L2Ar_8/s72-c/Money2.png" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/10/triumwirat-czyli-podatki-dynamic-i.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUMGQXY4fCp7ImA9WhdUEUk.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-6652836489546758461</id><published>2011-09-27T20:17:00.006+02:00</published><updated>2011-09-27T20:17:00.834+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-09-27T20:17:00.834+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SQL Server" /><title>Blokowanie w SQL Server dla początkujących</title><content type="html">&lt;b&gt;Jak ujarzmić MS SQL Server?&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-wo-6hDxFdns/TkA57ol9v1I/AAAAAAAAJJI/OQFJZLsshJg/s1600/SqlEmergency.PNG" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-wo-6hDxFdns/TkA57ol9v1I/AAAAAAAAJJI/OQFJZLsshJg/s1600/SqlEmergency.PNG" /&gt;&lt;/a&gt;&lt;/div&gt;Silnik baz danych MS SQL Server to takie niewdzięczne stworzenie w świecie .NET Framework. Relacyjne bazy danych są często w pogardzie u części programistów,&amp;nbsp; postrzegane jako zło konieczne. Niektórzy tworzą &lt;a href="http://en.wikipedia.org/wiki/NoSQL"&gt;NoSQL movement&lt;/a&gt;,&amp;nbsp; inni proponują, żeby przenieść bazę danych do &lt;a href="http://memcached.org/"&gt;pamięci operacyjnej&lt;/a&gt; lub chociaż do &lt;a href="http://natishalom.typepad.com/nati_shaloms_blog/2007/12/"&gt;chumry&lt;/a&gt;, usunąć relacje i wcisnąć ten cały data management w obiekty. Oczywiście, na dzisiaj (rok 2011, miesiąc Wrzesień) wielu nieszczęśliwych z tego powodu developerów musi zmagać się z SQL Server'em. Właśnie o pewnym aspekcie tych wysiłków chciałem pokrótce napisać.&lt;br /&gt;
Wyróżniłbym kilka klas problemów z którymi zmagają się na codzień programiści baz danych&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Indeksowanie tabel i widoków&lt;/li&gt;
&lt;li&gt;Blokowanie &lt;/li&gt;
&lt;li&gt;Analiza dużej ilości zapytań (tzw. trace)&lt;/li&gt;
&lt;li&gt; Porównywanie danych &lt;/li&gt;
&lt;li&gt;Analiza wydajności zapytań &lt;/li&gt;
&lt;li&gt;Śledzenie i rejestrowanie zapytań generowanych przez aplikację &lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Zapewne można wyróżnić jeszcze kilka kategorii, aczkolwiek proszę o wyrozumiałość, gdyż nic więcej nie przyszło mi do głowy. &amp;nbsp;W tym poście skupię się na zagadnieniach związanych z blokowaniem zapytań i narzędziach które mogą pomóc w rozwiązowaniu problemów spowodowanych przez blokady. &lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Blokowanie na ekranie&lt;/h4&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-LgI9C-f_eK0/TkJ1uLcg50I/AAAAAAAAJJ0/E2o45AWU9LI/s1600/SS_Block.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-LgI9C-f_eK0/TkJ1uLcg50I/AAAAAAAAJJ0/E2o45AWU9LI/s1600/SS_Block.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Blokowanie zapytań jest zjawiskiem częstym na platformie SQL Server, wynika ona z architektury tego silnika bazodanowego i domyślnego ustawienia poziomu izolacji tranzakcji na READ COMMITED. Praktycznie każde zapytanie generuje blokady (różnego rodzaju), zatem rozumienie mechanizmu blokad w SQL Serverze jest dosyć &lt;a href="http://msdn.microsoft.com/en-us/library/ms175519.aspx"&gt;istotne&lt;/a&gt;. Istnieje kilka prostych procedur składowanych, które mogą nam dostarczyć potrzebnych informacji na temat blokowanych procesów, zapytań. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;LockSampleDb&lt;/h4&gt;&lt;br /&gt;
Zacznę od utworzenia bazy danych LockSampleDb, która posłuży nam za poligon eksperymentalny do monitorowania blokad. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;CREATE DATABASE LockSampleDb
GO
Use LockSampleDb
GO
CREATE TABLE dbo.LockSample
(
 Id Int,
 Name Varchar(20)
)
GO
  --Works only in SQL Server 2008 and higher
 DECLARE @i Int = 1
 WHILE @i &amp;lt; 1000
 BEGIN
 INSERT INTO LockSample(Id,Name)
    VALUES (@i, 'Row '+CAST(@i AS VARCHAR)) 
    SET @i += 1
 END
GO 
&lt;/pre&gt;&lt;br /&gt;
Otwórz SQL Server Management Studio i uruchom poniższe zapytanie w jednym oknie.  &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;--Query Window 1
Use LockSampleDb
GO
BEGIN TRAN
 
 UPDATE LockSample
 SET Name = 'Row Updated'
 WHERE Id = 40
 --ROLLBACK TRAN
&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-I115N5ys1kc/ToF5sJudWjI/AAAAAAAAJbw/pbSob9hurkk/s1600/SQLWindow1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-I115N5ys1kc/ToF5sJudWjI/AAAAAAAAJbw/pbSob9hurkk/s1600/SQLWindow1.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
A w drugi uruchom poniższe zapytanie.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;Use LockSampleDb
GO
--Query Window 2 
SELECT * FROM  LockSample WHERE Id = 40
&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-8OaElzerMYA/ToF5xWlRlRI/AAAAAAAAJb0/qlRH82j0rjc/s1600/SQLWindow2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-8OaElzerMYA/ToF5xWlRlRI/AAAAAAAAJb0/qlRH82j0rjc/s1600/SQLWindow2.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
SELECT powinien ładnie zawisnąć, ponieważ próbuje odczytać wiersz, który jest już aktualizowany przez pierwsze zapytanie (tranzakcja jest otwarta, ponieważ nie została uruchomiona instrukcja COMMIT TRAN lub ROLLBACK TRAN).W tym momencie możemy zadziałać na kilka sposobów (SPID&amp;nbsp; procesu zablokowanego to 82, blokującego SPIP to 75, SPID - Server Process Identifier).&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Activity monitor dla leniwych&lt;/h4&gt;&lt;br /&gt;
Najprostszym narzędziem do analizy zablokowanych zapytań jest &amp;nbsp; SQL Server Management Studio Activity Monitor (aka Activity Monitor). &amp;nbsp;Wyświetli on wszystkie procesy SQL Server'a (nie mylić z sqlservr.exe) i w kolumnie Blocked By znajdziemy winowajcę (w SQL Server 2008 klikamy prawym na instancję SQL Servera i wybieramy z menu Activity Monitor).&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-1aaQK3u5wwY/ToF8oFr4OaI/AAAAAAAAJb4/FxkpYjQq1Ls/s1600/ActivityMOnitor.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="340" src="http://3.bp.blogspot.com/-1aaQK3u5wwY/ToF8oFr4OaI/AAAAAAAAJb4/FxkpYjQq1Ls/s640/ActivityMOnitor.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
Kolumna 'Blocked By' wyświetliła SPID &amp;nbsp;procesu blokującego ( w naszym przypadku 75). &amp;nbsp;Wyświetlając Details dla procesu SPID=75 możemy zobaczyć ostatnie zapytanie wykonane na tym procesie, które może powodować blokadę. Podkreślam, może powodować, jest to po prostu ostatnie zapytanie wykonane dla tego procesu.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-kAotX14w258/ToF9w4JFEdI/AAAAAAAAJb8/6k7sVABHXzI/s1600/SPIDDetails.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-kAotX14w258/ToF9w4JFEdI/AAAAAAAAJb8/6k7sVABHXzI/s1600/SPIDDetails.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;sp_who&lt;/h4&gt;&lt;br /&gt;
&lt;a href="http://msdn.microsoft.com/en-us/library/ms174313.aspx"&gt;sp_who&lt;/a&gt; jest procedurą systemową, jest ona częścią silnika bazodanowego. Po wywołaniu prezentuje ona listę "procesów" SQL Servera z informacjami o użytkowniku w kontekście którego uruchamiane są polecenia. Dla nas istotną informacją jest kolumn SPID (identyfikator procesu)oraz blk (numer SPID procesu który blokuje dany proces). Na poniższym obrazku widać, że proces SPID=82 jest blokowany przez proces SPID=75.  Czasami może być tak, że wiele procesów będzie blokowanych, a my szukamy tylko jednego. Z poziomu aplikacji trudno nam będzie ustalić jaki jest SPID danego połączenia, ale z pomocą może przyjść nieudokumentowana procedura &amp;nbsp;&lt;a href="http://sqlserverplanet.com/dba/using-sp_who2/"&gt;sp_who2&lt;/a&gt; która wyświetla dodatkowe informacje o procesie (np. ProgramName).&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-9O5ib_lQ7Ac/TkGNl1yMwfI/AAAAAAAAJJM/FK5ShvDvOlA/s1600/sp_who.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;br /&gt;
&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-5LTazidcbY4/ToGBC-ca8uI/AAAAAAAAJcI/eGSGt1SbkGY/s1600/spwho.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="616" src="http://2.bp.blogspot.com/-5LTazidcbY4/ToGBC-ca8uI/AAAAAAAAJcI/eGSGt1SbkGY/s640/spwho.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-NuU8nKspWcI/ToF_iDYgx5I/AAAAAAAAJcE/vgMNzrQksBE/s1600/spwho.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;br /&gt;
&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;h4&gt;sp_blocker_pss08 czyli kombajn&lt;/h4&gt;&lt;br /&gt;
&lt;a href="http://support.microsoft.com/kb/271509/en-gb"&gt;sp_blocker_pss08&lt;/a&gt; to procedura dostarczona przez Microsoft do rozwiązaywania problemów z blokowanie. Rezultatem uruchomienia tej procedury są informacje o blokadach, rodzajach blokad, procesach na poziomie instancji SQL Server'a.&amp;nbsp; &lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;sp_blocker_pss08 ma trzy rodzaje skryptów tworzących procedurę, w zależności od wersji SQL Servera, więc nie bądź zaskoczony błędami przy uruchomieniu Microsoft'owych skryptów. &lt;/blockquote&gt;&lt;br /&gt;
Oczywiście, zawsze można skorzystać po prostu z starej, dobrej &lt;a href="http://msdn.microsoft.com/en-us/library/ms187749.aspx"&gt;sp_lock&lt;/a&gt;, niestety jest ona oznaczona jako deprecated w BOL (Books Online).&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;sp_lock (deprecated)&lt;/h4&gt;&lt;br /&gt;
W dalszym poszukiwaniu winowajcy, możemy skorzystać z &amp;nbsp;procedury systemowej &lt;a href="http://msdn.microsoft.com/en-us/library/ms187749.aspx"&gt;sp_lock&lt;/a&gt;. Umożliwia ona wylistowanie blokad dla danego procesu lub dla wszystkich procesów SQL Server na instancji. Ponieważ wiemy, że proces SPID=75 blokuje zapytanie SELECT. Możemy pokusić się o wywołanie sp_lock w poniższy sposób.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;EXEC sp_lock 75&lt;/pre&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-QJI_YWQSdQA/ToF-OvELE4I/AAAAAAAAJcA/DD5gDvP0jCM/s1600/splock.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="92" src="http://4.bp.blogspot.com/-QJI_YWQSdQA/ToF-OvELE4I/AAAAAAAAJcA/DD5gDvP0jCM/s320/splock.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Widać, że na proces 75 założył Exclusive Lock na wierszu. Rozszyfrowując ObjId używając funkcji Object_Name docieramy do tabelki &amp;nbsp;LockSample.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;SELECT OBJECT_NAME(2105058535)&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
Można pójśc o krok dalej i znaleźć wiersz, na którym "zawisł" SELECT. Wystarczy użyć nieudokumentowanego polecenia &lt;a href="http://support.microsoft.com/kb/83065"&gt;DBCC PAGE&lt;/a&gt;. Odczytujemy file_id=1, page_id=78 i row_id=39 z columny Resource.&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;DBCC TRACEON(3604) 
DBCC PAGE (LockSampleDB, 1, 78,3)
&lt;/pre&gt;&lt;br /&gt;
I otrzymamy dump całej strony bazy danych na której &amp;nbsp;znajduje się nasz problematyczny wiersz i lokalizujemy dane w tym wierszu.&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-S0uUmOrZa4M/ToGDs7dbIEI/AAAAAAAAJcM/d2YmdtaaFqc/s1600/Row39.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-S0uUmOrZa4M/ToGDs7dbIEI/AAAAAAAAJcM/d2YmdtaaFqc/s1600/Row39.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Pomocne mogą okazać się Dynamic Management Views (DMV's, np. &lt;a href="http://msdn.microsoft.com/en-us/library/ms190345.aspx"&gt;sys.dm_tran_locks&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:sql"&gt;SELECT * FROM sys.dm_tran_locks WHERE request_session_id = 75
&lt;/pre&gt;&lt;br /&gt;
Zaprezentowałem kilka sposobów lokalizowania problemów z blokowaniem zapytań. Zachęcam do eksperymentów z blokowaniem, polecam laboratoria   &lt;a href="http://www.microsoft.com/download/en/details.aspx?id=22971"&gt;PSS Service Center Labs - 2005&lt;/a&gt; dostępne na stronie Microsoft. &lt;br /&gt;
&lt;br /&gt;
Hope this helps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-6652836489546758461?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/eFN0PyhVurU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/6652836489546758461/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/09/blokowanie-w-sql-server-dla.html#comment-form" title="Komentarze (1)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/6652836489546758461?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/6652836489546758461?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/eFN0PyhVurU/blokowanie-w-sql-server-dla.html" title="Blokowanie w SQL Server dla początkujących" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-wo-6hDxFdns/TkA57ol9v1I/AAAAAAAAJJI/OQFJZLsshJg/s72-c/SqlEmergency.PNG" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/09/blokowanie-w-sql-server-dla.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUQGQns5cSp7ImA9WhdUEEs.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-2448759617836971351</id><published>2011-09-26T19:25:00.002+02:00</published><updated>2011-09-26T20:55:23.529+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-09-26T20:55:23.529+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term=".NET" /><category scheme="http://www.blogger.com/atom/ns#" term="Windows" /><title>PowerShell - antywzorzec kopiuj/wklej</title><content type="html">&lt;h4&gt;Dlaczego lubię Add-Type&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-WXAoylM9V8w/ToDKbk6yj6I/AAAAAAAAJbs/Ddw4BvXI0eE/s1600/PSAntyPattern.PNG" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="140" src="http://1.bp.blogspot.com/-WXAoylM9V8w/ToDKbk6yj6I/AAAAAAAAJbs/Ddw4BvXI0eE/s200/PSAntyPattern.PNG" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;Pisząc skrypty w PowerShell'u, szczególnie takie jednorazowego użycia kuszącą opcją jest skorzystanie z cmdlet'u &lt;a href="http://technet.microsoft.com/en-us/library/dd315241.aspx"&gt;Add-Type&lt;/a&gt;. Umożliwia on rejestrację typu(-ów) danych zaimplementowanego w C#,VB.NET lub JScript i wykorzystanie go w skrypcie PowerShell'u. W moim przypadku gdy próbowałem użyć typów danych z .NET Framework w skrypcie otrzymałem następujący wyjątek.&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;span style="color: red;"&gt;Multiple ambiguous overloads found for ... and the argument count: ...&lt;/span&gt;&lt;/blockquote&gt;&lt;br /&gt;
Runtime Powershell'a z jakiegoś powodu nie mógł dopasować odpowiedniej sygnatury metody z typem przekazywanych parametrów. Zamiast spędzić kolejne pół godziny nad hackowaniem kodu, użyłem magicznego cmdlet'u Add-Type.&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Copy/Paste anti-pattern&lt;/h4&gt;&lt;br /&gt;
Skopiowałem istniejący kod w C#, wypisałem referencje do zależnych assembly i wszystko zadziałało bez problemu.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:xml"&gt;$referencedAssemblies = ( 
 "System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
    "System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
    )

$sourceCodeInCSharp = @"
using System.Xml.Linq;

namespace XmlTest
{
    public class XmlGenerator
    {
        public void SaveFile(string filePath)
        {
            XDocument document = new XDocument();
            XElement rootElement = new XElement("root",
                new XElement("children",
                    new[]{
                new XElement("child1", "Child1Value"),
                new XElement("child2", "Child2Value")
            }));
            document.Add(rootElement);
            document.Save(filePath);
        }
    }
}
"@


if ([Type]::GetType("XmlTest.XmlGenerator") -eq $null) 
{
 Add-Type -ReferencedAssemblies $referencedAssemblies -TypeDefinition $sourceCodeInCSharp -Language CSharpVersion3
}

$generator = New-Object XmlTest.XmlGenerator;
$generator.SaveFile("test.xml");
&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
Wynik działania skryptu jest następujący&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:xml"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;root&amp;gt;
  &amp;lt;children&amp;gt;
    &amp;lt;child1&amp;gt;Child1Value&amp;lt;/child1&amp;gt;
    &amp;lt;child2&amp;gt;Child2Value&amp;lt;/child2&amp;gt;
  &amp;lt;/children&amp;gt;
&amp;lt;/root&amp;gt;
&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Zatem, jeżeli nie lubisz Powershell'a, nie masz ochoty lub potrzeby kowertowania kodu z C#/VB.NET etc do PS to zachęcam z zapoznaniem sie z poleceniem &lt;a href="http://technet.microsoft.com/en-us/library/dd315241.aspx"&gt;Add-Type&lt;/a&gt;, które umożliwia także generowanie typów z plików kodu źródłowego.&lt;br /&gt;
&lt;br /&gt;
Hope this helps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-2448759617836971351?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/fDLMXymLg6o" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/2448759617836971351/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/09/powershell-antywzorzec-kopiujwklej.html#comment-form" title="Komentarze (0)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/2448759617836971351?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/2448759617836971351?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/fDLMXymLg6o/powershell-antywzorzec-kopiujwklej.html" title="PowerShell - antywzorzec kopiuj/wklej" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-WXAoylM9V8w/ToDKbk6yj6I/AAAAAAAAJbs/Ddw4BvXI0eE/s72-c/PSAntyPattern.PNG" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/09/powershell-antywzorzec-kopiujwklej.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkQGQX87fSp7ImA9WhdWGEw.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-3396986993656239857</id><published>2011-09-12T08:52:00.003+02:00</published><updated>2011-09-12T08:52:00.105+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-09-12T08:52:00.105+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term=".NET" /><title>Statystyczny programista .NET  - A.D. 2011</title><content type="html">&lt;h4&gt;Co powinien umieć programista .NET w 2011 roku?&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-_YwfqUx1p78/TmnneViejiI/AAAAAAAAJPc/30x6nR4VWlI/s1600/stats1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;br /&gt;
&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-_YwfqUx1p78/TmnneViejiI/AAAAAAAAJPc/30x6nR4VWlI/s1600/stats1.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="200" src="http://4.bp.blogspot.com/-_YwfqUx1p78/TmnneViejiI/AAAAAAAAJPc/30x6nR4VWlI/s200/stats1.png" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;Skupiając się w ostatnich dwóch latach na poznawaniu arkanów SharePoint'a i rozwijaniu własnych zainteresowań, prawie kompletnie zaniedbałem wgryzanie się w coraz to nowe technologie pojawiające się na platformie .NET. Ostatni raz poszukiwałem pracy 4 lata temu, z czystej ludzkiej ciekawości postanowiłem sprawdzić na jakie &amp;nbsp;umiejętności w obrębie technlogii .NET teraz jest zapotrzebowanie na polskim rynku pracy. Oczywiście w tym poście dopuszczę się wszelakich nadużyć i uogólnień, zatem &amp;nbsp; z góry proszę o wybaczenie. &amp;nbsp;Moim celem jest ustalenie jakie umiejętności definiują statystycznego "polskiego" programistę .NET.&lt;br /&gt;
&lt;br /&gt;
Do analizy rynku pracy dla programistów .NET wybrałem mało naukową metodę. Postanowiłem przeglądać ogłoszenia o pracę (&lt;a href="http://jobpilot.pl/"&gt;jobpilot.pl&lt;/a&gt;, &lt;a href="http://pracuj.pl/"&gt;pracuj.pl&lt;/a&gt;) w liczbie 40-stu i wypisywać (oraz zliczać) nazwy technologii, które pojawiają się w treści ogłoszenia.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Statystyczny koder powinien&lt;/b&gt;&amp;nbsp; &lt;br /&gt;
&lt;b&gt;&lt;br /&gt;
&lt;/b&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-gxPt5isAPQU/TmnrNbYz7zI/AAAAAAAAJPo/VVsqlyAv4L8/s1600/BusinessMan.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-gxPt5isAPQU/TmnrNbYz7zI/AAAAAAAAJPo/VVsqlyAv4L8/s1600/BusinessMan.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;ul&gt;&lt;li&gt;Programować w C# z zamkniętymi oczami&lt;/li&gt;
&lt;li&gt;Równie dobrze posługiwać się T-SQL'em&lt;/li&gt;
&lt;li&gt;Biegle operować ASP.NET WebFoms&lt;/li&gt;
&lt;li&gt;i w sumie tyle &amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Opcja "Nice to have"&lt;/b&gt;&lt;br /&gt;
&lt;ul&gt;&lt;a href="http://1.bp.blogspot.com/-CyqZF4jOekw/TmnqwMgTDEI/AAAAAAAAJPk/elPxz7A1JEs/s1600/average.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="200" src="http://1.bp.blogspot.com/-CyqZF4jOekw/TmnqwMgTDEI/AAAAAAAAJPk/elPxz7A1JEs/s200/average.png" width="200" /&gt;&lt;/a&gt;
&lt;li align="center"&gt; XML (XSLT, XML) &amp;nbsp;&lt;/li&gt;
&lt;li align="center"&gt;Web Services&amp;nbsp;&lt;/li&gt;
&lt;li align="center"&gt;WCF&amp;nbsp;&lt;/li&gt;
&lt;li align="center"&gt;JavaScript&lt;/li&gt;
&lt;li align="center"&gt;ASP.NET MVC&lt;/li&gt;
&lt;li align="center"&gt;SharePoint&lt;/li&gt;
&lt;li align="center"&gt;Design Patterns&lt;/li&gt;
&lt;li align="center"&gt;UML &amp;nbsp;(tutaj byłem zaskoczony)&amp;nbsp;&lt;/li&gt;
&lt;li align="center"&gt;Source control (SVN, TFS)&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;b&gt;Jeśli chcemy być bardziej niszowi to&amp;nbsp;&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-NPlCQfgGhZI/TmnqWD137VI/AAAAAAAAJPg/C7GHhuszP14/s1600/Genius.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;br /&gt;
&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-kDwfK91FM88/Tmnre5zT89I/AAAAAAAAJPs/OXh8VEQZ61E/s1600/Genius.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-kDwfK91FM88/Tmnre5zT89I/AAAAAAAAJPs/OXh8VEQZ61E/s1600/Genius.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;ul&gt;&lt;li&gt;WPF/Silverlight&lt;/li&gt;
&lt;li&gt;SCRUM (trochę słabo, że to jest nisza)&lt;/li&gt;
&lt;li&gt;Reporting Services&lt;/li&gt;
&lt;li&gt;Oracle&lt;/li&gt;
&lt;li&gt;Workflow Foundation&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Domain Driven Design&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Zaskoczył mnie prawie kompletny brak wymagań dotyczących TDD, Agile (SCRUM, Kanban), technologii okienkowych (WinForms, WPF). &amp;nbsp; Certyfikacje Microsoft też nie wydają się, żeby miały jakiekolwiek znaczenie dla przyszłych pracodawców (1/40). Ogólnie Web rządzi na całego, muszę się zacząć uczyć JavaScript'u :)&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-RsJHxKpJO0s/TmnxyBDhQrI/AAAAAAAAJPw/wP1E2-EeUyw/s1600/TechnlogyChart.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="383" src="http://2.bp.blogspot.com/-RsJHxKpJO0s/TmnxyBDhQrI/AAAAAAAAJPw/wP1E2-EeUyw/s640/TechnlogyChart.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
Moje pierwsze dwa lata pracy zawodowej to było programowanie w VB.NET, ale w Polsce na szczęście ten język jest równie popularny jak &lt;a href="http://pl.wikipedia.org/wiki/Esperanto"&gt;Esperanto&lt;/a&gt;. &amp;nbsp;A tutaj bardziej tabelkowe ujęcie zagadnienia.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-JNCt9M_I8aY/Tmnx-etHAeI/AAAAAAAAJP0/ejLPJOjy7vM/s1600/TechnlogyTable.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-JNCt9M_I8aY/Tmnx-etHAeI/AAAAAAAAJP0/ejLPJOjy7vM/s1600/TechnlogyTable.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;a href="https://docs.google.com/spreadsheet/pub?hl=en_US&amp;amp;hl=en_US&amp;amp;key=0AmZigrRucWqWdHJVZENNbHBjSXlQRDE5NWFCTDRMN0E&amp;amp;output=xls"&gt;Excel file.&lt;/a&gt; &amp;nbsp;Interaktywną wersja raportu można znaleźć &lt;a href="https://docs.google.com/spreadsheet/pub?hl=en_US&amp;amp;hl=en_US&amp;amp;key=0AmZigrRucWqWdHJVZENNbHBjSXlQRDE5NWFCTDRMN0E&amp;amp;output=html"&gt;tutaj&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;IMHO&lt;/span&gt;, dobry programista .NET powinien znać/umieć/rozumieć&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;C# (.NET 4.0, LINQ)&lt;/li&gt;
&lt;li&gt;Design patterns&lt;/li&gt;
&lt;li&gt;Domain Driven Design&lt;/li&gt;
&lt;li&gt;TDD, (x)Unit, Mocking frameworks&lt;/li&gt;
&lt;li&gt;Visual Studio (zaawansowany)&lt;/li&gt;
&lt;li&gt;Programowanie SQL Server &amp;nbsp;&lt;/li&gt;
&lt;li&gt;Metodologia&amp;nbsp; Agile (chociaż jedno Scrum, Kanban, XP) &amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;SOA&lt;/li&gt;
&lt;li&gt;XML stack (XSL, XSD, XML)&lt;/li&gt;
&lt;li&gt;Jedno repozytorium kodu np. SVN, Git, Team Foundation System&lt;/li&gt;
&lt;li&gt;Być WEB (JavaScript, CSS, HTML basics, &amp;nbsp; Accessibility e.g. WCAG) lub Windows Client &amp;nbsp;(WPF)&lt;/li&gt;
&lt;/ol&gt;&lt;span style="font-size: large;"&gt;IMHO&lt;/span&gt;, "Nice to have"&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;Algorytmy&amp;nbsp; w ramach zainteresowań&amp;nbsp;&lt;/li&gt;
&lt;li&gt;User Interface Design&lt;/li&gt;
&lt;li&gt;HTML/CSS&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;&lt;br /&gt;
Podsumowując, polski rynek pracy dla programistów .NET jest dosyć spolaryzowany. Dominują aplikacje web'owe z backend-em opartym na Microsoft SQL Server . W sumie nic nowego, tylko okienek żal.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Hope this helps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-3396986993656239857?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/blatgiOAgwQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/3396986993656239857/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/09/statystyczny-programista-net-ad-2011.html#comment-form" title="Komentarze (1)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/3396986993656239857?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/3396986993656239857?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/blatgiOAgwQ/statystyczny-programista-net-ad-2011.html" title="Statystyczny programista .NET  - A.D. 2011" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-_YwfqUx1p78/TmnneViejiI/AAAAAAAAJPc/30x6nR4VWlI/s72-c/stats1.png" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/09/statystyczny-programista-net-ad-2011.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D08NRHo7fip7ImA9WhdWFUQ.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-1206330752867338230</id><published>2011-09-09T20:12:00.003+02:00</published><updated>2011-09-09T21:18:15.406+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-09-09T21:18:15.406+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="WPF" /><title>WPF DataGrid w służbie metadanych</title><content type="html">&lt;h4&gt;DataGrid i ja&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-Br1UW7c7pFc/TmnNxPHRCvI/AAAAAAAAJPM/_j8_3SvA1Aw/s1600/cat.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-Br1UW7c7pFc/TmnNxPHRCvI/AAAAAAAAJPM/_j8_3SvA1Aw/s1600/cat.png" /&gt;&lt;/a&gt;&lt;/div&gt;Z programowaniem okienek w Windows rozstałem się na drugim roku studiów. Moje życie zawodowe tak się ułożyło, że nigdy później nie tknąłem Win32 API, MFC, WinForms,a tym bardziej WPF'a. W noworocznym postanowieniu ustaliłem, że spróbuję opanować chociaż podstawy Windows Presentation Foundation. W ramach realizacji postanowień, moja uwaga skupiła się na problemie prezentacji danych w postaci tabelarycznej w WPF. &lt;br /&gt;
&lt;br /&gt;
W .NET Framework 3.5 do dyspozycji programisty WPF był &lt;a href="http://wpf.codeplex.com/"&gt;WPF Toolkit&lt;/a&gt;, w którego skład wchodził &lt;a href="http://wpf.codeplex.com/wikipage?title=Tips%20%26%20Tricks&amp;amp;referringTitle=Home&amp;amp;ProjectName=wpf"&gt;DataGrid&lt;/a&gt;. W .NET Framework 4.0 kontrolka &lt;a href="http://msdn.microsoft.com/en-us/library/system.windows.controls.datagrid.aspx"&gt;DataGrid&lt;/a&gt; wróciła z wygnania na codeplex.com i została włączona do Windows Presentation Foundation.&lt;br /&gt;
&lt;br /&gt;
Ku mojemy zaskoczeniu DataGrid wykazał się dosyć rozbudowaną funkcjonalnością. Scenariusz, który chciałem zrealizować można zoobrazować w następujący sposób. Mamy listę piesków (dogs) i kotków (cats), wyświetlając listy tych zwierzaków nie chciałem deklaratywnie definiować każdego atrybutu w WPF'owym XAML'u. Chciałem wypracować bardziej dynamiczne podejście (cały kod projektu jest dostępny &lt;a href="https://sites.google.com/site/myblogsqlsever/WPFDataGrid.zip"&gt;tutaj&lt;/a&gt;). Klasy kotków i piesków wyglądają jak poniżej.&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;public abstract class AnimalBase
{
    public string NickName { get; set; }

    public DateTime DateOfBirth { get; set; }
}

public class Dog : AnimalBase
{
    public string FavouriteFood { get; set; }

    public string BarkingSong { get; set; }
}

public class Cat : AnimalBase
{
    public string HasStripes { get; set; }

    public string MiaowTune { get; set; }
}

&lt;/pre&gt;&lt;br /&gt;
Dodajmy klasę AnimalPlanet która wygeneruje nam trochę testowych danych, a przy okazji przyda się do zademonstrowania deklaratywnego tworzenia instancji obiektów w XAML'u.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:xml"&gt;&amp;lt;usercontrol.resources&amp;gt;
   &amp;lt;wpfdatagrid:animalplanet x:key="Animals"&amp;gt;&amp;lt;/wpfdatagrid:animalplanet&amp;gt;
&amp;lt;/usercontrol.resources&amp;gt;
&lt;/pre&gt;&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;public class AnimalPlanet
{
    private List&amp;lt;Cat&amp;gt; _cats;
    private List&amp;lt;Dog&amp;gt; _dogs;

    public AnimalPlanet()
    {
        _cats = new List&amp;lt;Cat&amp;gt;();
        _dogs = new List&amp;lt;Dog&amp;gt;();

        _cats.Add(new Cat
        {
            NickName = "Dachowiec", 
            DateOfBirth = DateTime.Now.AddMonths(-13).Date, 
            HasStripes = true,  
            MiaowTune = "DODA, Nie daj sie"
        });
        _cats.Add(new Cat 
        { 
            NickName = "Szczesciarz", 
            DateOfBirth = DateTime.Now.AddYears(-5).Date, 
            HasStripes = false, 
            MiaowTune = "DODA, Katharsis" 
        });
        
        _dogs.Add(new Dog 
        { 
            NickName = "Burek", 
            DateOfBirth = DateTime.Now.AddYears(-7).Date, 
            FavouriteFood = "Burito", 
            BarkingSong = "Feel, Jak aniola glos" 
        });
    }


    public List&amp;lt;Cat&amp;gt; Cats { get { return _cats; } }

    public List&amp;lt;Dog&amp;gt; Dogs { get { return _dogs; } }
}
&lt;/pre&gt;&lt;br /&gt;
&lt;h4&gt;Wersja deklaratywna&lt;/h4&gt;&lt;br /&gt;
Pierwsza wersja deklaratywna listy kotków i piesków powoduje, że musimy deklarować nagłówki, powtarzać deklaracje kolumn itd. Oczywiście posługiwanie XAML'em jest preferowaną metodą w budowaniu WPF'owego UI, aczkolwiek ja chciałem rozwiązać ten w sumie prosty problem w trochę inny sposób. &lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-oZzylLo7V_M/TmnO05GCz8I/AAAAAAAAJPU/t1X9dvvivxA/s1600/DeclarativeGrid.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-oZzylLo7V_M/TmnO05GCz8I/AAAAAAAAJPU/t1X9dvvivxA/s1600/DeclarativeGrid.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;i&gt;DeclarativeDataGrid.xaml&lt;/i&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:xml"&gt;&amp;lt;UserControl x:Class="WPFDataGrid.DeclarativeDataGrid"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:Animals="clr-namespace:WPFDataGrid.Domain.Animals"&amp;gt;
    
    &amp;lt;UserControl.Resources&amp;gt;
        &amp;lt;Animals:AnimalPlanet x:Key="Animals"&amp;gt;&amp;lt;/Animals:AnimalPlanet&amp;gt;
    &amp;lt;/UserControl.Resources&amp;gt;

    &amp;lt;Grid DataContext="{StaticResource ResourceKey=Animals}"&amp;gt;
        &amp;lt;Grid.RowDefinitions&amp;gt;
            &amp;lt;RowDefinition /&amp;gt;
            &amp;lt;RowDefinition Height="30" /&amp;gt;
            &amp;lt;RowDefinition /&amp;gt;
        &amp;lt;/Grid.RowDefinitions&amp;gt;
        
        &amp;lt;StackPanel Grid.Row="0"&amp;gt;
            &amp;lt;TextBlock FontWeight="Bold"&amp;gt;Dogs&amp;lt;/TextBlock&amp;gt;
            &amp;lt;DataGrid ItemsSource="{Binding Dogs}" AutoGenerateColumns="False" HorizontalAlignment="Left" CanUserAddRows="False"&amp;gt;
                &amp;lt;DataGrid.Columns&amp;gt;
                    &amp;lt;DataGridTextColumn Header="Nickname" Binding="{Binding NickName}" /&amp;gt;
                    &amp;lt;DataGridTextColumn Header="Date of birth" Binding="{Binding DateOfBirth}" /&amp;gt;
                    &amp;lt;DataGridTextColumn Header="Favourite Food" Binding="{Binding FavouriteFood}" /&amp;gt;
                    &amp;lt;DataGridTextColumn Header="Barking Song" Binding="{Binding BarkingSong}" /&amp;gt;
                &amp;lt;/DataGrid.Columns&amp;gt;
            &amp;lt;/DataGrid&amp;gt;
        &amp;lt;/StackPanel&amp;gt;

        &amp;lt;StackPanel Grid.Row="2"&amp;gt;
            &amp;lt;TextBlock FontWeight="Bold"&amp;gt;Cats&amp;lt;/TextBlock&amp;gt;
            &amp;lt;DataGrid ItemsSource="{Binding Cats}" AutoGenerateColumns="False" HorizontalAlignment="Left" CanUserAddRows="False"&amp;gt;
                &amp;lt;DataGrid.Columns&amp;gt;
                    &amp;lt;DataGridTextColumn Header="Nickname" Binding="{Binding NickName}" /&amp;gt;
                    &amp;lt;DataGridTextColumn Header="Date of birth" Binding="{Binding DateOfBirth}" /&amp;gt;
                    &amp;lt;DataGridCheckBoxColumn Header="Has Stripes" IsThreeState="True" Binding="{Binding HasStripes}" /&amp;gt;
                    &amp;lt;DataGridTextColumn Header="Miaow Tune" Binding="{Binding MiaowTune}" /&amp;gt;
                &amp;lt;/DataGrid.Columns&amp;gt;
            &amp;lt;/DataGrid&amp;gt;
        &amp;lt;/StackPanel&amp;gt;
    &amp;lt;/Grid&amp;gt;
&amp;lt;/UserControl&amp;gt;

&lt;/pre&gt;&lt;br /&gt;
&lt;h4&gt;Atrybuty na ratunek&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-Nxy3zP-Yt8c/TmnN5yA7w4I/AAAAAAAAJPQ/EA6gf0dCxto/s1600/Dog.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="191" src="http://2.bp.blogspot.com/-Nxy3zP-Yt8c/TmnN5yA7w4I/AAAAAAAAJPQ/EA6gf0dCxto/s200/Dog.png" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;W rozwiązaniu deklaratywnym każda kolumna DataGrid musiała mieć odpowiednik w XAML'u. Jeżeli mamy takich tabelek 10 to praktycznym rozwiązaniem może być generowanie kolumn tabeli na podstawie metadanych obiektów. Do definiowania metadanych użyłem atrybutów .NET. Atrybut VisibleItem dostarcza informacji o tym, które z właściwości (property) danej klasy należy wyświetlać w DataGrid. DisplayName definiuje nagłówek kolumny wyświetlającej dane. Poniżej definicja klasy VisibleItemAttribute, które określa, że ten atrybut może być stosowany do właściwości klas i dla danej właściwości może występować jednokrotnie. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class VisibleItemAttribute : Attribute
{
    public VisibleItemAttribute(string displayName)
    {
        DisplayName = displayName;
    }

    public string DisplayName { get; set; }
}
&lt;/pre&gt;&lt;br /&gt;
Po wprowadzeniu atrybutu VisibleItem klasa AnimalBase wygląda następująco. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;public abstract class AnimalBase
{
    [VisibleItem("Nickname")]
    public string NickName { get; set; }

    [VisibleItem("Date Of Birth")]
    public DateTime DateOfBirth { get; set; }
}
&lt;/pre&gt;&lt;br /&gt;
XAML nowej wersji DataGrida został znacząco uproszczony, aczkolwiek code-behind klasy znacząco sie rozrósł, gdyż cała logika odczytywania metadanych obiektów została tam zaimplementowana. &lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-x4r12iXHByA/TmnO-bOdRSI/AAAAAAAAJPY/qUtG5vnPO3w/s1600/MetadataGrid.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-x4r12iXHByA/TmnO-bOdRSI/AAAAAAAAJPY/qUtG5vnPO3w/s1600/MetadataGrid.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;i&gt;MetadataGrid.xaml&lt;/i&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:xml"&gt;&amp;lt;UserControl x:Class="WPFDataGrid.MetadataGrid"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&amp;gt;
    &amp;lt;Grid &amp;gt;
        &amp;lt;Grid.RowDefinitions&amp;gt;
            &amp;lt;RowDefinition /&amp;gt;
        &amp;lt;/Grid.RowDefinitions&amp;gt;

        &amp;lt;StackPanel Grid.Row="0"&amp;gt;
            &amp;lt;TextBlock FontWeight="Bold" Text="{Binding Title}" /&amp;gt;
            &amp;lt;DataGrid Name="dgMetadata" AutoGenerateColumns="False" HorizontalAlignment="Left" CanUserAddRows="False" /&amp;gt;
        &amp;lt;/StackPanel&amp;gt;
    &amp;lt;/Grid&amp;gt;
&amp;lt;/UserControl&amp;gt;

&lt;/pre&gt;&lt;br /&gt;
&lt;i&gt;MetadataGrid.xaml.cs&lt;/i&gt;&lt;br /&gt;
&lt;br /&gt;
Metoda BuildColumnsFromMetadata generuje kolumny DataGrid'a w zależności od obecności atrybutu VisibleItem oraz typu danej właściwości obiektu. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;public partial class MetadataGrid : UserControl
{
    public string Title { get; set; }

    private IList _itemsSource;

    public IList ItemsSource
    {
        get { return _itemsSource; }
        set
        {
            _itemsSource = value;
            BuildColumnsFromMetadata(_itemsSource);
            dgMetadata.ItemsSource = _itemsSource;
        }
    }

    public MetadataGrid()
    {
        InitializeComponent();
        DataContext = this;
    }

    private void BuildColumnsFromMetadata(IList data)
    {
        dgMetadata.Columns.Clear();
        if (data.Count &amp;gt; 0)
        {
            object firstElement = data[0];

            PropertyInfo[] properties = firstElement.GetType().GetProperties();
            foreach (PropertyInfo property in properties)
            {
                object[] visibleAttributes = property.GetCustomAttributes(typeof(VisibleItemAttribute), false);
                if (visibleAttributes.Length &amp;gt; 0)
                {
                    VisibleItemAttribute itemAttribute = (VisibleItemAttribute)visibleAttributes[0];
                    if (property.PropertyType.Name.Equals(typeof(bool).Name))
                    {
                        var checkBoxColumn = new DataGridCheckBoxColumn();
                        checkBoxColumn.Binding = new Binding(property.Name);
                        checkBoxColumn.Header = itemAttribute.DisplayName;
                        checkBoxColumn.IsThreeState = true;
                        dgMetadata.Columns.Add(checkBoxColumn);
                    }
                    else
                    {
                        var textColumn = new DataGridTextColumn();
                        textColumn.Binding = new Binding(property.Name);
                        textColumn.Header = itemAttribute.DisplayName;
                        dgMetadata.Columns.Add(textColumn);
                    }
                }
            }
        }
    }
}
&lt;/pre&gt;&lt;br /&gt;
Dodatkowo można byłoby dodać atrybuty grupowania, sortowania oraz rozszerzyć VisibleItem o właściwość kolejności kolumn i zaimplementować ich obsługę.&lt;br /&gt;
&lt;br /&gt;
Hope this helps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-1206330752867338230?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/fsBYTiz64ck" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/1206330752867338230/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/09/wpf-datagrid-w-suzbie-metadanych.html#comment-form" title="Komentarze (1)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/1206330752867338230?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/1206330752867338230?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/fsBYTiz64ck/wpf-datagrid-w-suzbie-metadanych.html" title="WPF DataGrid w służbie metadanych" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-Br1UW7c7pFc/TmnNxPHRCvI/AAAAAAAAJPM/_j8_3SvA1Aw/s72-c/cat.png" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/09/wpf-datagrid-w-suzbie-metadanych.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUQMQHcyeCp7ImA9WhdXGEw.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-6401263381145253861</id><published>2011-08-31T20:14:00.004+02:00</published><updated>2011-08-31T21:03:01.990+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-08-31T21:03:01.990+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Windows" /><title>Jak można zapełnić cały dysk i wyjść z tego cało?</title><content type="html">&lt;h4&gt;
Ignorancja to prosta droga do problemów&lt;/h4&gt;
&lt;br /&gt;
Pracując na wirtualnej maszynie i po raz kolejny ratując mój SharePoint'owy świat od zagłady, spostrzegłem, że na dysku C zostało mi 10 MB. Byłem zaskoczony, ale cóż mogę zrobić. Usunąłem niepotrzebne pliki na dysku i zyskałem 400MB, poczułem ulgę. Po 20 minutach znowu zabrakło mi miejsca na dysku C, zacząłem wiec myśleć (uff ...w końcu).&lt;br /&gt;
&lt;br /&gt;
Uruchomiłem &lt;a href="http://technet.microsoft.com/en-us/sysinternals/bb896645"&gt;Process Monitor&lt;/a&gt;'a (z SysInternals Suite), ustawiłem filtr na operacje zapisu do plik&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-5B2yoTmt4Ac/Tl4rJf0RK9I/AAAAAAAAJO0/iHeeICM50mw/s1600/ProcessMonitorFilter.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="245" src="http://3.bp.blogspot.com/-5B2yoTmt4Ac/Tl4rJf0RK9I/AAAAAAAAJO0/iHeeICM50mw/s400/ProcessMonitorFilter.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
zacząłem przeglądać trace'a i ...&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-7v7kXJ8XZd4/Tl4rM5BvLCI/AAAAAAAAJO4/o7qAssJU_Fg/s1600/ProcessMonitor.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="306" src="http://3.bp.blogspot.com/-7v7kXJ8XZd4/Tl4rM5BvLCI/AAAAAAAAJO4/o7qAssJU_Fg/s640/ProcessMonitor.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
.... zobaczyłem, że na dysku E generowane jest dużo plików z informacją o  Assembly Binding (Assembly Binder Log Entry). Spowodowane było to włączeniem logowania binding'u przez Fusion Log Viewer (&lt;a href="http://msdn.microsoft.com/en-us/library/e74a18c4%28v=VS.100%29.aspx"&gt;Fuslogvw.exe&lt;/a&gt;), które zapomniałem wyłączyć (jak włączyć logowanie pisałem &lt;a href="http://mmulawa.blogspot.com/2011/07/programista-i-jego-troubleshooting.html"&gt;tutaj&lt;/a&gt;).&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-F4cLK7GaGhY/Tl4sd6xmATI/AAAAAAAAJO8/H18LQD21t10/s1600/LogFusionFiles.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="265" src="http://1.bp.blogspot.com/-F4cLK7GaGhY/Tl4sd6xmATI/AAAAAAAAJO8/H18LQD21t10/s400/LogFusionFiles.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
Widzę, że ewidentnie to na dysku E generują się te pliku logu, ale "może" (to już jest lamerstwo z mojej strony) też coś na dysku C było zapisywane. Wyłączyłem logowanie i zrestartowałem wszystki procesy i serwisy korzystającego z CLR.NET, aby zaprzestać generowaniu plików z assembly biding.&lt;br /&gt;
&lt;br /&gt;
Szczęśliwy wróciłem do pracy i znowu po jakim czasie skończyło się miejsc na dysku C. Tym razem sięgnąłem po narzędzie zwane &amp;nbsp;&lt;a href="http://windirstat.info/index.html"&gt;WinDirStat&lt;/a&gt;. To cudo niemieckiej technologi, narysowało mi fajne mapki (TreeMap), przeszukało mi wszystkie pliki na dysku, przypisało kategorie na podstawie rozszerzenia pliku, i znalazło duży plik logu bazy danych (.ldf) i page file'a.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-JWq8dZhWN-c/Tl4oWnkSMSI/AAAAAAAAJOs/EYF0aKNrBCc/s1600/WinDirStat.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="284" src="http://4.bp.blogspot.com/-JWq8dZhWN-c/Tl4oWnkSMSI/AAAAAAAAJOs/EYF0aKNrBCc/s640/WinDirStat.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
Najbardziej podobał mi się TreeMap, który w zależności od rozmiaru pliku rysuje proporcjonalny kwadracik. Np. na czerwono widać plik o rozszerzeniu *.sys, a na zielono i żółto kolejno pliki baz danych *.mdf i *.ldf. Taka mapka umożliwia bardzo szybkie zlokalizowanie przerośniętego pliku i określenie, jakiego typu pliki zajmują najwięcej miejsca na dysku.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-ra5_vnRjj_Y/Tl4omkwBJ1I/AAAAAAAAJOw/XiFtTIP8uK0/s1600/TreeMap.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="196" src="http://1.bp.blogspot.com/-ra5_vnRjj_Y/Tl4omkwBJ1I/AAAAAAAAJOw/XiFtTIP8uK0/s640/TreeMap.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
Na wirtualnej maszynie nie robię backup'ów baz danych, a domyślnie bazy kontentowe SharePoint'a są ustawione na Full recovery model. Zatem transakcyjny log rośnie do dowolnych rozmiarów zanim nie zostanie przeprowadzony Full backup bazy lub chociaż backup logu. &amp;nbsp;Zmiana recovery model na Simple i shrink pliku logu transakcyjnego rozwiązało problem ciągłego przyrostu i zmiejszyło rozmiar pliku *.ldf.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-yQDzzptT8wY/Tl4tp8yBZHI/AAAAAAAAJPA/4K4rdQJEA9c/s1600/RecoverModel.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="574" src="http://3.bp.blogspot.com/-yQDzzptT8wY/Tl4tp8yBZHI/AAAAAAAAJPA/4K4rdQJEA9c/s640/RecoverModel.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
Page file'a zwalczyłem w następujący sposób (Manage Computer-&amp;gt;Advanced system settings-&amp;gt;Advanced tab-&amp;gt;Performance-&amp;gt;Advanced tab-&amp;gt;Change button.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-kWqPM9cXHkA/Tl4mjFLBdtI/AAAAAAAAJOo/eFhmx6hWtrw/s1600/PageFile.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="http://3.bp.blogspot.com/-kWqPM9cXHkA/Tl4mjFLBdtI/AAAAAAAAJOo/eFhmx6hWtrw/s640/PageFile.png" width="440" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
Nie polecam takich ustawień Page File'a na produkcyjnym serwerze, ale mi potrzebne było miejsce na dysku.&lt;br /&gt;
&lt;br /&gt;
Hope this help.&lt;br /&gt;
&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-6401263381145253861?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/mFa8XCJmGFA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/6401263381145253861/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/08/jak-mozna-zapenic-cay-dysk-i-wyjsc-z.html#comment-form" title="Komentarze (0)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/6401263381145253861?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/6401263381145253861?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/mFa8XCJmGFA/jak-mozna-zapenic-cay-dysk-i-wyjsc-z.html" title="Jak można zapełnić cały dysk i wyjść z tego cało?" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-5B2yoTmt4Ac/Tl4rJf0RK9I/AAAAAAAAJO0/iHeeICM50mw/s72-c/ProcessMonitorFilter.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/08/jak-mozna-zapenic-cay-dysk-i-wyjsc-z.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUQMR3Y5cCp7ImA9WhdQF0U.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-4123838572753180976</id><published>2011-08-19T21:49:00.000+02:00</published><updated>2011-08-19T21:49:46.828+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-08-19T21:49:46.828+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SharePoint" /><title>12 zasad kulturalnego programisty SharePoint'a (Część II - EOM)</title><content type="html">&lt;h3&gt;Zasad cdn-astąpił&lt;/h3&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-rz4rrxyxSRw/Tk67KIgMVmI/AAAAAAAAJLk/e8jqKRr0gZw/s1600/Kulturalnie.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="281" src="http://2.bp.blogspot.com/-rz4rrxyxSRw/Tk67KIgMVmI/AAAAAAAAJLk/e8jqKRr0gZw/s320/Kulturalnie.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;W &lt;a href="http://mmulawa.blogspot.com/2011/08/12-zasad-kulturalnego-programisty.html"&gt;poprzednim poście&lt;/a&gt; opisałem jak "kulturalny" programista SharePoint'a powinien obchodzić się z feature'ami. Tym razem postaram się opisać kolejne 8 zasad dotyczących WebPartów, Event Reciever'ów, zarządzania zasobami serwera oraz ogólną "czystością" środowiska.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;WebPart&lt;/h3&gt;&lt;br /&gt;
Przychodzi taki moment w projekcie, że klientowi nie podoba się dany Webpart i chciałby go usunąć ze swojego portfolio (Wepart Gallery). Usłużny programista usuwa niepotrzbną klasę webparta i wrzuca nową wersję biblioteki na serwer produkcyjny. Problemy, które mogą wyniknąć z takiego podejścia opisuję w postach &amp;nbsp;&lt;a href="http://mmulawa.blogspot.com/2011/06/test-spcontentdatabase-and-how-to.html"&gt;Test-SPContentDatabase and how to locate MissingWebPart&lt;/a&gt; oraz &lt;a href="http://mmulawa.blogspot.com/2011/06/splimitedwebpartmanager-and.html"&gt;SPLimitedWebPartManager and ErrorWebPart on and off relationship&lt;/a&gt;, polecam też &lt;a href="http://support.microsoft.com/kb/976218/"&gt;KB976218&lt;/a&gt;. Użytkownicy zostają z ErrorWebPart'ami wyświetlonymi na stronie i &amp;nbsp;pozostaje im tylko &lt;a href="http://sharepointblog.pl/2011/08/10/usuwanie-zlych-webpartow/"&gt;WebPart'owe sepuku&lt;/a&gt;.&amp;nbsp; Zatem &lt;b&gt;zasadar nr. 5&lt;/b&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-DOA4tgZJSZc/Tk1xRh-EqaI/AAAAAAAAJK8/TIV1bjI6OfI/s1600/trash.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-DOA4tgZJSZc/Tk1xRh-EqaI/AAAAAAAAJK8/TIV1bjI6OfI/s1600/trash.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;span style="font-size: large;"&gt;Klasa WebPart'a może zostać usunięta po zlikwidowaniu wszystkich instancji Webpart'a znajdujących się na farmie. &lt;/span&gt;&amp;nbsp;&lt;/blockquote&gt;&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Inną przypadłością deinstalacji WebPart'ów jest pozostawianie plików .dwp w WebPart gallery. Pliki te po usunięciu z katalogu SharePoint'a (12-tki lub 14-tki) pliku dwp oraz biblioteki zawierającej klasę WebPart'a zaśmiecają galerię WebPart'ów. Zatem &lt;b&gt;zasada nr. 6&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-DOA4tgZJSZc/Tk1xRh-EqaI/AAAAAAAAJK8/TIV1bjI6OfI/s1600/trash.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-DOA4tgZJSZc/Tk1xRh-EqaI/AAAAAAAAJK8/TIV1bjI6OfI/s1600/trash.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;span style="font-size: large;"&gt;Proces odinstalowania Webpart'a powinien zawierać usuwanie pliku dwp z WebPart Gallery.&lt;/span&gt; &amp;nbsp;&lt;/blockquote&gt;&lt;br /&gt;
Biblioteka zawierająca klasę WebPart'a musi być podpisana kluczem (strongly-signed) aby jednoznacznie określić wersję danego dll'a. W momencie gdy programista postanowi podbić wersję biblioteki (np. z 1.0.0.0 do 1.1.0.0) wpłynie to na istniejące instancje WebPart'a. Należy wtedy dodać&lt;a href="http://msdn.microsoft.com/en-us/library/2fc472t2%28v=VS.100%29.aspx"&gt; assembly binding redirection &lt;/a&gt;do web.config'a i "przekierować" wszystkie instniejące instancje ze starszej wersji na nową oraz dodać nowy wiersz do SafeControls dla 1.1.0.0 i utrzymać stary dla wersji 1.0.0.0. Jest to potrzebne do momentu wyświetlenia przez UI po raz pierwszy instacji WebPart'a, od tego momentu korzysta on z wersji 1.1.0.0. Po pewnym czasie można zatem usunąć &amp;nbsp; SafeControls dla 1.0.0.0, oczywiście jeżeli wszystkie WebParty zostały wyświetlone. Uwaga na boku, bug (opisany  &amp;nbsp;&lt;a href="http://www.bluedoglimited.com/SharePointThoughts/Lists/Posts/Post.aspx?ID=313"&gt;tutaj&lt;/a&gt;)&amp;nbsp;związany z &lt;a href="http://msdn.microsoft.com/en-us/library/ff595303.aspx"&gt;Assembly BindingRedirect element&lt;/a&gt; (solution manifest.xml) został naprawiony dopiero w &lt;a href="http://blogs.msdn.com/b/mcsnoiwb/archive/2011/07/18/assembly-binding-redirection-bug-fixed-in-june-cu.aspx"&gt;June CU 2011&lt;/a&gt; dla SharePoint'a 2010. &amp;nbsp;&lt;b&gt;Zatem zasada nr. 7&lt;/b&gt; &amp;nbsp; &lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-wXLUcPXKilg/Tk5eyL7vVwI/AAAAAAAAJLg/BgJgF0c0zOI/s1600/lucky.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-wXLUcPXKilg/Tk5eyL7vVwI/AAAAAAAAJLg/BgJgF0c0zOI/s1600/lucky.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;span style="font-size: large;"&gt;Nowe wersje biblioteki WebPart'a muszą być obsłużone przez wpisy BindingRedirect w Web.config'u&lt;/span&gt;.&amp;nbsp;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Event Receiver&lt;/h3&gt;&lt;br /&gt;
System wymaga ciągłych zmian, nasze feature'y rejestrują najdziwniejsze event receivery. Instalujemy komponenty od zewnętrznych dostawców, one też dodają event receivery aby obsługiwać możliwie najwięcej zdarzeń. OK, a jeżli nie jest już nam potrzebny dany Event Receiver, to trzeba go odpiąć od każdego zarejestrowanego zdarzenia, i kropka ( a jak nie to znowu&amp;nbsp; &lt;a href="http://support.microsoft.com/kb/976218/"&gt;KB976218&lt;/a&gt;). &lt;b&gt;Zasada nr 8&lt;/b&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-_z5-WxTDy8E/Tk5c-nez2jI/AAAAAAAAJLQ/ho9fPD8j0tw/s1600/uninstall.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-_z5-WxTDy8E/Tk5c-nez2jI/AAAAAAAAJLQ/ho9fPD8j0tw/s1600/uninstall.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;span style="font-size: large;"&gt;Klasę Event Receiver'a można usunąć&amp;nbsp; tylko po odrejestrowaniu wszystkich referencji do obsługiwanych zdarzeń.&amp;nbsp;&lt;/span&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;h3&gt;Rozmaitości&lt;/h3&gt;&lt;br /&gt;
W developmencie SharePoint'a zmiany trzeba dobrze zaplanować i przetestować. Szczególnie jeżeli nadgorliwy programista chce przeprowadzić intensywny "refactoring", aby naprawić całe zło tego świata. Zmiany nazw feature'ów, katalogów, plików, assembly, zmiany wersji assembly, usuwanie elementów, to przykłady refactoringu który może rzucić cię na kolana, jeżeli nie zrobisz tego prawidłowo. Na pocieszenie mogę powiedzieć, że SharePoint 2010 oferuje mechanizmy do np. przenoszenia plików w obrębie katalogu 14\Template\Features\ (patrz &lt;a href="http://msdn.microsoft.com/en-us/library/ff595311.aspx"&gt;MapFile&lt;/a&gt;) w ramach upgrade'u instancji feature'a. &lt;b&gt;Zasada nr 9&lt;/b&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;a href="http://4.bp.blogspot.com/-RjnfQIA-7bY/Tk5cq2SoA3I/AAAAAAAAJLM/c-vDBURdwNU/s1600/disaster.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-RjnfQIA-7bY/Tk5cq2SoA3I/AAAAAAAAJLM/c-vDBURdwNU/s1600/disaster.png" /&gt;&lt;/a&gt;&lt;span style="font-size: large;"&gt;Z refactoringiem w SharePoint jak z ogniem, czyli ostrożnie.&lt;/span&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
Kolejnym wdzięcznym tematem w programowaniu SharePoint są modyfikacje Web.config. Przypominam, że Web.config to nie smietnik, dodane wpisy należy usuwać gdy są już niepotrzebne. Należy też nauczyć się obsługi klasy &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spwebconfigmodification.aspx"&gt;SPWebConfigModification&lt;/a&gt; lub chociaż XPath'a, żeby nie dodawać np. duplikatów. Polecam feature'a do modyfikowania Web.config'a opisanego w tym &lt;a href="http://www.moss2007.be/blogs/vandest/archive/2008/10/03/spwebconfigmodification-without-hardcoding-the-modifications.aspx"&gt;poscie&lt;/a&gt;.&amp;nbsp; &lt;b&gt;Zasada nr 10&lt;/b&gt;. &lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;a href="http://2.bp.blogspot.com/-kUSfx5BQXFQ/Tk5dZRnrq6I/AAAAAAAAJLU/B71l1xJ45gg/s1600/police.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-kUSfx5BQXFQ/Tk5dZRnrq6I/AAAAAAAAJLU/B71l1xJ45gg/s1600/police.png" /&gt;&lt;/a&gt;&lt;span style="font-size: large;"&gt;Web.config trzymamy w należytym stanie. Dodajemy elementy i usuwamy kiedy są już nie potrzebne, zawsze używając SharePoint API.&amp;nbsp;&lt;/span&gt;&lt;/blockquote&gt;&lt;br /&gt;
Rozszerzając funkcjonalność SharePoint'a często korzystamy z obiektów SPWeb, SPSite. Obiekty te &amp;nbsp; konsumują znaczne ilości pamięci operacyjnej. Na farmie SharePoint'a pamięć należy szanować, dlatego wszystkich programistów SharePoint'a zachęcam do uważnego wczytania się w artykuł &lt;a href="http://msdn.microsoft.com/en-us/library/ee557362%28v=office.14%29.aspx"&gt;'Introduction to Using Disposable SharePoint Objects&lt;/a&gt;'. Po prostu 'Don't do evil' i nie produkuj memory leak'ów. Zresztą, nasz rodzimy SharePoint MVP &lt;a href="http://blog.gutek.pl/"&gt;Gutek&lt;/a&gt; o tym pisał np &lt;a href="http://msdn.microsoft.com/pl-pl/library/dispose.aspx"&gt;tutaj&lt;/a&gt;. &lt;b&gt;Zasada nr. 11.&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;a href="http://3.bp.blogspot.com/-EOIJIqzCP2w/Tk5dxnCis2I/AAAAAAAAJLY/TOo32C6YBfk/s1600/senior.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-EOIJIqzCP2w/Tk5dxnCis2I/AAAAAAAAJLY/TOo32C6YBfk/s1600/senior.png" /&gt;&lt;/a&gt;&lt;span style="font-size: large;"&gt;Programując w SharePoint szanuj nie tylko zieleń, ale i pamięć.&lt;/span&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
Na rynku dostępnych jest dużo produktów &amp;nbsp;partnerów Microsoft'u oferujących rozszerzenie funkcjonalności SharePoint'a. Może się zdarzyć, że wasz klient przymusi was do korzystania z takiego rozwiązania Out-of-the-box. Może ponieść to za sobą pewne konsekwencje, szczególnie przy instalowaniu nowych wersji, lub przy migracji na nową wersję SharePoint'a. Vendor oprogramowania może w dowolnej wersji naruszyć wstęczną kompatybilność (np. poprzez usunięcie feature'a), trzeba na to bardzo uważać i patrzeć mu na ręce analizując dostarczone pliki solution (WSP'y). Jest to o tyle istone, że rozwiązywanie potencjalnych problemów może spaść na biednego developer'a. &amp;nbsp;&lt;b&gt;Zatem zasada nr. 12&lt;/b&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;a href="http://4.bp.blogspot.com/-w8-cuoGz5m4/Tk5eOlTcDrI/AAAAAAAAJLc/M7UY6sOmPHc/s1600/love.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-w8-cuoGz5m4/Tk5eOlTcDrI/AAAAAAAAJLc/M7UY6sOmPHc/s1600/love.png" /&gt;&lt;/a&gt;&lt;span style="font-size: large;"&gt;Jeżeli używasz komponentów 3rd-party to zaprzyjaźnij się z zespołem wsparcia tego produktu. &lt;/span&gt;&amp;nbsp;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
Mam nadzieję, że ta lista "zasad" okaże się przydatna w uniknięciu chociaż części problemów z utrzymywaniem środowiska produkcyjnego opartego na platformie SharePoint.&lt;br /&gt;
&lt;br /&gt;
Hope this helps. &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-4123838572753180976?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/sDzOrhiBjP0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/4123838572753180976/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/08/12-zasad-kulturalnego-programisty_19.html#comment-form" title="Komentarze (0)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/4123838572753180976?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/4123838572753180976?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/sDzOrhiBjP0/12-zasad-kulturalnego-programisty_19.html" title="12 zasad kulturalnego programisty SharePoint'a (Część II - EOM)" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-rz4rrxyxSRw/Tk67KIgMVmI/AAAAAAAAJLk/e8jqKRr0gZw/s72-c/Kulturalnie.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/08/12-zasad-kulturalnego-programisty_19.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0AFR3g7cSp7ImA9WhdQGE4.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-2805605403291075790</id><published>2011-08-18T22:37:00.003+02:00</published><updated>2011-08-20T13:28:36.609+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-08-20T13:28:36.609+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SharePoint" /><title>12 zasad kulturalnego programisty SharePoint'a (Część I)</title><content type="html">&lt;h3&gt;Trauma i terapia w jednym&lt;/h3&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-FSRToX42SK8/Tk1y185z6OI/AAAAAAAAJLI/TuG7t1JyOCA/s1600/cutlery.gif" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="202" src="http://3.bp.blogspot.com/-FSRToX42SK8/Tk1y185z6OI/AAAAAAAAJLI/TuG7t1JyOCA/s320/cutlery.gif" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;Migracja z MOSS'a 2007 do SharePoint'a 2010 to dla mnie bardzo osobisty temat. Od roku z krótszymi lub dłuższymi przerwami zajmuję się zagadnieniami związanymi z migracją. Przechodzenia na nowszą&amp;nbsp; wersję SharePoint'a mogłoby być znacznie przyjemniejszym tematem, gdyby każdy był "kulturalnym" programistą SharePoint'a. Co to znaczy "kulturalny" programista SharePointa? Jest to taki jegomość, który po sobie zawsze zostawia porządek, a w szególności kod napisany przez tego jegomościa zawsze po sobie posprząta i nie rozrabia. Okazuje się, że na platformie SharePoint bardzo łatwo można zostawić po sobie bałagan. Jako część terapii post-migracyjnej postanowiłem napisać 12 zasad "kulturalnego" obchodzenia się z SharePoint'em. Przestrzeganie ich grawantuje dłuższe weekendy z rodziną i więcej czasu na łowienie ryb oraz szeroko rozumiane zdrowie psychiczne. &amp;nbsp; &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Feature&lt;/h3&gt;&lt;br /&gt;
Wszystko zawsze zaczyna się od niewinnego feature'a. W sumie SharePoint powinien nazywać się FeaturePoint. Feature'y to podstawa każdego komponentu w SharePoint'cie, zatem zrozumienie ich cyklu życia jest bardzo ważne. Feature instalujemy raz dla całej farmy, aktywujemy wielokrotnie (nazwę to instancją feature'a), to samo z deaktywacją (usunięcie instancji feature'a), i na koniec odinstalowujemy (ewentualnie upgrade'ujemy ale to inna historia). Czyli wszystko jasne. Niestety nie zawsze ten łańcuch zdarzeń musi się wykonać. W praktyce może to wyglądać następująco&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;CoolFeature jest instalowany&lt;/li&gt;
&lt;li&gt;CoolFeature jest aktywowany na Site1, Site2, Site3&lt;/li&gt;
&lt;li&gt;CoolFeature jest deaktywowany na Site1, Site2&lt;/li&gt;
&lt;li&gt;CoolFeature jest odisntalowany z atrybutem force lub usunięty z pliku solution (.wsp).&amp;nbsp;&lt;/li&gt;
&lt;li&gt;I zostaliśmy z osieroconą instancją feature'a na Site3 &lt;/li&gt;
&lt;/ol&gt;&lt;br /&gt;
Z tego przykładu wynika &lt;b&gt;zasada nr.1&lt;/b&gt; &lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-F9-Dn_p8e28/Tk1w-8If9NI/AAAAAAAAJK4/AS_y0SWHtOQ/s1600/alien.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-F9-Dn_p8e28/Tk1w-8If9NI/AAAAAAAAJK4/AS_y0SWHtOQ/s1600/alien.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;span style="font-size: large;"&gt;Zanim feature może być usunięty z pliku solution lub odinstalowany wszystkie jego instancje muszą być dezaktywowane i jeśli to konieczne odinstalowane. Dotyczy to całej farmy SharePoint'a.&lt;/span&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
Innym przypadkiem, z który można się zetknąć, jest zmiana Scope'u feature'a. Mamy do wyboru cztery Farm, Web Application, Site (site collection), Web (site). Scenariusz który może wpędzić nas w kłopoty jest następujący. &lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;CoolFeature jest instalowany (Scope=Site)&lt;/li&gt;
&lt;li&gt;CoolFeature jest aktywowany na Site1&lt;/li&gt;
&lt;li&gt;CoolFeature ma zmieniony Scope na Web&lt;/li&gt;
&lt;li&gt;CoolFeature jest instalowany z atrybutem force&lt;/li&gt;
&lt;li&gt;CoolFeature jest aktywowany z atrybutem force na Site1&lt;/li&gt;
&lt;li&gt;Zostaliśmy z osieroconą instancją feature'a na Site1 ze scope=Site &lt;/li&gt;
&lt;/ol&gt;Problemy, które mogą wyniknąć&amp;nbsp; z posiadania takich "sierot" opisuję w poście '&lt;a href="http://mmulawa.blogspot.com/2011/06/sharepoint-2010-upgrade-failed-feature.html"&gt;SharePoint 2010 upgrade failed - Feature upgrade failed for Feature&lt;/a&gt;'. Zatem, &lt;b&gt;zasada nr.2&lt;/b&gt;. &lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-DOA4tgZJSZc/Tk1xRh-EqaI/AAAAAAAAJK8/TIV1bjI6OfI/s1600/trash.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;br /&gt;
&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-MDcb4LYckMg/Tk1yRbackBI/AAAAAAAAJLE/j8HcpfG6KbI/s1600/prison.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-MDcb4LYckMg/Tk1yRbackBI/AAAAAAAAJLE/j8HcpfG6KbI/s1600/prison.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;span style="font-size: large;"&gt;Scope feature'a może być zmieniony w sytuacji, w której feature zostanie najpierw deaktywowany na całej farmie i odinstalowany. &lt;/span&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
Programista to taka niespokojna istota, cały czas chciałaby coś zmieniać. Najbardziej lubi zmieniać nazwy, a taką idealną nazwą do zmiany jest nazwa feature'a. Może się to przydażyć gdy zaczniemy np. przenosić kod&amp;nbsp; z Visual Studio 200x do Visual Studio 2010 dla instiejących feature'ów SharePoint'a. Zapewne będziemy chcieli skorzystać z designer'a feature'ów, aczkolwiek może się to skończyć różnie i nazwa feature'a ulegnie zmianie. Jako efekt uboczny, może się zdarzyć, że pozmieniamy ścieżki do plików, których instancje już istnieją na farmach produkcyjnych (np. instancje master page'a którego plik master jest częścią feature'a). Dlatego &lt;b&gt;zasada nr. 3&lt;/b&gt;.&amp;nbsp; &lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-H4dBtReB-DQ/Tk1x8w5dn_I/AAAAAAAAJLA/AmCfePNItKo/s1600/currencyexchange.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-H4dBtReB-DQ/Tk1x8w5dn_I/AAAAAAAAJLA/AmCfePNItKo/s1600/currencyexchange.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;span style="font-size: large;"&gt;Nazwa feature'a może być zmieniona w sytuacji &lt;/span&gt;&lt;span style="font-size: large;"&gt;, w której feature zostanie najpierw deaktywowany na całej farmie i odinstalowany.&lt;/span&gt;&lt;/blockquote&gt;&lt;br /&gt;
Feature'y podczas aktywacji mogą tworzyć dużo różnych obiektów (np. instancje list) lub rejestrować np. event receiver'y. W momencie deaktywacji, dobrze napisany feature powinien po sobie posprzątać, jeżeli zostało to uzgodnione z użytkownikami (np. usunięcie instacji listy przy deaktywacji feature'a może być nie do pomyślenia w niektórych przypadkach). Czyli &lt;b&gt;zasada nr. 4 &lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;a href="http://4.bp.blogspot.com/-DOA4tgZJSZc/Tk1xRh-EqaI/AAAAAAAAJK8/TIV1bjI6OfI/s1600/trash.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-DOA4tgZJSZc/Tk1xRh-EqaI/AAAAAAAAJK8/TIV1bjI6OfI/s1600/trash.png" /&gt;&lt;/a&gt;&lt;span style="font-size: large;"&gt;Jeżeli jest to możliwe feature powinien sprzątać po sobie w momencie deaktywacji.&lt;/span&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
Przy realizacji tych zasad należy kierować się zdrowym rozsądkiem, gdyż czasami z przyczyn obiektywnych nie można deaktywować feature'a. Zatem zasada nr. 0 to zdrowy rozsądek i dużo testów. W &lt;a href="http://mmulawa.blogspot.com/2011/08/12-zasad-kulturalnego-programisty_19.html"&gt;części drugiej &lt;/a&gt;napiszę o zasadach dotyczących WebPartów i Event Receiver'ów. &lt;br /&gt;
&lt;br /&gt;
Hope this helps. &lt;br /&gt;
&lt;br /&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-2805605403291075790?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/OS0RyG8CoxI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/2805605403291075790/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/08/12-zasad-kulturalnego-programisty.html#comment-form" title="Komentarze (0)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/2805605403291075790?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/2805605403291075790?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/OS0RyG8CoxI/12-zasad-kulturalnego-programisty.html" title="12 zasad kulturalnego programisty SharePoint'a (Część I)" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-FSRToX42SK8/Tk1y185z6OI/AAAAAAAAJLI/TuG7t1JyOCA/s72-c/cutlery.gif" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/08/12-zasad-kulturalnego-programisty.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkUCQX85fSp7ImA9WhdQEUo.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-2019251153395000260</id><published>2011-08-12T19:31:00.003+02:00</published><updated>2011-08-12T19:31:00.125+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-08-12T19:31:00.125+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term=".NET" /><category scheme="http://www.blogger.com/atom/ns#" term="Windows" /><title>PowerShell i debuggowanie biblioteki .Net</title><content type="html">&lt;h4&gt;"Reużywalność" w PowerShell'u&lt;/h4&gt;&lt;br /&gt;
Pisząc skrypt w PowerShell'u postanowiłem wywołać metodę z zewnętrznej biblioteki wchodzącej w skład rozwijanego przez mój zespól systemu. Zaimportowałem dll'kę za pomocą metody &lt;a href="http://msdn.microsoft.com/en-us/library/1009fa28.aspx"&gt;Assembly.LoadFrom&lt;/a&gt; , utworzyłem instancję typu danych i uruchomiłem metodę. Oczywiście otrzymałem wyjątek NullReferenceException, który wskazywał na to, że problem pojawił się w &amp;nbsp;metodzie z zewnętrzenej biblioteki. Zastanowiłem się chwilę i stwierdziłem, że nie zaszkodziłoby zdebuggować tej metody. Ku mojemu zaskoczeniu, okazało się, że użycie Visual Studio zadziałało bez zarzutu.&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Debuggowanie&lt;/h4&gt;&lt;br /&gt;
Skrypt ładuje bibliotekę ReusableLibrary.dll, nie jest to podpisana biblioteka, znajduje się w tym samym folderze co plik skryptu. Natępnie, tworzona jest instancja klasy NetworkManager i wywołana zostaje metoda SentPackage. Uruchomieniu poniższego skryptu w konsoli PowerShell'a spowodowało zwrócenie informacji o błędzie.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;$workingDir = Get-Location
$assemblyPath = [System.IO.Path]::Combine($workingDir,"ReusableLibrary.dll");
[System.Reflection.Assembly]::LoadFrom($assemblyPath);
$netManager = new-object Mulawa.ReusableLibrary.NetworkManager;
# Causes method to throw NullReferenceException
$netManager.SentPackage("");

trap  [Exception]
{
	write-error $($_.Exception.GetType().FullName); 
	write-error $($_.Exception.Message); 
}
&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
Błąd NullReferenceException poniżej.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-wBVOL1Nngb8/TkUEJTIOTII/AAAAAAAAJKY/CwFojoqdWvk/s1600/PSError.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="292" src="http://4.bp.blogspot.com/-wBVOL1Nngb8/TkUEJTIOTII/AAAAAAAAJKY/CwFojoqdWvk/s640/PSError.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
Dysponując kodem źródłowym (cały projekt dostępny jest &lt;a href="https://sites.google.com/site/myblogsqlsever/PowerShellDebug.zip"&gt;tutaj&lt;/a&gt;) do bibiloteki ReusableLibrary uruchomiłem "jedyne słuszne środowisko developerskie". &amp;nbsp;Skompilowałem ResuableLibrary.dll w trybie DEBUG i skopiowałem ddl'kę do katalogu w którym znajdowal się skrypt SentNetworkPackage.ps1. Uruchomiłem ponownie sesję PowerShell'a. Odpaliłem skrypt, tak żeby process konsoli PowerShell'a załadowala dll'kę do AppDomain.&lt;br /&gt;
&lt;br /&gt;
W Visual Studio wybrałem z menu Debug-&amp;gt;Attach to Process.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-dCNxvUrSBDU/TkTcNnK_VEI/AAAAAAAAJKU/uTeaq0uhn6o/s1600/AttachToProcess.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-dCNxvUrSBDU/TkTcNnK_VEI/AAAAAAAAJKU/uTeaq0uhn6o/s1600/AttachToProcess.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
W dialogu "Attach to Process" zlokalizowałem proces Powershell.exe i kliknąłem "Attach".&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-BmEIUunxzoo/TkUF6FLd-cI/AAAAAAAAJKc/bzasi2IvR6U/s1600/AttachToProcess2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="432" src="http://3.bp.blogspot.com/-BmEIUunxzoo/TkUF6FLd-cI/AAAAAAAAJKc/bzasi2IvR6U/s640/AttachToProcess2.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
Wybierając opcję Debug-&amp;gt;Windows-&amp;gt;Modules można sprawdzić, że symbole (pliki pdb) assembly ReusableLibrary.dll załadowały się poprawnie. Potwierdz, to też wypełniona czerwona kropka breakpoint'a.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-Yl0WLeoFUfs/TkUH305oBMI/AAAAAAAAJKg/6Iei1FSLPI0/s1600/SymbolsLoaded.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="200" src="http://4.bp.blogspot.com/-Yl0WLeoFUfs/TkUH305oBMI/AAAAAAAAJKg/6Iei1FSLPI0/s640/SymbolsLoaded.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
W tym momencie możemy uruchomić&amp;nbsp; ponownie &amp;nbsp;skrypt powershell &amp;nbsp;i przystopić do zlokalizowania źródła błędu.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-_IfqaqY4XAU/TkUJUUEbHwI/AAAAAAAAJKk/EF1eafzHzks/s1600/DebuggingPS.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="248" src="http://4.bp.blogspot.com/-_IfqaqY4XAU/TkUJUUEbHwI/AAAAAAAAJKk/EF1eafzHzks/s640/DebuggingPS.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://www.powergui.org/servlet/KbServlet/download/2391-102-5106/PowerGUI-Badge-GetToThePrompt.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img alt="Get-ToThePrompt -at PowerGUI.org" border="0" height="75" src="http://www.powergui.org/servlet/KbServlet/download/2391-102-5106/PowerGUI-Badge-GetToThePrompt.png" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;Do edycji skryptów PowerShell'owych użyłem darmowej wersji edytora &lt;a href="http://powergui.org/downloads.jspa"&gt;PowerGUI&lt;/a&gt;, który wspiera intellisense, umożliwia debuggowanie czy wstawianie snippetów, ma także wbudowaną wyszukiwarkę skryptów opublikowanych online. Ogólnie polecam.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-x8HGNz4FkF8/TkUO0Ag7HmI/AAAAAAAAJKo/TnG6DbmWJFg/s1600/PowerGUI.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="296" src="http://1.bp.blogspot.com/-x8HGNz4FkF8/TkUO0Ag7HmI/AAAAAAAAJKo/TnG6DbmWJFg/s640/PowerGUI.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Jeśli nie GAC to jak?&lt;/h4&gt;&lt;br /&gt;
Jeszcze dwa słowa o ładowaniu bibliotek zewnętrznych w Powershell'u. W przykładzie użyłem metody Assembly.LoadFrom, żeby wczytać bibliotekę (niepodpisana) z lokalnego folderu. Aczkolwiek biblioteka może być podpisana i znajdować się w GAC'u. Wtedy do załadowania możemy wykorzystać metodę &lt;a href="http://msdn.microsoft.com/en-us/library/ky3942xh.aspx"&gt;Assembly.Load&lt;/a&gt;. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;[System.Reflection.Assembly]::Load("ReusableLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7b7ae4b98f14b79d");
&lt;/pre&gt;&lt;br /&gt;
Jeżeli Assembly nie jest podpisane i znajduje się w katalogu aplikacji (dla powershell.exe domyślnie to jest SystemDrive\Windows\System32\WindowsPowerShell\v1.0) to wystarczy wywołać Assembly.Load w następujący sposób wykorzystując tylko assembly "simple name".&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;[System.Reflection.Assembly]::Load("ReusableLibrary");
&lt;/pre&gt;&lt;br /&gt;
Popularną metodą wczytywanie bibliotek w PowerShellu jest używanie &lt;a href="http://msdn.microsoft.com/en-us/library/12xc5368.aspx"&gt;Assembly.LoadWithPartialName&lt;/a&gt;. Ta metoda od .NET Framework 2.0 jest oznaczona jako obsolete, choć jej popularność nie słabnie. Metoda LoadWithPartialName próbuje załadować bibilotekę z katalogu aplikacjia lub z GAC'a pobrać ostatnią wersję biblioteki. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;[System.Reflection.Assembly]::LoadWithPartialName("ReusableLibrary");
&lt;/pre&gt;&lt;br /&gt;
Hope this helps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-2019251153395000260?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/hHybPxZaN24" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/2019251153395000260/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/08/powershell-i-debuggowanie-biblioteki.html#comment-form" title="Komentarze (1)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/2019251153395000260?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/2019251153395000260?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/hHybPxZaN24/powershell-i-debuggowanie-biblioteki.html" title="PowerShell i debuggowanie biblioteki .Net" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-wBVOL1Nngb8/TkUEJTIOTII/AAAAAAAAJKY/CwFojoqdWvk/s72-c/PSError.png" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/08/powershell-i-debuggowanie-biblioteki.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEcMQ3YycSp7ImA9WhdQEEQ.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-7008258366288133067</id><published>2011-08-11T20:15:00.007+02:00</published><updated>2011-08-11T21:48:02.899+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-08-11T21:48:02.899+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SharePoint" /><category scheme="http://www.blogger.com/atom/ns#" term="English" /><title>Why SharePoint 2010 parallel content database upgrade can make your life difficult?</title><content type="html">&lt;h4&gt;Parallel means faster upgrade&lt;/h4&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-WtFslepYq_A/TkPl1QDJfeI/AAAAAAAAJKA/shsMdoxC0MU/s1600/SharePointMigration.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-WtFslepYq_A/TkPl1QDJfeI/AAAAAAAAJKA/shsMdoxC0MU/s1600/SharePointMigration.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;a href="http://2.bp.blogspot.com/-IhGy34Ewd1Q/TkPlpcvSZxI/AAAAAAAAJJ8/vygFNtxpyXg/s1600/SharePointMigration.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;br /&gt;
&lt;/a&gt;&lt;br /&gt;
I've been faced with the upgrade of ~200 content databases (~2TB) from MOSS 2007 to SharePoint 2010. Content databases will be hosted on four different SP2010 farms, and the method chosen for the content upgrade was &lt;a href="http://technet.microsoft.com/en-us/library/cc263447.aspx"&gt;attach-database&lt;/a&gt;. This method is using &lt;a href="http://technet.microsoft.com/en-us/library/ff607581.aspx"&gt;Mount-SPContentDatabase&lt;/a&gt; command to attach and upgrade particular database. If upgrade fails for some reason you can restart it by using &lt;a href="http://technet.microsoft.com/en-us/library/ff607813.aspx"&gt;Upgrade-SPContentDatabase&lt;/a&gt; (it worked pretty well for me). Single database can take a significant time to upgrade (e.g. 50GB database took 4h), but the migration time depends on the number of &lt;a href="http://technet.microsoft.com/en-us/library/cc262891.aspx"&gt;factors&lt;/a&gt; (e.g. hardware, network latency, SharePoint data characteristics, disk space available).&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
The first attempt to tackle the task was by doing sequential upgrade (I just forgot that I can do it in a parallel way, good for me :)). After fixing problems with the content described &lt;a href="http://mmulawa.blogspot.com/2011/06/test-spcontentdatabase-and-how-to.html"&gt;here&lt;/a&gt; and &lt;a href="http://mmulawa.blogspot.com/2011/06/sharepoint-2010-upgrade-failed-feature.html"&gt;here&lt;/a&gt;, I got to the point where I was able to upgrade all content databases without any failures (single error causes upgrade being marked as failed). These were the happy times.&lt;br /&gt;
&lt;br /&gt;
However, content migration was taking to much time, so as being proactive I picked parallel upgrade approach. My inner voices were sceptic, however reading Microsoft &lt;a href="http://technet.microsoft.com/en-us/library/cc263447.aspx"&gt;documentation&lt;/a&gt; made me more confident.&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;You can attach and upgrade multiple databases at a time to speed up the upgrade process overall. The maximum number of parallel upgrades depends on your hardware. .... &amp;nbsp;&lt;span style="font-size: large;"&gt;Faster upgrade times for your overall environment&lt;/span&gt;.&lt;/blockquote&gt;&lt;br /&gt;
&lt;h4&gt;I didn't get lucky enough&lt;/h4&gt;&lt;br /&gt;
Hurray...., I said to myself. I've opended four poweshell sessions on the single front-end server and run my batch files full of Mount-SPContentDatabase calls. I was upgrading four content database at the same time. To my surprise, I've received a bunch of errors during this upgrade:&lt;br /&gt;
&lt;br /&gt;
&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt; &lt;td width="100"&gt;&lt;b&gt;Error Type&lt;/b&gt;&lt;/td&gt; &lt;td&gt;&lt;b&gt;Error Message&lt;/b&gt;&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign="top"&gt;&lt;br /&gt;
&lt;span style="background-color: #b45f06;"&gt;&lt;span style="background-color: #cc0000;"&gt;&lt;span style="background-color: white;"&gt;Feature upgrade failure&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;  &lt;td&gt;&lt;br /&gt;
Feature upgrade incomplete for Feature 'Fields' (Id: 'ca7bd552-10b1-4563-85b9-5ed1d39c962a') in Site '[SiteURLHere]'. Exception: A transport-level error has occurred when sending the request to the server. (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.)&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign="top"&gt;&lt;span style="background-color: orange;"&gt;&lt;span style="background-color: white;"&gt;Database schema upgrade failure&lt;/span&gt;&lt;/span&gt;&lt;/td&gt; &lt;td&gt;Action 4.0.138.0 of Microsoft.SharePoint.Upgrade.SPContentDatabaseSequence failed. Exception: A transport-level error has occurred when receiving results from the server. (provider: TCP Provider, error: 0 - The specified network name is no longer available.)&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign="top"&gt;&lt;span style="background-color: #f6b26b;"&gt;&lt;span style="background-color: white;"&gt;Upgrade failure&lt;/span&gt;&lt;/span&gt;&lt;/td&gt; &lt;td&gt;This upgrade session has been stopped. Possible causes include the process being terminated abruptly or the OS has rebooted. Please restart the upgrade again.&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign="top"&gt;&lt;span style="background-color: #93c47d;"&gt;Upgrade completed successfully&lt;/span&gt;&lt;/td&gt; &lt;td&gt;&lt;i&gt;Content database is being shown on Check upgrade status page (http://CentralAdminUrl/_admin/UpgradeStatus.aspx) as migrated, however it's not. This &amp;nbsp;can be verified on the &amp;nbsp;Review database status page (http://CentralAdminUrl/_admin/DatabaseStatus.aspx).&lt;/i&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;When accessing SharePoint sites &amp;nbsp;from not-upgraded content database&amp;nbsp;&lt;/i&gt; &lt;i&gt;through API&lt;/i&gt; &lt;i&gt;&amp;nbsp;you can get the following error:&lt;/i&gt;&lt;br /&gt;
&lt;br /&gt;
Microsoft.SharePoint.Upgrade.SPUpgradeCompatibilityException: There is a compatibility range mismatch between the Web server and database "Content database name", and connections to the data have been blocked to due to this incompatibility. This can happen when a content database has not been upgraded to be within the compatibility range of the Web server, or if the database has been upgraded to a higher level than the web server. The Web server and the database must be upgraded to the same version and build level to return to compatibility range&lt;br /&gt;
&lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;/tr&gt;
&lt;/tbody&gt;   &lt;/table&gt;&lt;br /&gt;
&lt;h4&gt;So, does parallel upgrade sucks?&lt;/h4&gt;&lt;br /&gt;
I was unpleasantly surprised to see random TCP stack errors, database schema upgrade failures appearing on random content databases. I suspected that my fellow "parallel upgrade" was causing all the hassle. I've restored content databases that have experienced failures to the MOSS 2007 version. I rerun upgrade sequentially (only one content database being upgraded on the SharePoint farm) and all errors where gone.&lt;br /&gt;
&lt;br /&gt;
I'm not saying "parallel upgrade" is the ultimate evil. It just need to be handled carefully. Performance of the SQL Server IO subsystem, network latency and memory pressure on front-end, back-end servers needs to be monitored. And if need, hardware or system settings will have to be adjusted.&lt;br /&gt;
&lt;br /&gt;
Or you can just stick to the sequential upgrade to be on the safe side during the "migration weekend". At least this will not increase the risk getting yourself into trouble you won't have time to solve.&lt;br /&gt;
&lt;br /&gt;
Hope this helps.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-7008258366288133067?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/xuaONeb5NgU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/7008258366288133067/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/08/why-sharepoint-2010-parallel-content.html#comment-form" title="Komentarze (0)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/7008258366288133067?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/7008258366288133067?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/xuaONeb5NgU/why-sharepoint-2010-parallel-content.html" title="Why SharePoint 2010 parallel content database upgrade can make your life difficult?" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-WtFslepYq_A/TkPl1QDJfeI/AAAAAAAAJKA/shsMdoxC0MU/s72-c/SharePointMigration.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/08/why-sharepoint-2010-parallel-content.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0UESX4-fCp7ImA9WhdRFUo.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-3721408699058460934</id><published>2011-08-05T22:00:00.013+02:00</published><updated>2011-08-05T22:13:28.054+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-08-05T22:13:28.054+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="WPF" /><title>BackgroundWorker kontra Dispatcher</title><content type="html">&lt;h4&gt;Co robić gdy użytkownik jest niecierpliwy?&lt;/h4&gt;&lt;br /&gt;
Pracując nad nową wersją &lt;a href="http://replicationexplorer.codeplex.com/"&gt;Replication Explorer&lt;/a&gt;'a postanowiłem ulepszyć UX (User Expirience) dialogu do łączenia się z serwerem (Dystrybutorem replikacji). W aktualnej wersji programu, w momencie gdy użytkownik wciśnie przycisk 'Connect' cały UI przestaje odpowiadać do momentu połączenia z Dystrybutorem. Nie można anulować akcji łączenia, trzeba czekać 30 sekund żeby komunikat o niemożności zlokalizowania serwera został wyświetlony itp. &lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-XeKjIVA6Az8/TjuWW4stloI/AAAAAAAAJJA/QDPC0r60lZs/s1600/RepExplorerHang.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="291" src="http://3.bp.blogspot.com/-XeKjIVA6Az8/TjuWW4stloI/AAAAAAAAJJA/QDPC0r60lZs/s320/RepExplorerHang.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-XAgks2PKrbU/TjmnPDBt5sI/AAAAAAAAJI8/avHLSzYfhF0/s1600/ReplicationExplorerFreeze.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;br /&gt;
&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Rozwiązanie problemu "wiszącego" dialogu musi spełniać następujące warunki:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;asynchroniczna operacja&amp;nbsp; w prosty sposób musi zwrócić dane do głównego okna programu&lt;/li&gt;
&lt;li&gt;przerwanie operacji połączenia powinno być możliwe w każdym momencie,&amp;nbsp;&lt;/li&gt;
&lt;li&gt;na czas wykonywania operacji połączenia z serwerem, UI powinno być dalej gotowe na interakcję z użytkownikiem,&amp;nbsp;&lt;/li&gt;
&lt;li&gt;w czasie ustanawiania połączenia z Dystrybutorem, UI powinno wyświetlać prostą animację sugerującą, że akcja łącznia trwa,&lt;/li&gt;
&lt;li&gt;operacja połączenia w prosty sposób musi przekazać informację o ewentualnym błędzie, &amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;AsyncDialog - najprościej wyjaśnić na przykładzie&lt;/h4&gt;W ramach testów różnych rozwiązań zaimplemntowałem przykładową aplikację AsyncDialog (źródła dostępne są &lt;a href="https://sites.google.com/site/myblogsqlsever/AsyncDialog.zip"&gt;&lt;span style="background-color: white;"&gt;tutaj&lt;/span&gt;&lt;/a&gt;). AsyncDialog pobiera informacje o wersji buildu SQL Servera i wyświetla je w głównym oknie. &lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-IOgfoDLeOVU/TjxC7YSh5HI/AAAAAAAAJJE/7JSOQNCIwIw/s1600/AsyncDialog.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-IOgfoDLeOVU/TjxC7YSh5HI/AAAAAAAAJJE/7JSOQNCIwIw/s1600/AsyncDialog.PNG" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
W oknie ConnectDialog użytkownik moze pobierać informacje o SQL Serverze za pomocą czterech mechanizmów.&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;&lt;b&gt;Dispatcher On Complete&lt;/b&gt; -wykorzystuje &lt;a href="http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.aspx"&gt;Dispatcher&lt;/a&gt;'a i właściwości klasy &lt;a href="http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcheroperation.aspx"&gt;DispatcherOperation&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Simple Dispatcher&lt;/b&gt; - prezentuje najprostsze wywołanie &lt;a href="http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.aspx"&gt;Dispatcher&lt;/a&gt;'a.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Background worker&lt;/b&gt; - używa klasy &lt;a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx"&gt;BackgroundWorker&lt;/a&gt;, która umożliwia uruchomienie animacji w czasie połączenia&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Thread Pool&lt;/b&gt; - do wykonania asynchronicznej operacji wykorzystuje metodę &lt;br /&gt;
&lt;a href="http://msdn.microsoft.com/en-us/library/4yd16hza.aspx"&gt;ThreadPool.QueueUserWorkItem&lt;/a&gt;.&lt;br /&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;br /&gt;
Poniżej klasy pomocnicze wykorzystywane przez AsyncDialog do pobrania danych z instacji MS SQL Servera. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;public class SqlServerMetaMan
{
    private readonly string _serverName;

    public SqlServerMetaMan(string serverName)
    {
        _serverName = serverName;
    }

    public ServerInfo GetSqlServerVersion()
    {
        var connectionStringBuilder = new SqlConnectionStringBuilder();
        connectionStringBuilder.InitialCatalog = "master";
        connectionStringBuilder.DataSource = _serverName;
        connectionStringBuilder.IntegratedSecurity = true;
        using(var connection = new SqlConnection(connectionStringBuilder.ConnectionString))
        {
            connection.Open();
            return new ServerInfo{Name = connection.DataSource
              , Version = connection.ServerVersion};
        }
    }
}

public class ServerInfo
{
    public string Name { get; set; }
    public string Version { get; set; }
}

&lt;/pre&gt;&lt;br /&gt;
Poniżej przykładowa animacja obrazka przedstawiającego ster, która wykonuje obrót obrazka o 360 stopni. Kod źródłowy był inspirowany artykułem &lt;a href="http://www.codeproject.com/KB/WPF/WPFAnimation.aspx"&gt;'Beginner's WPF Animation Tutorial'&lt;/a&gt;.&amp;nbsp; Animacja jest uruchamiana przed nawiązaniem połączenia do serwera i jest zatrzymywana po otrzymaniu danych lub anulowaniu akcji. &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;private void StartAnimation()
{
    TimeSpan animationDuration = TimeSpan.FromSeconds(5);
    DoubleAnimation animation = new DoubleAnimation(360, 0, new Duration(animationDuration));
    RotateTransform rotateTransform = new RotateTransform();
    progressIcon.RenderTransform = rotateTransform;
    progressIcon.RenderTransformOrigin = new Point(0.5, 0.5);
    animation.RepeatBehavior = RepeatBehavior.Forever;
    rotateTransform.BeginAnimation(RotateTransform.AngleProperty, animation);
}

&lt;/pre&gt;&lt;br /&gt;
&lt;h4&gt;Asynchronicznie do dzieła&lt;/h4&gt;&lt;br /&gt;
Istnieje co najmniej kilka opcji do uruchamiania operacji asynchronicznych na WPF. Jako że nie miałem z nimi żadnego szczególnego doświadczenia, zacząłem od &lt;a href="http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.aspx"&gt;Dispatcher&lt;/a&gt;'a, który sprawiał wrażenie najprostszego w implementacji. Jest to klasa, która jest bazową dla praktycznie wszystkich obiektów wizulanych WPF'a, aczkolwiek instacja Dispatcher'a jest tylko jedna (singleton). Z braku podstawowej wiedzy o WPF'ie oczekiwałem, że jakikolwiek kod uruchomiony za pomocą Dispatcher'a uruchomi się na oddzielnym wątku. Tak oczywiście nie jest, ponieważ delegaty przekazywane do metod &lt;a href="http://msdn.microsoft.com/en-us/library/ms591593.aspx"&gt;Invoke&lt;/a&gt; i &lt;a href="http://msdn.microsoft.com/en-us/library/ms591206.aspx"&gt;BeginInvoke&lt;/a&gt; są kolejkowane i uruchamiane w kolejności zgodnej z podanym &lt;a href="http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcherpriority.aspx"&gt;priorytetem&lt;/a&gt; przez jeden-jedyny wątek UI (architektura WPF jest też oparta na STA czy Single-Threaded Apartment, tak samo jak Win32 i Windows Forms). Poniżej przykładowy kod wywołania metody Dispatcher.BeginInvoke.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;private void CallUsingDispatcher()
{
    Dispatcher.BeginInvoke(
        new Action(DispatcherDisplayServerInfo),
        DispatcherPriority.Normal);
}

private void DispatcherDisplayServerInfo()
{
    ServerInfo serverInfo = GetSqlServerVersion(tbServerName.Text);
    OwnerWindow.DataContext = serverInfo;
    this.Close();
}&amp;nbsp;&lt;/pre&gt;&lt;br /&gt;
W momencie wywołania metody GetSqlServerVersion i próby połączenia do SQL Servera całe UI "zastygnie" w oczekiwaniu na ustanowienie komunikacji z serwerem. Jest to oczywiście zachowanie niepożądane i dlatego w moim senariuszu Dispatcher nie może być brany pod uwagę.&amp;nbsp;  &lt;br /&gt;
&lt;br /&gt;
Niespełnione warunki:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;nie można anulować operacji w czasie jej wykonywania,&lt;/li&gt;
&lt;li&gt;UI przestaje odpowiadać na czas trwania operacji połączenia, &lt;/li&gt;
&lt;li&gt;nie można obsłużyć animacji w czasie wykonania operacji.&lt;/li&gt;
&lt;/ul&gt;&lt;h4&gt;Elastyczny BackgroundWorker&lt;/h4&gt;&lt;br /&gt;
Nie tracąc nadziei sprawdziłem następną klasę o nazwie &lt;a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx"&gt;BackgroundWorker&lt;/a&gt;. Umożliwia ona asynchroniczne uruchamianie operacji na odzielnym wątku, anulowanie akcji (właściwość &lt;a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.workersupportscancellation.aspx"&gt;WorkerSupportsCancellation&lt;/a&gt;), przekazanie informacji o błędzie oraz przekazywanie informacji o postępie prac. Aczkolwiek, &lt;i&gt;nie wszystko złoto co się świeci&lt;/i&gt;,&amp;nbsp; interakcję z WPF'owym UI możemy uzyskać poprzez obiekt wyżej wspomnianego &lt;br /&gt;
Dispatcher'a. Inaczej otrzymamy następujący komunikat:&lt;br /&gt;
&lt;br /&gt;
&lt;div style="color: red;"&gt;InvalidOperationException : The calling thread cannot access this object because a different thread owns it.&lt;/div&gt;&lt;br /&gt;
Poniżej fragmenty implementacji scenariusza wykorzystującego klasę BackgroundWorker'a.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;private void CallUsingBackgroundWorker()
{
    worker = new BackgroundWorker();
    worker.WorkerSupportsCancellation = true;
    worker.DoWork += WorkerDoWork;
    worker.RunWorkerCompleted += WorkerRunCompleted;
    worker.RunWorkerAsync(tbServerName.Text);
}
        
private void WorkerDoWork(object sender, DoWorkEventArgs e)
{
    string serverName = (string) e.Argument;
    BackgroundWorker backgroundWorker = (BackgroundWorker) sender;
    try
    {
        ServerInfo serverInfo = GetSqlServerVersion(serverName);
        e.Result = serverInfo;
        Dispatcher.BeginInvoke(new Action(() =&amp;gt;
            {
                OwnerWindow.Title += "Background Worker update";
            }), DispatcherPriority.Normal
            );

                
    }
    catch (Exception)
    {
        if (backgroundWorker.CancellationPending)
        {
            //Suppress exception if user submitted cancellation
            e.Cancel = true;
        }
        else
        {
            throw;    
        }
                
    }
}

private void WorkerRunCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    StopAnimation();
    if(e.Error != null &amp;amp;&amp;amp; !e.Cancelled)
    {
        MessageBox.Show(e.Error.Message, "Error occured");
    }
    else if(e.Cancelled)
    {
        MessageBox.Show("Connection attempt cancelled by the user", "User action");
    }
    else if (e.Result != null)
    {
        OwnerWindow.DataContext = e.Result as ServerInfo;
        this.Close();
    }
}

&lt;/pre&gt;&lt;br /&gt;
&lt;h4&gt;ThreadPool do pomocy&lt;/h4&gt;&lt;br /&gt;
Z czystej ciekawości postanowiłem sprawdzić standardowe klasy wykorzystywane w .NET-towych aplikacjach wielowątkowych. Oczywiście, stara dobra, metdoa &lt;a href="http://msdn.microsoft.com/en-us/library/4yd16hza.aspx"&gt;ThreadPool.QueueUserWorkItem&lt;/a&gt; spisała się dobrze. Nie zablokowała wątku UI, pozwoliła przekazać parametr z UI (nazwę serwera), aczkolwiek z przekazywaniem informacji o błędach było już gorzej.&amp;nbsp; &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;private void CallUsingThreadPool()
{
    ThreadPool.QueueUserWorkItem(UserWorkItemCallback, 
        tbServerName.Text);
}

private void UserWorkItemCallback(object state)
{
    string serverName = (string) state;
    ServerInfo serverInfo = GetSqlServerVersion(serverName);

    Dispatcher.BeginInvoke(new Action(() =&amp;gt;
    {
        this.StopAnimation();
        OwnerWindow.Title += "UserWorkItem Callback update";
        OwnerWindow.DataContext = serverInfo;
        this.Close();
    }), DispatcherPriority.Normal);
}

&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
Niespełnione warunki:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;nie można w prosty sposób anulować operacji&lt;/li&gt;
&lt;li&gt;nie można przekazać informacji o błędach które wystąpiły podczas wykonania operacji połączenia&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;h4&gt;And the winner is&lt;/h4&gt;&lt;br /&gt;
Rozwiązanie, które wybrałem do implementacji dialogu połącznia opiera się na wykorzystaniu klasy &lt;a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx"&gt;BackgroundWorker&lt;/a&gt;. Dostarcza ona proste mechanizmy do obsługi przerywania akcji, przekazywania błędów i wyników do interfejsu użytkownika.&lt;br /&gt;
&lt;br /&gt;
Hope this helps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-3721408699058460934?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/wXek6i5UNtM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/3721408699058460934/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/08/backgroundworker-kontra-dispatcher.html#comment-form" title="Komentarze (3)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/3721408699058460934?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/3721408699058460934?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/wXek6i5UNtM/backgroundworker-kontra-dispatcher.html" title="BackgroundWorker kontra Dispatcher" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-XeKjIVA6Az8/TjuWW4stloI/AAAAAAAAJJA/QDPC0r60lZs/s72-c/RepExplorerHang.png" height="72" width="72" /><thr:total>3</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/08/backgroundworker-kontra-dispatcher.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEMDRXo4cCp7ImA9WhdRFUo.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-93102234036429866</id><published>2011-07-27T22:25:00.002+02:00</published><updated>2011-08-05T22:34:34.438+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-08-05T22:34:34.438+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term=".NET" /><category scheme="http://www.blogger.com/atom/ns#" term="Windows" /><title>Programista i jego Troubleshooting Toolkit (Część II) - .NET</title><content type="html">&lt;h4&gt;Rozwiązywanie problemów na platformie .NET&lt;/h4&gt;&lt;br /&gt;
W kolejnym poście skupię się na temacie rozwiązywania problemów na platformie .NET z którymi borykają się programiści. Jest to zagadnienie bliskie memu sercu, ponieważ jako etatowy "detektyw .NET" wielokrotnie byłem zmuszony do korzystania z większości z tych narzędzi, szczególnie gdy zawiodło mnie bezmyślne wyszukanie w &lt;a href="http://googleitfor.me/"&gt;Google&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;support.microsoft.com - powrót do korzeni&lt;/h4&gt;&lt;br /&gt;
&lt;a href="http://support.microsoft.com/"&gt;http://support.microsoft.com/&lt;/a&gt; - postanowiłem zacząć od tej mało popularnej wśród programistów strony, na której Microsoft publikuje poprawki, artykuły techniczne oraz wszelakie rozwiązania problemów. Ta strona może być o tyle istotna w procesie poszukiwania rozwiązania, że &lt;a href="http://support.microsoft.com/"&gt;support.microsoft.com&lt;/a&gt; zawiera rozwiązania i informacje które zostały już wielokrotnie sprawdzone przez inżynierów Microsoft w akcji. &lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-x6ef5_cCbzI/Ti2eFnBm_YI/AAAAAAAAJD4/EPqMMueIDE4/s1600/SupprotMicrosoftCom.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="318" src="http://4.bp.blogspot.com/-x6ef5_cCbzI/Ti2eFnBm_YI/AAAAAAAAJD4/EPqMMueIDE4/s640/SupprotMicrosoftCom.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
Artykuły w Microsoft'owym KB są wielokrotnie zatwierdzane i sprawdzane  zanim ujrzą światło dzienne, tym zasadniczo różnią się od blogów,  artykułów i wypowiedzi na forach etc. W moim odczuciu wyszukiwarki nie zawsze pozycjonują wysoko strony z rozwiązaniami z &lt;a href="http://support.microsoft.com/"&gt;support.microsoft.com&lt;/a&gt;, dlatego zawsze gdy rozwiązanie nie jest oczywiste warto sprawdzić to u ŹRÓDŁA.&amp;nbsp; Można też pójść w kierunku bardziej ortodoksyjnym i założyć konto na &lt;a href="http://kbalertz.com/"&gt;kbalertz.com&lt;/a&gt;. Usługa ta umożliwia otrzymywanie powiadomień o świeżo opublikowanych artykułach dla technologii, które wybierzemy w swoim profilu (np. &lt;span style="color: black;"&gt;.NET Framework, &lt;/span&gt;&lt;span style="color: black;"&gt;SQL Server 2008 R2&lt;/span&gt;&lt;span style="color: black;"&gt; etc)&lt;/span&gt;. &lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Fusion Log Viewer - assembly binding&lt;/h4&gt;&lt;br /&gt;
&lt;a href="http://msdn.microsoft.com/en-us/library/e74a18c4%28v=VS.100%29.aspx"&gt;Fusion Log Viewer&lt;/a&gt; (FUSLOGVW) - jest częścią Windows SDK i jest to bardzo użyteczne narzędzie gdy nie jesteśmy w stanie ustalić jakich assembly program nie może właściwie załadować i skąd je próbuje pobrać. Domyślnie Fusion Log Viewer loguje nieudane ładowania assembly. Aczkolwiek po zmianach w rejestrze poprzez dodanie &lt;b&gt;HKLM\Software\Microsoft\Fusion\ForceLog&lt;/b&gt; i ustawieniu wartości na 1, otrzymamy pełny log udanych i nieudanych tzw. assembly bindings.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-bxz9Dm_PsIQ/Ti3F7q2kmNI/AAAAAAAAJD8/9ky_4SRBaEI/s1600/FusionLogViewerRegistry.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="406" src="http://4.bp.blogspot.com/-bxz9Dm_PsIQ/Ti3F7q2kmNI/AAAAAAAAJD8/9ky_4SRBaEI/s640/FusionLogViewerRegistry.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Po zmianach w rejestrze możemy śledzić próby załadowania assembly przez aplikacje uruchamiane na .NET CLR. Poniżej prezentuję prostą aplikację, która próbuje wczytać assembly o nieistniejącej nazwie.  &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:csharp"&gt;using System.Reflection;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            AssemblyName assemblyName = new AssemblyName();
            assemblyName.Name = "FakeAssemblyName";
            Assembly loadedAssembly = Assembly.Load(assemblyName);
        }
    }
}
&lt;/pre&gt;&lt;br /&gt;
Log Fusion viewer wyświetli nieudaną próbę w następujący sposób. Można przejrzeć plik logu dla wiersza oznaczonego FakeAssemblyName (patrz poniżej).&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-O0AKKZExEZQ/Ti3KPSSk_7I/AAAAAAAAJEA/hoIrVzkWoj0/s1600/FusionLogViewer.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="321" src="http://2.bp.blogspot.com/-O0AKKZExEZQ/Ti3KPSSk_7I/AAAAAAAAJEA/hoIrVzkWoj0/s640/FusionLogViewer.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Tesktowy log dokładnie prezentuje, w jakich katalogach assembly resolver poszukuje nieszczęsnego FakeAssemblyName.dll.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:xml"&gt;*** Assembly Binder Log Entry  (2011-07-25 @ 19:23:19) ***

The operation failed.
Bind result: hr = 0x80070002. The system cannot find the file specified.

Assembly manager loaded from:  C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
Running under executable  c:\users\...\ConsoleApplication1\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe
--- A detailed error log follows. 

=== Pre-bind state information ===
LOG: User = MaxPC\Max
LOG: DisplayName = FakeAssemblyName
 (Partial)
WRN: Partial binding information was supplied for an assembly:
WRN: Assembly Name: FakeAssemblyName | Domain ID: 1
WRN: A partial bind occurs when only part of the assembly display name is provided.
WRN: This might result in the binder loading an incorrect assembly.
WRN: It is recommended to provide a fully specified textual identity for the assembly,
WRN: that consists of the simple name, version, culture, and public key token.
WRN: See whitepaper http://go.microsoft.com/fwlink/?LinkId=109270 for more information and common solutions to this issue.
LOG: Appbase = file:///c:/users/.../ConsoleApplication1/ConsoleApplication1/bin/Debug/
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = NULL
LOG: AppName = ConsoleApplication1.exe
Calling assembly : ConsoleApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.
===
LOG: This bind starts in default load context.
LOG: No application configuration file found.
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
LOG: Attempting download of new URL file:///c:/users/.../ConsoleApplication1/ConsoleApplication1/bin/Debug/FakeAssemblyName.DLL.
LOG: Attempting download of new URL file:///c:/users/.../ConsoleApplication1/ConsoleApplication1/bin/Debug/FakeAssemblyName/FakeAssemblyName.DLL.
LOG: Attempting download of new URL file:///c:/users/.../ConsoleApplication1/ConsoleApplication1/bin/Debug/FakeAssemblyName.EXE.
LOG: Attempting download of new URL file:///c:/users/.../ConsoleApplication1/ConsoleApplication1/bin/Debug/FakeAssemblyName/FakeAssemblyName.EXE.
LOG: All probing URLs attempted and failed.
&lt;/pre&gt;&lt;br /&gt;
Jak widać Log Fusion Viewer to ciekawe narzędzie, które może pomóc w rozwikłaniu skomplikowanych problemów z ładowaniem assembly.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;IL Diassembler - metadata&lt;/h4&gt;&lt;br /&gt;
&lt;a href="http://msdn.microsoft.com/en-us/library/f7dy01k1%28v=VS.100%29.aspx"&gt;IL Diassembler ( ILDASM)&lt;/a&gt; - jest to program dystrybuowany razem z Windows SDK lub z Visual Studio. Umożliwia on zapisanie lub przeglądanie kodu IL (Intermediate Language) dla danego assembly (dll lub exe). Częściowo jego funkcjonalność pokrywa się z &lt;a href="http://wiki.sharpdevelop.net/ILSpy.ashx"&gt;ILSpy&lt;/a&gt;'em o którym piszę poniżej, jego przewagą w pracy z IL'em może być interfejs konsolowy oraz bardziej wyrafinowany sposób eksportu IL'a. Opcją którą zdarzyło mi się używać było przeglądanie Manifestu assembly, który zawiera m.in referencje do innych assembly, klucz publiczny, ustawienia corflags dotyczące architektury procesora który może wykonywać daną aplikację (w Visual Studio możemy ustawić podczas kompilacji x64, x86, any CPU).&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-2b4zOwu5RXc/Ti8T00lGLVI/AAAAAAAAJEk/Y3t0uGDfkPU/s1600/ILDASM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-2b4zOwu5RXc/Ti8T00lGLVI/AAAAAAAAJEk/Y3t0uGDfkPU/s1600/ILDASM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-KhKsXI_d13o/TiQv9to1joI/AAAAAAAAJC0/Pj3m8YksZP8/s1600/Ball-go-32.png" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-KhKsXI_d13o/TiQv9to1joI/AAAAAAAAJC0/Pj3m8YksZP8/s1600/Ball-go-32.png" /&gt;&lt;/a&gt;&lt;/div&gt;Lista referencji do zewnętrznych assembly z ich tokenami klucza publicznego.&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;ILSpy - dekompilacja&lt;/h4&gt;&lt;br /&gt;
&lt;a href="http://wiki.sharpdevelop.net/ILSpy.ashx"&gt;ILSpy&lt;/a&gt; - to darmowy odpowiednik .NET Reflector'a. Jest on oczywiście bardziej wyrafinowanym dekompilatorem niż IL Diassembler. Niestety nie ma takiej ilości pluginów jak &lt;a href="http://reflectoraddins.codeplex.com/"&gt;.NET Reflector&lt;/a&gt;, jest też trochę wolniejszy od swojego płatnego odpowiednika, nie integruje się z Visual Studio i nie daje możliwości debuggowania dekompilowanego assembly. Aczkolwiek parafrazując staropolskie przysłowie, darowanemu programowi się w pluginy nie zagląda. Dekompilatory dobrze wpisują się w scenariusz gdy musimy rozwijać system napisany przez zewnętrznego dostawcę (np. SharePoint, Microsoft CRM), ale nie mamy dostępu do kodu źródłowego. Mamy szczęście w momencie gdy dostawca nie użył &lt;a href="http://en.wikipedia.org/wiki/Obfuscated_code"&gt;obfuskatora&lt;/a&gt;, gdyż wtedy żaden dekompilator nam nie pomoże. ILSpy umożliwia konwersję kodu &lt;a href="http://en.wikipedia.org/wiki/Common_Intermediate_Language"&gt;CIL&lt;/a&gt; (Common Intermediate Language) do kodu C#, pozwala także podejrzeć kod IL.&amp;nbsp; &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-2qamA2Bpd1k/Ti5akf6m-zI/AAAAAAAAJEI/cYomjpHWPhg/s1600/ILSpy.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="402" src="http://4.bp.blogspot.com/-2qamA2Bpd1k/Ti5akf6m-zI/AAAAAAAAJEI/cYomjpHWPhg/s640/ILSpy.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Inną sytuacją kiedy ILSpy może okazać się przydatny jest weryfikacja, czy kod źródłowy, który mamy w systemie kontroli wersji zgadza się z kodem assembly, które wykonuje się na danym środowisku serwerowym. Niejednokrotnie musiałem dekompilować assembly na jakimś serwerze, aby tylko sprawdzić czy przypadkiem ktoś nie wrzucił starej/swojej/nowszej wersji danego assembly. W końcu, ILSpy daje nam takie możliwość weryfikacji kodu źródłowego, więc dlaczego z tego nie skorzystać. Alternatywą dla ILSpy'a może być &lt;a href="http://www.telerik.com/products/decompiling.aspx"&gt;JustDecompile&lt;/a&gt;, aczkolwiek ostrzegam, ostatnia beta którą uruchomiłem, rzuciła przy starcie pięknym czerwono-białym wyjątkiem i na tym się skończyło. &lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-KhKsXI_d13o/TiQv9to1joI/AAAAAAAAJC0/Pj3m8YksZP8/s1600/Ball-go-32.png" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-KhKsXI_d13o/TiQv9to1joI/AAAAAAAAJC0/Pj3m8YksZP8/s1600/Ball-go-32.png" /&gt;&lt;/a&gt;&lt;/div&gt;Możliwość załadowania i dekompilacji assembly bezpośrednio z GAC'a.&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;.NET Memory Profiler - CPU i pamięć&lt;/h4&gt;&lt;br /&gt;
&lt;a href="http://memprofiler.com/"&gt;MemProfiler &lt;/a&gt;- staram się opisywać narzędzia, które są darmowe i ogólnodostępne, aczkolwiek dla MemProfiler'a postanowiłem zrobić wyjątek. Jest to rozbudowany profiler dla aplikacji opartych o platformę .NET. Muszę się przyznać, że przynajmniej w dwóch przypadkach wycieków pamięci udało mi się namierzyć problematyczny kod źródłowy tylko dzięki błyskotliwości tego narzędzia. MemProfiler pozwala wykonywać snapshot'y pamięci i później analizować różnice w ilości obiektów zaalokowanych na stercie, instancje obiektów na których nie została wywołana metoda Dispose (Dispose Tracker) itd. Ogólnie polecam zapoznanie się z tym narzędziem, jeżeli zgłosi się do was szef, że proces na serwerze produkcyjnym zajmuje teraz 2GB, a przed releasem zajmował 300 MB ;).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-Ba2yH_AZnz4/TjBkcpynMQI/AAAAAAAAJEo/GUJZfbTfooI/s1600/MemProfilerRealtimeMonitoring.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="403" src="http://2.bp.blogspot.com/-Ba2yH_AZnz4/TjBkcpynMQI/AAAAAAAAJEo/GUJZfbTfooI/s640/MemProfilerRealtimeMonitoring.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;blockquote&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-KhKsXI_d13o/TiQv9to1joI/AAAAAAAAJC0/Pj3m8YksZP8/s1600/Ball-go-32.png" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-KhKsXI_d13o/TiQv9to1joI/AAAAAAAAJC0/Pj3m8YksZP8/s1600/Ball-go-32.png" /&gt;&lt;/a&gt;&lt;/div&gt;Diagram pokazujący ilość zaalokowanej pamięci na stercie .NET i pamięci niezarządzanej (unmanaged memory).&lt;/blockquote&gt;&lt;a href="http://3.bp.blogspot.com/-KhKsXI_d13o/TiQv9to1joI/AAAAAAAAJC0/Pj3m8YksZP8/s1600/Ball-go-32.png" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;/a&gt;&lt;br /&gt;
&lt;h4&gt;&amp;nbsp;&lt;/h4&gt;&lt;h4&gt;Na koniec - zestaw hardkorowca czyli Hang, Crash, Dump&lt;/h4&gt;&lt;br /&gt;
Czasami jest tak, że trzeba wykazać się niestandardowym myśleniem. Szczególnie gdy wszystkie znane metody zawiodły. Przykłady takich przypadków to:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Niespodziewane crush procesu, usługi czy IIS'a (np. w3wp.exe)&lt;/li&gt;
&lt;li&gt;Wieszanie się procesów&lt;/li&gt;
&lt;li&gt;Losowe OutOfMemoryException&lt;/li&gt;
&lt;/ul&gt;W takich momentach należy użyć trochę bardziej wyrafinowanych narzędzi, część z nich wchodzi w skład&amp;nbsp; pakietu &lt;a href="http://msdn.microsoft.com/en-us/windows/hardware/gg463009"&gt;Debugging Tools for Windows&lt;/a&gt;. Możemy rozpocząć z &lt;a href="http://www.microsoft.com/download/en/details.aspx?id=24370"&gt;Debug Diagnostic Tool v1.1&lt;/a&gt; i spróbować przeanalizować działanie naszej aplikacji pod kątem jednego z kilku możliwych problemów (Crash, Memory Leak lub IIS Hang). W końcu dostępna już jest wersja tego narzędzia dla systemów 64 bitowych. Jeżeli nasza analiza nie przyniesie oczekiwanych rezultatów, proponuje pójść ścieżką dokładnie opisaną przez &lt;a href="http://blogs.msdn.com/b/tess/"&gt;Tess Ferrandez&lt;/a&gt; w swojej serii postów &lt;a href="http://blogs.msdn.com/b/tess/archive/2008/02/04/net-debugging-demos-information-and-setup-instructions.aspx"&gt;.NET Debugging Demos&lt;/a&gt;.&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;Podstawa to WinDbg (dostępny w ramach&amp;nbsp; &lt;a href="http://msdn.microsoft.com/en-us/windows/hardware/gg463009"&gt;Debugging Tools for Windows&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://support.microsoft.com/default.aspx?scid=kb;en-us;286350"&gt;Adplus&lt;/a&gt; - skrypt napisany w vbscript (dostępny w ramach&amp;nbsp; &lt;a href="http://msdn.microsoft.com/en-us/windows/hardware/gg463009"&gt;Debugging Tools for Windows&lt;/a&gt;), przydatny przy generowania różnego rodzajów zrzutów pamięci (memory dump) dla danego procesu.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/bb190764.aspx"&gt;SOS &lt;/a&gt;(Sun of strike) - rozszerzenie do debuggowania .NET-towych aplikacji, dla .NET 4 dostępny w %systemDirve%\Microsoft.NET\Framework\v4.0.30319\SOS.dll&lt;/li&gt;
&lt;/ol&gt;&lt;br /&gt;
Poza tym, lekcje debuggowania u Tess Fernandez to sama przyjemność i satysfakcja z dłubania na poziomie adresów w pamięci. Polecam. &lt;br /&gt;
&lt;br /&gt;
Kilka linków do aplikacji uzupełniających tą listę&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/z9z62c29%28v=VS.100%29.aspx"&gt;Visual Studio Performance Tools&lt;/a&gt; - niestety nie wszyscy mogą sobie pozwolić na Visual Studio 2010 Premium lub Ultimate, aczkolwiek na pewno warto przyjrzeć się tym narzędziom.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://pal.codeplex.com/"&gt;Performance Analysis of Logs (PAL) Tool&lt;/a&gt; - zainstaluj PAL'a i wymagane dodatkowe oprogramowanie (np. Chart Controls), zmień ustawienia regionalne na United States i bez szczegółowej wiedzy na temat performance counterów zbieraj informacje na temat np. aplikacji ASP.NET lub SQL Server'a i wielu innych produktów Microsoft'u. Na stronie projektu możemy znaleźć kilka przydatnych how-to.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.microsoft.com/download/en/details.aspx?id=16273"&gt;CLR Profiler&lt;/a&gt; - profiler Microsoft'u, trudniejszy w obsłudze niż MemProfiler czy inne komercyjne profilery &lt;a href="http://www.yourkit.com/.net/profiler/index.jsp"&gt;YourKit .NET Profiler&lt;/a&gt; czy &lt;a href="http://www.red-gate.com/products/dotnet-development/ants-performance-profiler/"&gt;Ants .NET Profiler&lt;/a&gt;, aczkolwiek ma jedną niezaprzeczalną zaletę, jest darmowy. &amp;nbsp; &lt;/li&gt;
&lt;/ol&gt;Hope this helps.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-93102234036429866?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/X_eka70GSrE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/93102234036429866/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/07/programista-i-jego-troubleshooting.html#comment-form" title="Komentarze (0)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/93102234036429866?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/93102234036429866?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/X_eka70GSrE/programista-i-jego-troubleshooting.html" title="Programista i jego Troubleshooting Toolkit (Część II) - .NET" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-x6ef5_cCbzI/Ti2eFnBm_YI/AAAAAAAAJD4/EPqMMueIDE4/s72-c/SupprotMicrosoftCom.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/07/programista-i-jego-troubleshooting.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DE8AQH09eyp7ImA9WhdSFUU.&quot;"><id>tag:blogger.com,1999:blog-14793170.post-5397267551819006834</id><published>2011-07-22T19:37:00.004+02:00</published><updated>2011-07-25T11:40:41.363+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-25T11:40:41.363+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Windows" /><title>Nie ignoruj bluescreen'a</title><content type="html">&lt;h4&gt;Analiza crush dump'a - podstway&lt;/h4&gt;&lt;br /&gt;
Niedawno obejrzałem dosyć stary już webcast &lt;a href="https://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032298076&amp;amp;culture=en-us"&gt;Windows Hang and Crash Dump Analysis&lt;/a&gt;, w którym Mark Russinovich (człowiek od &lt;a href="http://technet.microsoft.com/en-us/sysinternals/bb842062"&gt;Sysinternals Suite&lt;/a&gt;) udowodnił mi, że nie trzeba być geniuszem, żeby wykonać analizę crush dump'a (&lt;a href="http://pl.wikipedia.org/wiki/Blue_Screen_of_Death"&gt;bluescreen&lt;/a&gt; przeważnie produkuje memory dumpa). Czytelników spragnionych szczegółów i dokładniejszych wyjaśnień odsyłam do obejrzenia w całości tego ciekawego webcast'u.&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;Bluescreen - zdarzy się, ale kto by się przejmował&lt;/h4&gt;&lt;br /&gt;
Na szczęście na codzień w pracy nie muszę zajmować się środowiskiem serwerowym, zatem większość bluescreen'ów otrzymałem na moich stacjach roboczych (w pracy, w domu). Czasami efektem bluescreen'u może być niespodziewany restart. Niestety nigdy nie wpadłem na pomysł, żeby przeprowadzić bardziej wnikliwą analizę tego zjawiska.  Podczas crash'u memory dump najpierw zapisywany jest do page file'a, a później generowany jest memory dump (mini-dumpy są zawsze generowany z datą w nazwie pliku, u mnie&amp;nbsp; lądują w folderze C:\Windows\Minidump\ ). W Windows 7 Microsoft zostawił tylko możliwość generowania zrzutów Kernel-memory lub Small memory dump, w sumie zrzut 8GB pamięci na dysk trochę by musiał trwać i wymagałby dodatkowego miejsca na dysku. (Aby dostać się do ustawień rodzaju zrzutu pamięci wybierz prawym Computer później Properties -&amp;gt; Advanced System settings -&amp;gt; Zakładka Advanced -&amp;gt;Wybierz dla Startup and Recovery przycisk Settings. ).&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-_2ttCDPpZ1I/TikwOI-9B3I/AAAAAAAAJDY/ICCGE6SRcZY/s1600/MemoryDumpType.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-_2ttCDPpZ1I/TikwOI-9B3I/AAAAAAAAJDY/ICCGE6SRcZY/s1600/MemoryDumpType.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Jeżeli mamy do dyspozycji Wirtualną Maszynę to można sobie wygenerować bluescreena narzędziem &lt;a href="http://download.sysinternals.com/Files/Notmyfault.zip"&gt;NotMyFault&lt;/a&gt; (autor oczywiście &amp;nbsp; &lt;a href="http://technet.microsoft.com/en-us/sysinternals/bb963901"&gt;Mark Russinovich&lt;/a&gt;). Na własne ryzyko, zabawa uszkodzonym driverem &amp;nbsp;zawsze wiąże się z możliwością uszkodzenia np. danych. Przykład wygenerowania bluescreena na Windows Server 2008 R2 zamieszczam poniżej na potrzeby demo. Widać że na załączonym obrazku, że system jeszcze generuje crash-dumpa dla kernel-memory (zajmuję to trochę czasu bo to nie jest mini-dump i 256kb).&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-ejxBrEzr-4o/TilRISqd-JI/AAAAAAAAJDk/dUldq3ccO_I/s1600/Bluescreen.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="480" src="http://1.bp.blogspot.com/-ejxBrEzr-4o/TilRISqd-JI/AAAAAAAAJDk/dUldq3ccO_I/s640/Bluescreen.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
Jeżeli nie zdąrzyliśmy zarejestrować nazwy problematycznego drivera z bluescreen'a (jeżeli jest dostępny w używanej przez nas wersji Windows) to polecam&amp;nbsp; sciągnięcie &lt;a href="http://msdn.microsoft.com/en-us/windows/hardware/gg463009"&gt;Debugging Tools for Windows&lt;/a&gt; i odpalenie WinDbg. Zanim zaczenie się analizę memory dump'a warto ustawić ścieżkę do &lt;a href="http://support.microsoft.com/kb/311503"&gt;Microsoft Symbols Server&lt;/a&gt; aby pobrać pliki symboli pdb dla systemu Windows. Jeżeli lokalnie będziemy składować pliki w katalogu c:\symbols to dialog File-&amp;gt;Symbol File Path (Ctrl+S) powinien wyglądać następująco.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-_9qA0O7mwQU/TilW8HOSpcI/AAAAAAAAJDo/i0LbS_s7BCk/s1600/SymbolServerSetup.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="234" src="http://2.bp.blogspot.com/-_9qA0O7mwQU/TilW8HOSpcI/AAAAAAAAJDo/i0LbS_s7BCk/s640/SymbolServerSetup.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-xtwRAVeSKUA/Tik3NmKtqNI/AAAAAAAAJDg/z5hLdmKR1GU/s1600/SymbolServerSetup.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;br /&gt;
&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;pre&gt;SRV*c:\symbols*http://msdl.microsoft.com/download/symbols
&lt;/pre&gt;&lt;br /&gt;
Po uporaniu się z symbolami, możemy załadować memory dump'a (lub mini-dump'a) i "zabrać się" do analizy.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-i6YRyBTE8tM/Tik1y1YvsNI/AAAAAAAAJDc/MTnWmhkuQR0/s1600/WinDbgOpenCrashDump.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="466" src="http://2.bp.blogspot.com/-i6YRyBTE8tM/Tik1y1YvsNI/AAAAAAAAJDc/MTnWmhkuQR0/s640/WinDbgOpenCrashDump.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Po załadowaniu pliku i uruchomienieniu w WinDbg poniżeszj komendy powoduje dostarczenie bardziej dokładnych informacji o driverze który spowodował całe zamieszanie.&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;!analyze -v
&lt;/pre&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-33hkedss_Gw/TilYWq8Wy5I/AAAAAAAAJDs/3yaOprLSKzo/s1600/DumpAnalize.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;br /&gt;
&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-NhGybmh4ieM/TilYwavafaI/AAAAAAAAJDw/3tl14TLz0tY/s1600/DumpAnalize.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="412" src="http://2.bp.blogspot.com/-NhGybmh4ieM/TilYwavafaI/AAAAAAAAJDw/3tl14TLz0tY/s640/DumpAnalize.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Na powyższym obrazku widać problematyczny driver o nazwie myfault.sys. Można oczywiście uniknąć uruchamiania WinDbg i skorzystać ze strony &lt;a href="http://www.osronline.com/page.cfm?name=analyze"&gt;Instant Online Crash Analysis&lt;/a&gt; i wrzucić swój mini-dump do analizy tego narzędzia.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-aJPesl-IQgI/TilaXyLwA7I/AAAAAAAAJD0/Jq9kiKehv0U/s1600/OnlineReport.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="248" src="http://4.bp.blogspot.com/-aJPesl-IQgI/TilaXyLwA7I/AAAAAAAAJD0/Jq9kiKehv0U/s640/OnlineReport.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;Po zlokalizowaniu drivera (myfault.sys) trzeba niestety użyć Google'a aby wybadać co z tym fantem zrobić i określić przez które oprogramowanie z naszego systemu jest używany (u mnie kiedyś szwankował 3rd party firewall).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/14793170-5397267551819006834?l=mmulawa.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/BlogMaksymilianaMulawy/~4/JQOyncjNYXo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://mmulawa.blogspot.com/feeds/5397267551819006834/comments/default" title="Komentarze do posta" /><link rel="replies" type="text/html" href="http://mmulawa.blogspot.com/2011/07/nie-ignoruj-bluescreena.html#comment-form" title="Komentarze (2)" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/5397267551819006834?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/14793170/posts/default/5397267551819006834?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/BlogMaksymilianaMulawy/~3/JQOyncjNYXo/nie-ignoruj-bluescreena.html" title="Nie ignoruj bluescreen'a" /><author><name>Maks Mulawa</name><uri>http://www.blogger.com/profile/05496192118119860493</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="28" height="32" src="http://3.bp.blogspot.com/-KzEveAJ-530/ThygQcc3cyI/AAAAAAAAJB8/Bdtl9cOuT8k/s220/MaxMulawa.PNG" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-_2ttCDPpZ1I/TikwOI-9B3I/AAAAAAAAJDY/ICCGE6SRcZY/s72-c/MemoryDumpType.png" height="72" width="72" /><thr:total>2</thr:total><feedburner:origLink>http://mmulawa.blogspot.com/2011/07/nie-ignoruj-bluescreena.html</feedburner:origLink></entry></feed>

