<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Kursdotnet.pl - Wiedza o programowaniu .NET, Azure i AI</title>
    <link>https://kursdotnet.pl</link>
    <description>Kursdotnet.pl to blog poświęcony programowaniu w .NET, technologii Azure oraz sztucznej inteligencji. Publikujemy analizy, artykuły i treści praktyczne dla programistów.</description>
    <language>pl</language>
    <pubDate>Tue, 15 Sep 2026 19:09:00 +0200</pubDate>
    <lastBuildDate>Tue, 15 Sep 2026 19:09:00 +0200</lastBuildDate>
    <item>
      <title>var w C# bez tajemnic - kiedy używać, a kiedy nie?</title>
      <link>https://kursdotnet.pl/var-w-c-bez-tajemnic-kiedy-uzywac-a-kiedy-nie</link>
      <description>Var w C# to typowanie statyczne, nie dynamiczne. Sprawdź, kiedy poprawia czytelność kodu, a kiedy lepiej użyć jawnego typu.</description>
      <content:encoded><![CDATA[<?xml encoding="utf-8" ?><p>Gdy deklaracja zmiennej zaczyna wygl&#261;da&#263; jak &#347;ciana nazw typ&oacute;w, <code>var</code> potrafi wyra&#378;nie poprawi&#263; czytelno&#347;&#263; kodu. Problem w tym, &#380;e wiele os&oacute;b nadal myli go z typowaniem dynamicznym albo u&#380;ywa bez zastanowienia. Wyja&#347;niam, jak dzia&#322;a <code>var</code> w C#, kiedy warto po niego si&#281;ga&#263;, gdzie pojawiaj&#261; si&#281; ograniczenia oraz czym r&oacute;&#380;ni si&#281; od jawnego typu i konstrukcji <code>new()</code>.</p><div class="short-summary">
  <h2 id="najwazniejsze-zasady-uzywania-var-w-c">Najwa&#380;niejsze zasady u&#380;ywania var w C#</h2>
  <ul>
    <li>
<strong>Typowanie odbywa si&#281; podczas kompilacji</strong>, wi&#281;c <code>var</code> nie oznacza zmiennej dynamicznej.</li>
    <li>
<strong>Inicjalizacja jest obowi&#261;zkowa</strong> i musi nast&#261;pi&#263; w tej samej instrukcji.</li>
    <li>
<strong>Nie u&#380;yjesz go dla p&oacute;l, parametr&oacute;w ani typ&oacute;w zwracanych</strong> metod.</li>
    <li>
<strong>Najlepiej sprawdza si&#281; przy oczywistym typie</strong>, d&#322;ugich nazwach generycznych i zapytaniach LINQ.</li>
    <li>
<strong><code>var x = new();</code> nie dzia&#322;a</strong>, poniewa&#380; kompilator nie dostaje typu docelowego.</li>
  </ul>
</div><p><img src="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/post_image/099efafab1bd5ca1798cbf37aff190cf/c-var-implicit-typing-code-example-in-visual-studio.webp" class="image article-image" loading="lazy" alt="Wykres por&oacute;wnuje czytelno&#347;&#263; deklaracji zmiennych w C# z u&#380;yciem `var` (niejawne) vs. jawne typowanie."></p><h2 id="co-naprawde-oznacza-var-w-c">Co naprawd&#281; oznacza var w C#</h2><p><code>var</code> jest tak zwanym <strong>s&#322;owem kontekstowym</strong>. Kompilator odczytuje wyra&#380;enie po prawej stronie i na jego podstawie ustala konkretny typ zmiennej.</p><pre><code>var age =  thirty;
var name = "Anna";
var price = 19.99m;</code></pre><p>W powy&#380;szym przyk&#322;adzie zmienne otrzymaj&#261; odpowiednio typy <code>int</code>, <code>string</code> i <code>decimal</code>. Po kompilacji nie s&#261; to &bdquo;zmienne bez typu&rdquo;. Ka&#380;da ma <strong>&#347;ci&#347;le okre&#347;lony typ</strong>, kt&oacute;rego nie mo&#380;na p&oacute;&#378;niej zmieni&#263;.</p><pre><code>var number = 10;
number = "dziesi&#281;&#263;"; // b&#322;&#261;d kompilacji</code></pre><p>To najwa&#380;niejsze rozr&oacute;&#380;nienie. <code>var</code> nie jest odpowiednikiem <code>dynamic</code> ani wariantu znanego z niekt&oacute;rych innych j&#281;zyk&oacute;w. Nie op&oacute;&#378;nia sprawdzania typ&oacute;w do uruchomienia programu i nie wy&#322;&#261;cza podpowiedzi IntelliSense. W praktyce zyskujemy kr&oacute;tszy zapis, ale nadal pracujemy z <strong>typowaniem statycznym</strong>.</p><h2 id="jak-kompilator-ustala-typ-zmiennej">Jak kompilator ustala typ zmiennej</h2><p>Typ wynika bezpo&#347;rednio z wyra&#380;enia inicjalizuj&#261;cego. Czasem rezultat jest banalnie oczywisty, a czasem trzeba zwr&oacute;ci&#263; uwag&#281; na szczeg&oacute;&#322;y, takie jak typ litera&#322;u albo wynik operatora.</p><pre><code>var wholeNumber = 10;       // int
var bigNumber = 10L;        // long
var amount = 10.5;          // double
var money = 10.5m;          // decimal
var enabled = true;         // bool
var letters = new[] { 'a', 'b' }; // char[]</code></pre><p>Litera&#322;y maj&#261; swoje domy&#347;lne typy. Liczba ca&#322;kowita bez sufiksu jest zwykle typu <code>int</code>, liczba dziesi&#281;tna bez sufiksu <code>double</code>, a dopisek <code>m</code> wymusza <code>decimal</code>. Ten detal ma znaczenie w obliczeniach finansowych, gdzie przypadkowe u&#380;ycie <code>double</code> mo&#380;e prowadzi&#263; do niepo&#380;&#261;danych zaokr&#261;gle&#324;.</p><p>Mechanizm dzia&#322;a r&oacute;wnie&#380; dla kolekcji, klas i typ&oacute;w generycznych.</p><pre><code>var users = new List<user>();
var service = new EmailService();
var numbers = new[] { 1, 2, 3, 4 };</user></code></pre><p>W tych przypadkach typ jest nadal konkretny: odpowiednio <code>List<user></user></code>, <code>EmailService</code> i <code>int[]</code>. Z mojego do&#347;wiadczenia wynika, &#380;e w&#322;a&#347;nie przy d&#322;ugich typach generycznych <code>var</code> najcz&#281;&#347;ciej poprawia odbi&oacute;r kodu, bo czytelnik mo&#380;e skupi&#263; si&#281; na nazwie zmiennej i sposobie jej u&#380;ycia.</p><h2 id="gdzie-mozna-uzywac-var-a-gdzie-nie">Gdzie mo&#380;na u&#380;ywa&#263; var, a gdzie nie</h2><p>Najcz&#281;&#347;ciej spotkasz <code>var</code> przy zmiennych lokalnych deklarowanych wewn&#261;trz metod. Mo&#380;na go te&#380; stosowa&#263; w kilku konstrukcjach j&#281;zyka, o ile kompilator potrafi od razu ustali&#263; typ.</p><pre><code>for (var i = 0; i &lt; 10; i++)
{
    Console.WriteLine(i);
}

foreach (var user in users)
{
    Console.WriteLine(user.Name);
}

using var stream = File.OpenRead("data.json");

if (int.TryParse(text, out var result))
{
    Console.WriteLine(result);
}</code></pre><p>Nie mo&#380;na natomiast napisa&#263; deklaracji bez warto&#347;ci pocz&#261;tkowej ani przypisa&#263; bezpo&#347;rednio <code>null</code>.</p><pre><code>var value;          // b&#322;&#261;d
var empty = null;   // b&#322;&#261;d
var callback = () =&gt; Console.WriteLine("OK"); // b&#322;&#261;d</code></pre><p>W ostatnim przypadku kompilator nie wie, jaki typ delegata ma zosta&#263; u&#380;yty. Trzeba wskaza&#263; go jawnie albo przypisa&#263; funkcj&#281; do wcze&#347;niej okre&#347;lonego typu.</p><pre><code>Action callback = () =&gt; Console.WriteLine("OK");</code></pre><p><code>var</code> nie s&#322;u&#380;y r&oacute;wnie&#380; do deklarowania p&oacute;l klas, parametr&oacute;w metod ani typ&oacute;w zwracanych.</p><pre><code>public class ReportService
{
    private var reportName; // b&#322;&#261;d

    public var CreateReport() // b&#322;&#261;d
    {
        return ...;
    }
}</code></pre><p>W publicznym API jawne typy s&#261; potrzebne, poniewa&#380; opisuj&#261; kontrakt <a href="https://kursdotnet.pl/programowanie-obiektowe-w-c-bez-zbednej-teorii">klasy</a> lub metody. Dzi&#281;ki nim osoba korzystaj&#261;ca z biblioteki wie, czego oczekiwa&#263; bez analizowania implementacji.</p><h2 id="var-a-jawny-typ-i-target-typed-new">Var a jawny typ i target-typed new</h2><p>Najprostsze por&oacute;wnanie wygl&#261;da tak:</p><pre><code>List<string> names = new List<string>();
var names = new List<string>();</string></string></string></code></pre><p>Obie wersje tworz&#261; obiekt tego samego typu i maj&#261; takie samo zachowanie w czasie dzia&#322;ania. <strong>Nie ma automatycznego zysku wydajno&#347;ci</strong> wynikaj&#261;cego z u&#380;ycia <code>var</code>. R&oacute;&#380;nica dotyczy zapisu oraz tego, ile informacji o typie widzimy bezpo&#347;rednio w tej linii.</p><p>C# pozwala r&oacute;wnie&#380; pomin&#261;&#263; typ po prawej stronie, gdy wynika on z lewej strony deklaracji.</p><pre><code>List<string> names = new();</string></code></pre><p>To tak zwane <strong>target-typed new</strong>, czyli konstrukcja, w kt&oacute;rej <code>new()</code> korzysta z typu docelowego. Nie mo&#380;na jednak po&#322;&#261;czy&#263; jej bezpo&#347;rednio z <code>var</code>.</p><pre><code>var names = new(); // b&#322;&#261;d, brak typu docelowego
var names = new List<string>(); // poprawnie</string></code></pre><p>To ograniczenie jest logiczne. W pierwszej wersji nie ma &#380;adnej informacji, kt&oacute;ra pozwoli&#322;aby kompilatorowi wybra&#263; klas&#281; do utworzenia. Samo <code>var</code> oznacza przecie&#380; &bdquo;ustal typ z prawej strony&rdquo;, a prawa strona nie podaje typu.</p><table>
  <tbody>
    <tr>
      <th>Zapis</th>
      <th>Co wida&#263; od razu</th>
      <th>Kiedy ma sens</th>
    </tr>
    <tr>
      <td><code>User user = new();</code></td>
      <td>Typ jest jawny</td>
      <td>Gdy typ ma znaczenie dla zrozumienia kodu</td>
    </tr>
    <tr>
      <td><code>var user = new User();</code></td>
      <td>Typ wynika z prawej strony</td>
      <td>Gdy konstruktor jasno okre&#347;la, co powstaje</td>
    </tr>
    <tr>
      <td><code>var user = new();</code></td>
      <td>Brak informacji o typie</td>
      <td>Niepoprawny zapis</td>
    </tr>
  </tbody>
</table><h2 id="kiedy-uzycie-var-poprawia-kod">Kiedy u&#380;ycie var poprawia kod</h2><p>Najlepsza regu&#322;a nie brzmi &bdquo;zawsze u&#380;ywaj <code>var</code>&rdquo; ani &bdquo;zawsze pokazuj typ&rdquo;. Ja stosuj&#281; prostsze kryterium: je&#380;eli typ jest <strong>oczywisty z prawej strony</strong>, skr&oacute;cony zapis zwykle pomaga.</p><pre><code>var customer = new Customer();
var invoice = new Invoice();
var cancellationToken = new CancellationToken();</code></pre><p>Nie musz&#281; wtedy czyta&#263; tej samej informacji dwa razy. Nazwa klasy m&oacute;wi wystarczaj&#261;co du&#380;o, a deklaracja nie jest obci&#261;&#380;ona powt&oacute;rzeniem typu.</p><p><code>var</code> jest szczeg&oacute;lnie przydatny przy LINQ, gdzie pe&#322;ny typ wyniku mo&#380;e by&#263; d&#322;ugi albo zale&#380;e&#263; od sposobu wykonania zapytania.</p><pre><code>var activeUsers = users
    .Where(user =&gt; user.IsActive)
    .<a href="https://kursdotnet.pl/sortowanie-w-linq-orderby-thenby-i-ef-bez-pulapek">OrderBy</a>(user =&gt; user.Name)
    .ToList();</code></pre><p>W tym miejscu jawny typ m&oacute;g&#322;by wygl&#261;da&#263; tak:</p><pre><code>List<user> activeUsers = users
    .Where(user =&gt; user.IsActive)
    .OrderBy(user =&gt; user.Name)
    .ToList();</user></code></pre><p>Obie wersje s&#261; poprawne, ale <code>var</code> ogranicza powtarzanie. Trzeba tylko uwa&#380;a&#263;, gdy nie jest jasne, czy wynik jest materializowan&#261; list&#261;, <code>IEnumerable<user></user></code>, czy zapytaniem wykonywanym dopiero p&oacute;&#378;niej. W takich miejscach jawny typ mo&#380;e by&#263; cenn&#261; informacj&#261; dla osoby utrzymuj&#261;cej kod.</p><p class="read-more"><strong>Przeczytaj r&oacute;wnie&#380;: <a href="https://kursdotnet.pl/generyki-w-c-i-net-jak-pisac-bezpieczniejszy-kod">Generyki w C# i .NET - jak pisa&#263; bezpieczniejszy kod?</a></strong></p><h3 id="typy-anonimowe">Typy anonimowe</h3><p>Istnieje te&#380; przypadek, w kt&oacute;rym <code>var</code> jest praktycznie konieczny. Typ anonimowy nie ma nazwy, kt&oacute;r&#261; mo&#380;na wpisa&#263; w kodzie.</p><pre><code>var summary = new
{
    ProductName = "Klawiatura",
    Quantity = 2,
    Total = 399.98m
};

Console.WriteLine(summary.ProductName);</code></pre><p>To dobre rozwi&#261;zanie dla tymczasowych projekcji w LINQ albo danych wykorzystywanych wy&#322;&#261;cznie lokalnie. Nie nadaje si&#281; natomiast jako typ przekazywany przez publiczny interfejs. Je&#347;li obiekt ma opu&#347;ci&#263; metod&#281; lub warstw&#281; aplikacji, lepiej utworzy&#263; nazwany rekord albo klas&#281;.</p><h2 id="kiedy-lepiej-zrezygnowac-z-var">Kiedy lepiej zrezygnowa&#263; z var</h2><p>Najwi&#281;kszy problem pojawia si&#281; wtedy, gdy typ nie wynika jasno z kodu. Przyk&#322;ad poni&#380;ej jest formalnie poprawny, ale wymaga od czytelnika przej&#347;cia do definicji metody.</p><pre><code>var result = repository.GetData();</code></pre><p>Czy <code>result</code> jest list&#261;, s&#322;ownikiem, pojedynczym rekordem, czy mo&#380;e obiektem reprezentuj&#261;cym wynik operacji? Sama linia tego nie wyja&#347;nia. W krytycznych miejscach jawny typ potrafi skr&oacute;ci&#263; analiz&#281; kodu, nawet je&#347;li zapis jest d&#322;u&#380;szy.</p><pre><code>OperationResult result = repository.GetData();</code></pre><p>Ostro&#380;no&#347;&#263; przydaje si&#281; te&#380; przy typach prostych, gdy typ wp&#322;ywa na znaczenie obliczenia.</p><pre><code>var value = 1 / 2; // wynik typu int, czyli 0
double preciseValue = 1.0 / 2.0; // 0.5</code></pre><p>Sam problem nie wynika z <code>var</code>, lecz z typ&oacute;w litera&#322;&oacute;w i operatora dzielenia. Mimo to jawny typ mo&#380;e lepiej komunikowa&#263; intencj&#281;. W kodzie finansowym, algorytmach i konwersjach jednostek nie zostawia&#322;bym takich decyzji przypadkowi.</p><p>Nie u&#380;ywa&#322;bym <code>var</code> tak&#380;e wtedy, gdy zmienna ma reprezentowa&#263; wa&#380;n&#261; abstrakcj&#281;.</p><pre><code>IReadOnlyCollection<user> users = LoadUsers();</user></code></pre><p>Ten zapis m&oacute;wi czytelnikowi co&#347; wi&#281;cej ni&#380; konkretna klasa zwr&oacute;cona przez metod&#281;. Pokazuje, &#380;e dalszy kod potrzebuje tylko kolekcji do odczytu. To r&oacute;&#380;nica mi&#281;dzy ujawnieniem implementacji a opisaniem potrzebnego kontraktu.</p><h2 id="najczestsze-bledy-w-praktyce">Najcz&#281;stsze b&#322;&#281;dy w praktyce</h2><p>Pocz&#261;tkuj&#261;cy cz&#281;sto zak&#322;adaj&#261;, &#380;e <code>var</code> pozwala zmieni&#263; typ zmiennej w trakcie dzia&#322;ania programu. Tak nie jest.</p><pre><code>var data = "tekst";
data = 123; // b&#322;&#261;d kompilacji</code></pre><p>Drugi cz&#281;sty b&#322;&#261;d to u&#380;ywanie <code>var</code> bez inicjalizacji. Kompilator nie ma wtedy &#380;adnego &#378;r&oacute;d&#322;a informacji o typie.</p><pre><code>var data; // brak informacji o typie</code></pre><p>Trzeba te&#380; uwa&#380;a&#263; na typ wyniku metod i przeci&#261;&#380;enia. Zmiana wyra&#380;enia po prawej stronie mo&#380;e zmieni&#263; typ zmiennej, a razem z nim dost&#281;pne w&#322;a&#347;ciwo&#347;ci, metody i przeci&#261;&#380;enia.</p><pre><code>var value = GetValue();
Console.WriteLine(value.Length);</code></pre><p>Je&#380;eli <code>GetValue()</code> zacznie zwraca&#263; inny typ, kod mo&#380;e przesta&#263; si&#281; kompilowa&#263;. To nie wada mechanizmu, tylko sygna&#322;, &#380;e typ by&#322; istotn&#261; cz&#281;&#347;ci&#261; za&#322;o&#380;enia i by&#263; mo&#380;e powinien zosta&#263; zapisany jawnie.</p><p>Osobn&#261; pu&#322;apk&#261; s&#261; typy <a href="https://kursdotnet.pl/operator-w-c-jak-dziala-i-kiedy-go-uzywac">nullable</a>. Je&#347;li metoda zwraca <code>string?</code>, u&#380;ycie <code>var</code> nie usuwa informacji o mo&#380;liwo&#347;ci wyst&#261;pienia warto&#347;ci <code>null</code>. Kompilator nadal mo&#380;e zg&#322;osi&#263; ostrze&#380;enie przy pr&oacute;bie u&#380;ycia wyniku bez sprawdzenia.</p><pre><code>var name = FindUserName();

Console.WriteLine(name.Length); // mo&#380;liwe ostrze&#380;enie nullable</code></pre><p>Dlatego <code>var</code> nie powinien by&#263; traktowany jako spos&oacute;b na omijanie regu&#322; bezpiecze&#324;stwa typ&oacute;w. On tylko skraca deklaracj&#281;, a nie zmienia zasad j&#281;zyka.</p><h2 id="prosta-regula-dla-zespolu">Prosta regu&#322;a dla zespo&#322;u</h2><p>Wsp&oacute;lny styl jest wa&#380;niejszy ni&#380; sp&oacute;r o jedyn&#261; s&#322;uszn&#261; wersj&#281;. Dobrze dzia&#322;aj&#261; regu&#322;y oparte na czytelno&#347;ci, na przyk&#322;ad nast&#281;puj&#261;ce:</p><ul>
  <li>u&#380;ywaj <code>var</code>, gdy typ jest oczywisty z inicjalizatora,</li>
  <li>zostaw jawny typ, gdy pokazuje wa&#380;n&#261; abstrakcj&#281; albo intencj&#281;,</li>
  <li>stosuj <code>var</code> przy typach anonimowych i skomplikowanych wynikach LINQ,</li>
  <li>nie u&#380;ywaj go jako zamiennika dla <code>dynamic</code>,</li>
  <li>konfiguruj analizatory i formatowanie, aby ca&#322;y projekt trzyma&#322; jedn&#261; konwencj&#281;.</li>
</ul><p>Nie traktowa&#322;bym te&#380; d&#322;ugo&#347;ci typu jako jedynego kryterium. Czasem d&#322;uga deklaracja jest cen&#261; za jasno&#347;&#263;, a czasem tylko powtarza informacj&#281;, kt&oacute;r&#261; i tak natychmiast wida&#263;.</p><p>Najpraktyczniejszy test jest prosty: zas&#322;o&#324; praw&#261; stron&#281; deklaracji i przeczytaj sam&#261; zmienn&#261;. Je&#347;li bez typu trudno zrozumie&#263;, czym ona jest, jawna deklaracja prawdopodobnie b&#281;dzie lepsza. Je&#347;li konstruktor albo wyra&#380;enie m&oacute;wi wszystko, <code>var</code> upraszcza kod bez utraty znaczenia.</p><h2 id="var-jako-skrot-nie-ukrywanie-typow">Var jako skr&oacute;t, nie ukrywanie typ&oacute;w</h2><p><code>var</code> w C# oznacza automatyczne ustalenie typu podczas kompilacji. Nie wprowadza dynamicznego typowania, nie daje zmiennej specjalnych mo&#380;liwo&#347;ci i nie wp&#322;ywa samodzielnie na wydajno&#347;&#263; programu.</p><p>Najwi&#281;cej zysku daje przy oczywistych obiektach, typach generycznych, p&#281;tlach, zapytaniach LINQ i typach anonimowych. Gdy typ wyra&#380;a kontrakt, wa&#380;n&#261; abstrakcj&#281; albo zamiar obliczenia, jawny zapis cz&#281;sto b&#281;dzie czytelniejszy.</p><p>Traktuj&#281; t&#281; konstrukcj&#281; jak narz&#281;dzie redakcyjne dla kodu. Dobrze u&#380;yta usuwa szum, ale u&#380;yta automatycznie potrafi ukry&#263; informacj&#281;, kt&oacute;rej kolejny programista b&#281;dzie potrzebowa&#322; najbardziej.</p>]]></content:encoded>
      <author>Radosław Krajewski</author>
      <category>C# i .NET</category>
      <media:thumbnail url="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/thumbnail/73f2a3459f8df0b4ba4ff584c552e0a8/var-w-c-bez-tajemnic-kiedy-uzywac-a-kiedy-nie.webp"/>
      <pubDate>Tue, 15 Sep 2026 19:09:00 +0200</pubDate>
    </item>
    <item>
      <title>LINQ i foreach w C# bez pułapek - praktyczne zasady</title>
      <link>https://kursdotnet.pl/linq-i-foreach-w-c-bez-pulapek-praktyczne-zasady</link>
      <description>LINQ i foreach w C#: poznaj wykonanie odroczone, ToList(), ForEach i pułapki wielokrotnej enumeracji. Sprawdź praktyczne zasady.</description>
      <content:encoded><![CDATA[<?xml encoding="utf-8" ?><p>Masz kolekcj&#281; obiekt&oacute;w, filtrujesz j&#261; za pomoc&#261; LINQ i chcesz wykona&#263; operacj&#281; na ka&#380;dym znalezionym elemencie? Najcz&#281;&#347;ciej najlepszym rozwi&#261;zaniem b&#281;dzie zwyk&#322;a p&#281;tla <code>foreach</code>, ale spos&oacute;b zapisania zapytania, moment jego wykonania i wyb&oacute;r mi&#281;dzy <code>foreach</code>, <code>ForEach</code> oraz <code>ToList()</code> maj&#261; du&#380;e znaczenie. Poka&#380;&#281; praktyczne przyk&#322;ady, typowe b&#322;&#281;dy i sytuacje, w kt&oacute;rych pozornie poprawny kod mo&#380;e wykona&#263; si&#281; wi&#281;cej razy, ni&#380; zak&#322;adasz.</p><div class="short-summary">
<h2 id="najwazniejsze-zasady-iterowania-po-wynikach-linq">Najwa&#380;niejsze zasady iterowania po wynikach LINQ</h2>
<ul>
<li>
<strong><code>foreach</code> wykonuje zapytanie LINQ</strong>, gdy zaczyna odczytywa&#263; jego elementy.</li>
<li>
<strong><code>Where</code> filtruje</strong>, a <strong><code>Select</code> przekszta&#322;ca</strong> dane przed przej&#347;ciem po kolekcji.</li>
<li>
<strong><code>ForEach</code> nie jest metod&#261; LINQ</strong>, tylko metod&#261; <a href="https://kursdotnet.pl/programowanie-obiektowe-w-c-bez-zbednej-teorii">klasy</a> <code>List<t></t></code>.</li>
<li>
<strong><code>ToList()</code> materializuje wynik</strong> i zatrzymuje go w pami&#281;ci jako list&#281;.</li>
<li>Przy <strong><code>IQueryable</code></strong> trzeba uwa&#380;a&#263; na wielokrotne wykonanie zapytania do bazy.</li>
</ul>
</div><p><img src="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/post_image/ff5907815e3aeef9831b553218e1f03c/c-linq-foreach-iteration-code-example-in-visual-studio.webp" class="image article-image" loading="lazy" alt="Konwersja p&#281;tli `foreach` na zapytanie LINQ. Kod filtruje kr&oacute;tkie pozdrowienia."></p><h2 id="linq-i-foreach-w-c-w-najprostszym-wariancie">LINQ i foreach w C# w najprostszym wariancie</h2><p>Najbardziej czytelny schemat wygl&#261;da tak: najpierw buduj&#281; zapytanie, a p&oacute;&#378;niej przechodz&#281; po jego wynikach za pomoc&#261; <code>foreach</code>. W tym przyk&#322;adzie interesuj&#261; mnie tylko aktywne produkty dro&#380;sze ni&#380; 100 z&#322;.</p><pre><code>var products = new[]
{
    new { Name = "Klawiatura", Price = 180m, IsActive = true },
    new { Name = "Mysz", Price = 80m, IsActive = true },
    new { Name = "Monitor", Price = 900m, IsActive = false }
};

var expensiveActiveProducts = products
    .Where(product =&gt; product.IsActive &amp;&amp; product.Price &gt; 100)
    .OrderBy(product =&gt; product.Price);

foreach (var product in expensiveActiveProducts)
{
    Console.WriteLine($"{product.Name}: {product.Price} z&#322;");
}</code></pre><p><code>Where</code> ogranicza zbi&oacute;r, a <code>OrderBy</code> ustala kolejno&#347;&#263;. Samo przypisanie wyniku do zmiennej <strong>nie musi jeszcze uruchomi&#263; ca&#322;ej logiki</strong>. W przypadku typowych operator&oacute;w LINQ wynik jest obliczany dopiero wtedy, gdy zaczynam go enumerowa&#263;, na przyk&#322;ad w&#322;a&#347;nie w p&#281;tli <code>foreach</code>.</p><p>To wa&#380;ne, bo zmienna <code>expensiveActiveProducts</code> przechowuje przede wszystkim opis operacji, a niekoniecznie gotow&#261; list&#281; element&oacute;w. Dlatego mo&#380;na powiedzie&#263;, &#380;e p&#281;tla jest nie tylko sposobem odczytu danych, ale tak&#380;e momentem wykonania zapytania.</p><h2 id="co-dokladnie-robi-foreach-podczas-iterowania">Co dok&#322;adnie robi foreach podczas iterowania</h2><p>P&#281;tla <code>foreach</code> korzysta z mechanizmu enumeracji, czyli odczytu kolejnych element&oacute;w sekwencji. Dla <code>IEnumerable<t></t></code> compiler wykorzystuje enumerator i wywo&#322;uje go krok po kroku, dzi&#281;ki czemu nie musisz r&#281;cznie obs&#322;ugiwa&#263; indeksu ani sprawdza&#263;, czy kolekcja ma jeszcze dane.</p><pre><code>var numbers = Enumerable.Range(1, 10)
    .Where(number =&gt; number % 2 == 0)
    .Select(number =&gt; number * 10);

foreach (var number in numbers)
{
    Console.WriteLine(number);
}</code></pre><p>Wynikiem b&#281;d&#261; liczby 20, 40, 60, 80 i 100. Najpierw dzia&#322;a filtr, p&oacute;&#378;niej projekcja, czyli przekszta&#322;cenie ka&#380;dego elementu. <strong>Kolejno&#347;&#263; operator&oacute;w ma znaczenie</strong>, poniewa&#380; wcze&#347;niejsze odfiltrowanie danych mo&#380;e ograniczy&#263; liczb&#281; element&oacute;w przetwarzanych przez nast&#281;pne operacje.</p><p>W praktyce cz&#281;sto spotykam te&#380; pr&oacute;b&#281; u&#380;ycia <code>Select</code> do wykonywania efekt&oacute;w ubocznych, na przyk&#322;ad zapisu do logu. To zwykle z&#322;y kierunek, poniewa&#380; <code>Select</code> s&#322;u&#380;y do tworzenia nowej sekwencji. Gdy chc&#281; wykona&#263; akcj&#281; dla ka&#380;dego elementu, wybieram <code>foreach</code>, bo kod od razu pokazuje swoj&#261; intencj&#281;.</p><h2 id="foreach-foreach-i-tolist-maja-rozne-zadania">foreach, ForEach i ToList maj&#261; r&oacute;&#380;ne zadania</h2><p>Te trzy konstrukcje bywaj&#261; wrzucane do jednego worka, cho&#263; rozwi&#261;zuj&#261; r&oacute;&#380;ne problemy. Najwi&#281;ksze nieporozumienie polega na tym, &#380;e <strong><code>ForEach</code> nie jest standardowym operatorem LINQ</strong>. Jest metod&#261; dost&#281;pn&#261; mi&#281;dzy innymi dla <code>List<t></t></code>.</p><table>
<tbody>
<tr>
<th>Rozwi&#261;zanie</th>
<th>Kiedy dzia&#322;a</th>
<th>Najwa&#380;niejsza cecha</th>
</tr>
<tr>
<td><code>foreach</code></td>
<td>Na dowolnym obiekcie mo&#380;liwym do enumeracji</td>
<td>Obs&#322;uguje <code>break</code>, <code>continue</code> i wyj&#261;tki w naturalny spos&oacute;b</td>
</tr>
<tr>
<td><code>List<t>.ForEach</t></code></td>
<td>Tylko na li&#347;cie</td>
<td>Przyjmuje akcj&#281; w postaci delegata, ale gorzej skaluje si&#281; przy bardziej z&#322;o&#380;onej logice</td>
</tr>
<tr>
<td><code>ToList()</code></td>
<td>Na sekwencji LINQ</td>
<td>Materializuje wynik i zapisuje elementy w pami&#281;ci</td>
</tr>
</tbody>
</table><pre><code>var names = users
    .Where(user =&gt; user.IsActive)
    .Select(user =&gt; user.Name)
    .ToList();

foreach (var name in names)
{
    Console.WriteLine(name);
}</code></pre><p>W tym wariancie <code>ToList()</code> ko&#324;czy budowanie zapytania i tworzy konkretn&#261; list&#281;. Przydaje si&#281;, gdy chc&#281; <strong>wykona&#263; zapytanie tylko raz</strong>, u&#380;y&#263; wynik&oacute;w kilka razy albo mie&#263; mo&#380;liwo&#347;&#263; dodawania i usuwania element&oacute;w z kolekcji wynikowej.</p><p>Nie dodaj&#281; jednak <code>ToList()</code> automatycznie. Je&#347;li dane s&#261; du&#380;e, materializacja mo&#380;e niepotrzebnie zwi&#281;kszy&#263; zu&#380;ycie pami&#281;ci. Gdy potrzebuj&#281; tylko jednorazowo przej&#347;&#263; po wynikach, bezpo&#347;redni <code>foreach</code> jest prostszy i cz&#281;sto korzystniejszy.</p><h2 id="wykonanie-odroczone-potrafi-zmienic-wynik">Wykonanie odroczone potrafi zmieni&#263; wynik</h2><p>Wykonanie odroczone oznacza, &#380;e zapytanie LINQ jest obliczane dopiero przy enumeracji. Dzi&#281;ki temu mog&#281; zbudowa&#263; zapytanie wcze&#347;niej, a dane zostan&#261; odczytane dopiero wtedy, gdy naprawd&#281; b&#281;d&#261; potrzebne. Ta wygoda ma jednak konsekwencj&#281;: wynik mo&#380;e zale&#380;e&#263; od stanu &#378;r&oacute;d&#322;a w momencie wykonania.</p><pre><code>var numbers = new List<int> { 1, 2, 3 };

var evenNumbers = numbers.Where(number =&gt; number % 2 == 0);

numbers.Add(4);

foreach (var number in evenNumbers)
{
    Console.WriteLine(number);
}</int></code></pre><p>Wynik obejmie r&oacute;wnie&#380; liczb&#281; 4, poniewa&#380; lista zosta&#322;a zmieniona przed wykonaniem p&#281;tli. Gdy zale&#380;y mi na zatrzymaniu konkretnego stanu danych, tworz&#281; kopi&#281; przez <code>ToList()</code>.</p><pre><code>var evenNumbersSnapshot = numbers
    .Where(number =&gt; number % 2 == 0)
    .ToList();

numbers.Add(6);

foreach (var number in evenNumbersSnapshot)
{
    Console.WriteLine(number);
}</code></pre><p>Drugi wa&#380;ny problem to wielokrotna enumeracja. Ka&#380;de przej&#347;cie po zapytaniu mo&#380;e ponownie uruchomi&#263; filtr, sortowanie, a przy <code>IQueryable</code> tak&#380;e zapytanie do bazy. <strong>Je&#347;li wynik b&#281;dzie u&#380;ywany kilka razy, &#347;wiadomie zdecyduj, czy potrzebujesz materializacji</strong>, zamiast liczy&#263; na przypadkowe zachowanie.</p><h2 id="praktyczne-przyklady-z-projekcja-i-grupowaniem">Praktyczne przyk&#322;ady z projekcj&#261; i grupowaniem</h2><p>Najwi&#281;ksza si&#322;a po&#322;&#261;czenia LINQ z <code>foreach</code> pojawia si&#281; wtedy, gdy p&#281;tla dostaje ju&#380; dane przygotowane dok&#322;adnie do konkretnego zadania. Nie musz&#281; w &#347;rodku sprawdza&#263; wszystkich warunk&oacute;w, pobiera&#263; w&#322;a&#347;ciwo&#347;ci i budowa&#263; obiekt&oacute;w pomocniczych.</p><pre><code>var reportRows = orders
    .Where(order =&gt; order.Status == OrderStatus.Paid)
    .Select(order =&gt; new
    {
        order.Id,
        CustomerName = order.Customer.Name,
        Total = order.Items.Sum(item =&gt; item.Price * item.Quantity)
    })
    .OrderByDescending(row =&gt; row.Total);

foreach (var row in reportRows)
{
    Console.WriteLine(
        $"Zam&oacute;wienie {row.Id}, klient: {row.CustomerName}, suma: {row.Total} z&#322;");
}</code></pre><p>Ten styl ogranicza odpowiedzialno&#347;&#263; p&#281;tli do prezentacji lub wykonania dzia&#322;ania. W kodzie produkcyjnym szczeg&oacute;lnie ceni&#281; sobie takie rozdzielenie, bo <strong>warunki i obliczenia s&#261; w zapytaniu, a efekt ko&#324;cowy w p&#281;tli</strong>.</p><p>Je&#347;li logika filtrowania robi si&#281; d&#322;u&#380;sza, wydzielam j&#261; do osobnej metody. W projektach, w kt&oacute;rych cz&#281;sto korzystam z pomocniczych funkcji bez stanu obiektu, przydatne mo&#380;e by&#263; tak&#380;e <a href="https://kursdotnet.pl/static-w-c-metody-pola-i-klasy-bez-typowych-pulapek">u&#380;ycie metod static w C#</a>. Trzeba jednak pami&#281;ta&#263;, &#380;e metoda przekazywana do LINQ przez Entity Framework musi by&#263; mo&#380;liwa do przet&#322;umaczenia na zapytanie &#378;r&oacute;d&#322;owe, je&#347;li pracujemy na <code>IQueryable</code>.</p><p>Przy grupowaniu pojawia si&#281; z kolei naturalna p&#281;tla zagnie&#380;d&#380;ona.</p><pre><code>var productsByCategory = products
    .Where(product =&gt; product.IsActive)
    .GroupBy(product =&gt; product.Category);

foreach (var group in productsByCategory)
{
    Console.WriteLine(group.Key);

    foreach (var product in group)
    {
        Console.WriteLine($"- {product.Name}");
    }
}</code></pre><p>To czytelniejsze ni&#380; r&#281;czne budowanie s&#322;ownika, gdy potrzebuj&#281; tylko przej&#347;&#263; po kategoriach i ich elementach. <strong><code>GroupBy</code> porz&#261;dkuje dane przed iteracj&#261;</strong>, a nie zast&#281;puje samej p&#281;tli.</p><h2 id="typowe-bledy-przy-iterowaniu-wynikow-linq">Typowe b&#322;&#281;dy przy iterowaniu wynik&oacute;w LINQ</h2><h3 id="modyfikowanie-zrodlowej-kolekcji-w-petli">Modyfikowanie &#378;r&oacute;d&#322;owej kolekcji w p&#281;tli</h3><p>Usuwanie element&oacute;w z listy podczas dzia&#322;ania <code>foreach</code> zwykle ko&#324;czy si&#281; wyj&#261;tkiem albo pomini&#281;ciem cz&#281;&#347;ci danych. Bezpieczniej utworzy&#263; kopi&#281; element&oacute;w do usuni&#281;cia, u&#380;y&#263; <code>RemoveAll</code> albo przygotowa&#263; now&#261; list&#281; za pomoc&#261; <code>Where</code> i <code>ToList()</code>.</p><pre><code>var remainingUsers = users
    .Where(user =&gt; !user.IsBlocked)
    .ToList();</code></pre><h3 id="wykonywanie-tego-samego-zapytania-kilka-razy">Wykonywanie tego samego zapytania kilka razy</h3><p>Ten kod mo&#380;e wielokrotnie przej&#347;&#263; po &#378;r&oacute;dle:</p><pre><code>if (query.Any())
{
    Console.WriteLine(query.Count());
}

foreach (var item in query)
{
    Process(item);
}</code></pre><p>Przy zwyk&#322;ej tablicy b&#281;dzie to przede wszystkim dodatkowa praca procesora. Przy &#378;r&oacute;dle bazodanowym mo&#380;e oznacza&#263; <strong>trzy osobne operacje</strong>. Je&#380;eli potrzebuj&#281; jednego zestawu danych, cz&#281;sto materializuj&#281; go raz i dopiero p&oacute;&#378;niej sprawdzam jego zawarto&#347;&#263;.</p><h3 id="mylenie-ienumerable-z-iqueryable">Mylenie IEnumerable z IQueryable</h3><p><code>IEnumerable<t></t></code> oznacza przetwarzanie element&oacute;w po stronie aplikacji. <code>IQueryable<t></t></code> pozwala dostawcy, na przyk&#322;ad ORM, prze&#322;o&#380;y&#263; cz&#281;&#347;&#263; operacji na zapytanie &#378;r&oacute;d&#322;owe. To rozr&oacute;&#380;nienie wp&#322;ywa na wydajno&#347;&#263;, liczb&#281; pobieranych kolumn i ilo&#347;&#263; danych przesy&#322;anych z bazy.</p><p class="read-more"><strong>Przeczytaj r&oacute;wnie&#380;: <a href="https://kursdotnet.pl/async-i-await-w-c-bez-blokowania-watku-praktyczny-przewodnik">Async i await w C# bez blokowania w&#261;tku - praktyczny przewodnik</a></strong></p><h3 id="nieprawidlowe-porownywanie-statusow">Nieprawid&#322;owe por&oacute;wnywanie status&oacute;w</h3><p>Je&#380;eli elementy maj&#261; status zapisany jako typ wyliczeniowy, filtr powinien por&oacute;wnywa&#263; warto&#347;ci tego samego typu, a nie przypadkowe liczby lub teksty.</p><pre><code>var paidOrders = orders
    .Where(order =&gt; order.Status == OrderStatus.Paid);

foreach (var order in paidOrders)
{
    Process(order);
}</code></pre><p>Przy pracy ze statusami i flagami dobrze zna&#263; zasady <a href="https://kursdotnet.pl/enum-w-c-jak-uzywac-go-bezpiecznie">bezpiecznego u&#380;ycia enum w C#</a>. Czytelna nazwa warto&#347;ci zwykle wygrywa z magiczn&#261; liczb&#261;, ale sam typ wyliczeniowy nie zabezpieczy aplikacji przed b&#322;&#281;dnym mapowaniem danych z zewn&#281;trznego &#378;r&oacute;d&#322;a.</p><h2 id="kiedy-zwykly-foreach-nie-wystarczy">Kiedy zwyk&#322;y foreach nie wystarczy</h2><p>Standardowa p&#281;tla jest w&#322;a&#347;ciwa dla sekwencji synchronicznych. Je&#347;li dane nap&#322;ywaj&#261; asynchronicznie, na przyk&#322;ad z paginowanego API albo strumienia, mo&#380;na potrzebowa&#263; <code>await foreach</code> i typu <code>IAsyncEnumerable<t></t></code>.</p><pre><code>await foreach (var message in GetMessagesAsync())
{
    await HandleMessageAsync(message);
}</code></pre><p>Nie zamieniam jednak zwyk&#322;ego <code>foreach</code> na wersj&#281; asynchroniczn&#261; tylko dlatego, &#380;e kod zawiera <code>async</code>. <strong>Asynchroniczna enumeracja ma sens wtedy, gdy samo &#378;r&oacute;d&#322;o dostarcza dane etapami</strong>. Dla gotowej listy w pami&#281;ci niczego istotnego nie poprawi.</p><p>Podobnie nie ka&#380;dy problem wymaga LINQ. Gdy p&#281;tla ma skomplikowan&#261; logik&#281;, kilka warunk&oacute;w wyj&#347;cia i obs&#322;ug&#281; b&#322;&#281;d&oacute;w, klasyczny <code>foreach</code> bywa po prostu &#322;atwiejszy do testowania. Zwi&#281;z&#322;o&#347;&#263; zapisu jest zalet&#261; tylko wtedy, gdy nie ukrywa dzia&#322;ania kodu.</p><h2 id="dobry-domyslny-wybor-dla-codziennego-kodu-c">Dobry domy&#347;lny wyb&oacute;r dla codziennego kodu C#</h2><p>Najcz&#281;&#347;ciej zaczynam od zapytania LINQ, kt&oacute;re filtruje, sortuje lub projektuje dane, a ko&#324;cz&#281; je zwyk&#322;ym <code>foreach</code>. Dostaj&#281; wtedy czytelny podzia&#322; odpowiedzialno&#347;ci: <strong>LINQ przygotowuje dane, a p&#281;tla wykonuje operacj&#281;</strong>.</p><p><code>ToList()</code> dodaj&#281; &#347;wiadomie, gdy potrzebuj&#281; migawki danych, wielokrotnego u&#380;ycia wyniku albo kolekcji, kt&oacute;r&#261; b&#281;d&#281; modyfikowa&#263;. Z kolei <code>List<t>.ForEach</t></code> zostawiam dla bardzo kr&oacute;tkich akcji, w kt&oacute;rych brak <code>break</code> i <code>continue</code> nie b&#281;dzie problemem.</p><p>Je&#347;li zapami&#281;tasz jedn&#261; rzecz, niech b&#281;dzie ni&#261; moment wykonania zapytania. To w&#322;a&#347;nie on najcz&#281;&#347;ciej decyduje o tym, czy kod b&#281;dzie przewidywalny, wydajny i bezpieczny w aplikacji .NET.</p>]]></content:encoded>
      <author>Przemysław Kwiatkowski</author>
      <category>C# i .NET</category>
      <media:thumbnail url="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/thumbnail/bb57b31968a20640d7351ecc74fd939e/linq-i-foreach-w-c-bez-pulapek-praktyczne-zasady.webp"/>
      <pubDate>Tue, 15 Sep 2026 18:36:00 +0200</pubDate>
    </item>
    <item>
      <title>Mikroserwisy w .NET bez chaosu - granice, wzorce i wdrożenie</title>
      <link>https://kursdotnet.pl/mikroserwisy-w-net-bez-chaosu-granice-wzorce-i-wdrozenie</link>
      <description>Mikroserwisy w .NET: poznaj granice usług, komunikację, Outbox, Sagę i wdrożenia w chmurze. Sprawdź, jak zacząć bez chaosu.</description>
      <content:encoded><![CDATA[<?xml encoding="utf-8" ?><p>Masz dzia&#322;aj&#261;cy monolit, ale ka&#380;da wi&#281;ksza zmiana zaczyna wp&#322;ywa&#263; na ca&#322;&#261; aplikacj&#281;, wdro&#380;enie trwa coraz d&#322;u&#380;ej, a skalowanie jednego modu&#322;u wymaga uruchamiania wszystkiego od nowa. Mikroserwisy w .NET mog&#261; rozwi&#261;za&#263; ten problem, pod warunkiem &#380;e granice us&#322;ug wynikaj&#261; z domeny, a nie z przypadkowego podzia&#322;u klas. Poka&#380;&#281;, jak projektowa&#263; takie systemy, kt&oacute;re wzorce naprawd&#281; pomagaj&#261; oraz jak przygotowa&#263; aplikacj&#281; do wdro&#380;enia w kontenerach i chmurze.</p><div class="short-summary">
<h2 id="najwazniejsze-decyzje-wplywaja-na-sukces-mikroserwisow">Najwa&#380;niejsze decyzje wp&#322;ywaj&#261; na sukces mikroserwis&oacute;w</h2>
<ul>
<li>
<strong>Granice domenowe</strong> s&#261; wa&#380;niejsze ni&#380; liczba us&#322;ug i rozmiar projekt&oacute;w.</li>
<li>Ka&#380;dy mikroserwis powinien mie&#263; <strong>w&#322;asne dane i niezale&#380;ny cykl wdawniania</strong>.</li>
<li>Do komunikacji synchronicznej sprawdz&#261; si&#281; HTTP lub gRPC, a do proces&oacute;w rozproszonych <strong>zdarzenia i kolejki</strong>.</li>
<li>
<strong>Outbox, Saga, retry i circuit breaker</strong> ograniczaj&#261; ryzyko utraty danych oraz kaskadowych awarii.</li>
<li>ASP.NET Core, Docker, .NET Aspire i Kubernetes tworz&#261; praktyczny stos do budowy i wdra&#380;ania us&#322;ug.</li>
</ul>
</div><h2 id="kiedy-mikroserwisy-w-net-maja-sens">Kiedy mikroserwisy w .NET maj&#261; sens</h2><p>Mikroserwis to osobny proces odpowiedzialny za konkretn&#261; zdolno&#347;&#263; biznesow&#261;, na przyk&#322;ad katalog produkt&oacute;w, zam&oacute;wienia albo p&#322;atno&#347;ci. Powinien da&#263; si&#281; <strong>testowa&#263;, wdra&#380;a&#263; i skalowa&#263; niezale&#380;nie</strong> od pozosta&#322;ych cz&#281;&#347;ci systemu. Sama technologia .NET nie sprawia jednak, &#380;e aplikacja automatycznie staje si&#281; mikroserwisowa. Potrzebne s&#261; jeszcze w&#322;a&#347;ciwe granice, kontrakty i spos&oacute;b zarz&#261;dzania zale&#380;no&#347;ciami.</p><p>Najwi&#281;ksz&#261; korzy&#347;&#263; widz&#281; w systemach, kt&oacute;re maj&#261; kilka niezale&#380;nych obszar&oacute;w biznesowych oraz zespo&#322;y pracuj&#261;ce nad nimi w r&oacute;&#380;nym tempie. Je&#347;li katalog produkt&oacute;w wymaga cz&#281;stych wdro&#380;e&#324;, a modu&#322; raportowy obci&#261;&#380;a baz&#281; tylko raz dziennie, mo&#380;na rozwija&#263; i skalowa&#263; te elementy osobno. Zyskujemy wtedy <strong>autonomi&#281; zespo&#322;&oacute;w i bardziej precyzyjne wykorzystanie infrastruktury</strong>.</p><p>Cena jest konkretna. Zamiast jednego procesu pojawiaj&#261; si&#281; sie&#263;, kontenery, logi z wielu instancji, problemy z cz&#281;&#347;ciow&#261; awari&#261; i trudniejsze <a href="https://kursdotnet.pl/architektura-heksagonalna-w-net-porty-adaptery-i-praktyka">testy integracyjne</a>. Dlatego dla ma&#322;ego systemu, kt&oacute;ry rozwija 2-4 osoby, rozs&#261;dniejszy bywa <strong>modularny monolit</strong>. Pozwala zachowa&#263; granice domenowe, ale nie wymusza od razu ca&#322;ej infrastruktury rozproszonej.</p><h2 id="granice-uslug-trzeba-wyprowadzic-z-domeny">Granice us&#322;ug trzeba wyprowadzi&#263; z domeny</h2><p>Najcz&#281;stszy b&#322;&#261;d polega na dzieleniu aplikacji wed&#322;ug warstw technicznych. Osobny serwis dla kontroler&oacute;w, osobny dla repozytori&oacute;w i osobny dla logiki biznesowej nie daje mikroserwis&oacute;w. Daje rozproszon&#261; aplikacj&#281;, w kt&oacute;rej ka&#380;da prosta zmiana wymaga wielu wdro&#380;e&#324;.</p><p>Lepszym punktem wyj&#347;cia jest analiza proces&oacute;w biznesowych. W sklepie internetowym naturalnymi kandydatami mog&#261; by&#263; <strong>Orders, Catalog, Basket i Payments</strong>, poniewa&#380; ka&#380;dy obszar ma inne regu&#322;y, dane oraz tempo zmian. Nie oznacza to, &#380;e ka&#380;dy z nich od razu musi sta&#263; si&#281; osobnym wdro&#380;eniem. Najpierw warto ustali&#263;, gdzie przebiegaj&#261; granice odpowiedzialno&#347;ci.</p><h3 id="jedna-usluga-jeden-wlasciciel-danych">Jedna us&#322;uga, jeden w&#322;a&#347;ciciel danych</h3><p>Regu&#322;a &bdquo;jedna baza na ca&#322;y system&rdquo; szybko niszczy niezale&#380;no&#347;&#263; us&#322;ug. Je&#347;li dwa mikroserwisy zapisuj&#261; te same tabele, ich wdro&#380;enia s&#261; ze sob&#261; zwi&#261;zane, a zmiana schematu wymaga koordynacji wszystkich konsument&oacute;w. Zdecydowanie bezpieczniej jest, gdy us&#322;uga <strong>posiada w&#322;asny model danych</strong>, nawet je&#347;li kilka baz dzia&#322;a w tym samym silniku SQL.</p><p>W&#322;asna baza nie oznacza, &#380;e ka&#380;da us&#322;uga musi u&#380;ywa&#263; innej technologii. Orders i Payments mog&#261; korzysta&#263; z SQL Servera, a Catalog mo&#380;e u&#380;ywa&#263; PostgreSQL albo dokumentowej bazy danych, je&#347;li uzasadnia to spos&oacute;b wyszukiwania. Wa&#380;niejsze od r&oacute;&#380;norodno&#347;ci technologii jest to, aby inne us&#322;ugi nie omija&#322;y API i nie czyta&#322;y cudzych tabel.</p><h3 id="nie-przesadzaj-z-rozmiarem">Nie przesadzaj z rozmiarem</h3><p>Mikroserwis nie powinien by&#263; tak ma&#322;y, &#380;e ka&#380;da zmiana biznesowa wymaga komunikacji mi&#281;dzy pi&#281;cioma procesami. Je&#347;li utworzenie zam&oacute;wienia wymaga dziesi&#281;ciu synchronicznych wywo&#322;a&#324;, system staje si&#281; kruchy i trudny do diagnozowania. W praktyce zaczynam od <strong>2-3 dobrze wyznaczonych modu&#322;&oacute;w</strong>, a dalszy podzia&#322; uzale&#380;niam od rzeczywistych potrzeb.</p><p>Pomocne pytanie brzmi: czy ten fragment mo&#380;e mie&#263; w&#322;asny cykl &#380;ycia, w&#322;asne regu&#322;y i w&#322;asne dane? Je&#347;li odpowied&#378; jest negatywna, prawdopodobnie nie ma jeszcze powodu, aby wydziela&#263; go do osobnej us&#322;ugi.</p><h2 id="komunikacja-i-spojnosc-danych-wymagaja-swiadomych-kompromisow">Komunikacja i sp&oacute;jno&#347;&#263; danych wymagaj&#261; &#347;wiadomych kompromis&oacute;w</h2><p>W systemie rozproszonym ka&#380;de wywo&#322;anie mi&#281;dzy us&#322;ugami jest operacj&#261; sieciow&#261;. Mo&#380;e si&#281; op&oacute;&#378;ni&#263;, zako&#324;czy&#263; b&#322;&#281;dem albo zosta&#263; wykonane, mimo &#380;e odpowied&#378; nie dotar&#322;a do nadawcy. Z tego powodu projektuj&#281; komunikacj&#281; tak, aby <strong>awaria jednej us&#322;ugi nie zatrzymywa&#322;a ca&#322;ego systemu</strong>.</p><h3 id="http-grpc-czy-komunikaty">HTTP, gRPC czy komunikaty</h3><table>
<tbody>
<tr>
<th>Mechanizm</th>
<th>Kiedy pasuje</th>
<th>G&#322;&oacute;wne ryzyko</th>
</tr>
<tr>
<td>HTTP/REST</td>
<td>Publiczne API, proste operacje i integracje z innymi systemami</td>
<td>Zale&#380;no&#347;&#263; od dost&#281;pno&#347;ci us&#322;ugi wywo&#322;ywanej w danym momencie</td>
</tr>
<tr>
<td>gRPC</td>
<td>Szybka komunikacja wewn&#281;trzna mi&#281;dzy us&#322;ugami z kontrolowanym kontraktem</td>
<td>Wi&#281;ksza z&#322;o&#380;ono&#347;&#263; po stronie klient&oacute;w i mniejsza wygoda dla zewn&#281;trznych konsument&oacute;w</td>
</tr>
<tr>
<td>Broker wiadomo&#347;ci</td>
<td>Procesy asynchroniczne, integracje i reakcje na zdarzenia domenowe</td>
<td>Op&oacute;&#378;niona sp&oacute;jno&#347;&#263; oraz konieczno&#347;&#263; obs&#322;ugi ponowie&#324;</td>
</tr>
</tbody>
</table><p>REST jest dobrym domy&#347;lnym wyborem dla publicznego API. gRPC, czyli binarny protok&oacute;&#322; komunikacyjny oparty na kontraktach definiowanych w plikach Protobuf, ma sens przy intensywnej komunikacji wewn&#281;trznej. Broker wiadomo&#347;ci, taki jak <a href="https://kursdotnet.pl/rabbitmq-vs-kafka-ktory-broker-pasuje-do-twojej-aplikacji">RabbitMQ</a>, Azure Service Bus albo Kafka, oddziela nadawc&#281; od odbiorcy i pozwala kontynuowa&#263; prac&#281; nawet wtedy, gdy konsument jest chwilowo niedost&#281;pny.</p><h3 id="outbox-i-saga-zamiast-jednej-transakcji">Outbox i Saga zamiast jednej transakcji</h3><p>Za&#322;&oacute;&#380;my, &#380;e Orders zapisuje zam&oacute;wienie w bazie, a potem publikuje zdarzenie OrderCreated. Je&#347;li proces zako&#324;czy si&#281; pomi&#281;dzy tymi operacjami, zam&oacute;wienie istnieje, ale Payments nie dostanie informacji o jego utworzeniu. Wzorzec <strong>Transactional Outbox</strong> zapisuje dane biznesowe i zdarzenie w jednej lokalnej transakcji, a osobny proces publikuje komunikat p&oacute;&#378;niej.</p><p>Gdy proces obejmuje kilka us&#322;ug, przydaje si&#281; Saga. Ka&#380;da us&#322;uga wykonuje w&#322;asn&#261; transakcj&#281;, a w razie problemu uruchamiana jest akcja kompensuj&#261;ca, na przyk&#322;ad anulowanie rezerwacji albo zwrot p&#322;atno&#347;ci. To nie jest pe&#322;ny zamiennik transakcji ACID, dlatego trzeba zaakceptowa&#263; <strong>sp&oacute;jno&#347;&#263; ostateczn&#261;</strong> i jasno opisa&#263; stany po&#347;rednie.</p><p>Ka&#380;dy konsument komunikat&oacute;w powinien by&#263; idempotentny. Oznacza to, &#380;e ponowne przetworzenie tego samego zdarzenia nie mo&#380;e podw&oacute;jnie pobra&#263; p&#322;atno&#347;ci ani utworzy&#263; drugiej wysy&#322;ki. W praktyce stosuj&#281; identyfikator wiadomo&#347;ci, tabel&#281; przetworzonych komunikat&oacute;w oraz retry z ograniczeniem liczby pr&oacute;b.</p><h2 id="budowa-mikroserwisu-w-aspnet-core">Budowa mikroserwisu w ASP.NET Core</h2><p>Typowa us&#322;uga mo&#380;e by&#263; aplikacj&#261; ASP.NET Core Web API uruchamian&#261; w kontenerze. Jej publiczna cz&#281;&#347;&#263; powinna by&#263; niewielka, a logika biznesowa oddzielona od infrastruktury. Dla prostego CRUD-u wystarczy prostszy uk&#322;ad, natomiast w bardziej z&#322;o&#380;onym module lepiej sprawdza si&#281; <strong>Clean Architecture lub podej&#347;cie heksagonalne</strong>.</p><p>Nie stosuj&#281; CQRS, czyli rozdzielenia modeli odczytu i zapisu, automatycznie w ka&#380;dej us&#322;udze. Jest przydatne wtedy, gdy odczyty i zapisy maj&#261; r&oacute;&#380;ne wymagania, model domenowy jest z&#322;o&#380;ony albo potrzebujemy osobnych &#347;cie&#380;ek skalowania. Przy zwyk&#322;ym katalogu produkt&oacute;w dodatkowa warstwa mo&#380;e tylko zwi&#281;kszy&#263; ilo&#347;&#263; kodu.</p><p>Minimalny punkt startowy mo&#380;e wygl&#261;da&#263; tak:</p><pre><code>var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddHealthChecks();

var app = builder.Build();

app.MapControllers();
app.MapHealthChecks("/health");

app.Run();</code></pre><p>Sam endpoint zdrowia nie wystarcza do monitorowania aplikacji. Warto rozdzieli&#263; <strong>liveness</strong>, czyli informacj&#281;, czy proces dzia&#322;a, od <strong>readiness</strong>, kt&oacute;ra m&oacute;wi, czy us&#322;uga mo&#380;e przyjmowa&#263; ruch. Je&#347;li baza danych jest niedost&#281;pna, proces mo&#380;e nadal dzia&#322;a&#263;, ale nie powinien otrzymywa&#263; nowych &#380;&#261;da&#324; wymagaj&#261;cych zapisu.</p><p>Kontrakt API powinien by&#263; wersjonowany ostro&#380;nie. Dodanie opcjonalnego pola zwykle jest bezpieczniejsze ni&#380; zmiana znaczenia istniej&#261;cego pola. Przy komunikatach asynchronicznych trzeba zachowa&#263; kompatybilno&#347;&#263; wsteczn&#261;, poniewa&#380; starszy konsument mo&#380;e przetwarza&#263; zdarzenia jeszcze d&#322;ugo po wdro&#380;eniu nowej wersji producenta.</p><p><img src="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/post_image/0b3b94ac2e7a160c47a6d40983de5a81/net-microservices-architecture-diagram-aspnet-core-docker-message-broker-kubernetes.webp" class="image article-image" loading="lazy" alt="Architektura aplikacji eShop z wykorzystaniem .NET microservices, obejmuj&#261;ca aplikacje klienckie, Docker Host i komunikacj&#281; przez Event Bus."></p><h2 id="wdrazanie-wymaga-automatyzacji-i-obserwowalnosci">Wdra&#380;anie wymaga automatyzacji i obserwowalno&#347;ci</h2><p>Kontener upraszcza powtarzalne uruchamianie us&#322;ugi, ale nie rozwi&#261;zuje zarz&#261;dzania ca&#322;ym systemem. W &#347;rodowisku lokalnym przydaje si&#281; Docker Compose, natomiast wi&#281;ksze instalacje mog&#261; korzysta&#263; z Kubernetes, Azure Container Apps albo innych platform kontenerowych. Wyb&oacute;r zale&#380;y od potrzeb zespo&#322;u, a nie od mody na konkretny orchestrator.</p><p>.NET Aspire u&#322;atwia lokalne uruchamianie aplikacji sk&#322;adaj&#261;cej si&#281; z wielu projekt&oacute;w, baz danych i zale&#380;no&#347;ci. Szczeg&oacute;lnie ceni&#281; go podczas developmentu, gdy chc&#281; szybko sprawdzi&#263; konfiguracj&#281; po&#322;&#261;cze&#324;, health checki oraz podstawow&#261; telemetri&#281;. Do produkcji nadal trzeba &#347;wiadomie wybra&#263; docelow&#261; platform&#281;, polityk&#281; sekret&oacute;w, skalowanie i spos&oacute;b aktualizacji.</p><p>Pipeline CI/CD powinien budowa&#263; obraz, uruchamia&#263; testy, skanowa&#263; zale&#380;no&#347;ci i wdra&#380;a&#263; konkretn&#261; wersj&#281; artefaktu. Nie warto budowa&#263; obrazu ponownie dla ka&#380;dego &#347;rodowiska. Bezpieczniejszy model polega na tym, &#380;e <strong>ten sam obraz przechodzi przez test, staging i produkcj&#281;</strong>, a r&oacute;&#380;ni&#261; si&#281; tylko konfiguracja oraz sekrety.</p><p class="read-more"><strong>Przeczytaj r&oacute;wnie&#380;: <a href="https://kursdotnet.pl/solid-w-c-bez-przesady-zasady-przyklady-i-refaktoryzacja">SOLID w C# bez przesady - zasady, przyk&#322;ady i refaktoryzacja</a></strong></p><h3 id="co-obserwowac-od-pierwszego-dnia">Co obserwowa&#263; od pierwszego dnia</h3><ul>
<li>
<strong>Logi strukturalne</strong> z identyfikatorem korelacji, nazw&#261; operacji i identyfikatorem u&#380;ytkownika.</li>
<li>
<strong>Metryki</strong> dotycz&#261;ce czasu odpowiedzi, liczby b&#322;&#281;d&oacute;w, d&#322;ugo&#347;ci kolejek i wykorzystania zasob&oacute;w.</li>
<li>
<strong>&#346;lady rozproszone</strong>, kt&oacute;re pokazuj&#261; przebieg jednego &#380;&#261;dania przez kilka us&#322;ug.</li>
<li>Alerty dla op&oacute;&#378;nie&#324;, wzrostu b&#322;&#281;d&oacute;w, niedost&#281;pno&#347;ci zale&#380;no&#347;ci i zalegaj&#261;cych komunikat&oacute;w.</li>
</ul><p>Retry bez limitu potrafi zamieni&#263; chwilowy problem w lawin&#281; &#380;&#261;da&#324;. Dlatego &#322;&#261;cz&#281; go z timeoutem, backoffem i circuit breakerem. Ten ostatni czasowo odcina wywo&#322;ania do niesprawnej us&#322;ugi, dzi&#281;ki czemu pozosta&#322;e komponenty zachowuj&#261; <strong>cz&#281;&#347;ciow&#261; sprawno&#347;&#263; zamiast czeka&#263; bez ko&#324;ca</strong>.</p><h2 id="najwieksze-bledy-wynikaja-z-przeceniania-technologii">Najwi&#281;ksze b&#322;&#281;dy wynikaj&#261; z przeceniania technologii</h2><p>Najcz&#281;&#347;ciej widz&#281; systemy, w kt&oacute;rych zesp&oacute;&#322; zaczyna od Kubernetes, brokera i kilkunastu projekt&oacute;w, zanim ustali model domeny. Infrastruktura wygl&#261;da wtedy profesjonalnie, ale ka&#380;da zmiana wymaga r&#281;cznego synchronizowania wielu element&oacute;w. Mikroserwisy nie naprawi&#261; niejasnych regu&#322; biznesowych ani z&#322;ej komunikacji w zespole.</p><table>
<tbody>
<tr>
<th>Sytuacja</th>
<th>Rozs&#261;dny wyb&oacute;r</th>
<th>Dlaczego</th>
</tr>
<tr>
<td>Ma&#322;a aplikacja i jeden zesp&oacute;&#322;</td>
<td>Modularny monolit</td>
<td>Mniej operacyjnych zale&#380;no&#347;ci i prostsze testy</td>
</tr>
<tr>
<td>Kilka niezale&#380;nych obszar&oacute;w biznesowych</td>
<td>Stopniowe wydzielanie us&#322;ug</td>
<td>Mo&#380;liwo&#347;&#263; skalowania i wdra&#380;ania wybranych modu&#322;&oacute;w</td>
</tr>
<tr>
<td>Du&#380;o proces&oacute;w asynchronicznych</td>
<td>Mikroserwisy z brokerem zdarze&#324;</td>
<td>Lepsze odseparowanie producent&oacute;w i konsument&oacute;w</td>
</tr>
<tr>
<td>Monolit z trudnym modu&#322;em do zmiany</td>
<td>Strangler Fig</td>
<td>Stopniowa migracja bez przepisywania ca&#322;o&#347;ci</td>
</tr>
</tbody>
</table><p>Nie zaczyna&#322;bym te&#380; od dzielenia wsp&oacute;lnego modelu klas mi&#281;dzy wszystkie us&#322;ugi. Wsp&oacute;lna biblioteka DTO mo&#380;e szybko sta&#263; si&#281; ukryt&#261; zale&#380;no&#347;ci&#261;, kt&oacute;ra blokuje niezale&#380;ne wersjonowanie. Lepiej wsp&oacute;&#322;dzieli&#263; kontrakty tylko wtedy, gdy jest to &#347;wiadoma decyzja, a nie wygodny skr&oacute;t.</p><p>Najbezpieczniejsza &#347;cie&#380;ka migracji prowadzi przez <strong>modularny monolit, pomiary i jedno kontrolowane wydzielenie</strong>. Najpierw mo&#380;na oddzieli&#263; modu&#322;, kt&oacute;ry ma wyra&#378;ne granice i realny problem ze skalowaniem lub tempem zmian. Dopiero dane z produkcji poka&#380;&#261;, czy kolejne us&#322;ugi przynios&#261; korzy&#347;&#263;.</p><h2 id="od-czego-zaczac-pierwszy-projekt-mikroserwisowy">Od czego zacz&#261;&#263; pierwszy projekt mikroserwisowy</h2><p>Wybra&#322;bym niewielki, ale kompletny przep&#322;yw biznesowy, na przyk&#322;ad utworzenie zam&oacute;wienia. Powinien obejmowa&#263; API, walidacj&#281;, zapis danych, komunikat domenowy, obs&#322;ug&#281; b&#322;&#281;du i podstawowe metryki. Taki zakres uczy wi&#281;cej ni&#380; pi&#281;&#263; atrap us&#322;ug, kt&oacute;re tylko zwracaj&#261; dane z pami&#281;ci.</p><p>Przed wdro&#380;eniem sprawdzi&#322;bym cztery rzeczy. Czy ka&#380;da us&#322;uga mo&#380;e dzia&#322;a&#263; bez dost&#281;pu do cudzej bazy? Czy wiadomo, co dzieje si&#281; po timeoutach i ponowieniu komunikatu? Czy da si&#281; znale&#378;&#263; jedno &#380;&#261;danie w logach wszystkich komponent&oacute;w? Czy zesp&oacute;&#322; potrafi wdro&#380;y&#263; zmian&#281; bez r&#281;cznej koordynacji kilku repozytori&oacute;w?</p><p>Je&#347;li odpowiedzi s&#261; twierdz&#261;ce, technologia zaczyna pracowa&#263; na rzecz architektury. Je&#347;li nie, kolejne biblioteki tylko zamaskuj&#261; problem. W mikroserwisach najwi&#281;ksz&#261; przewag&#281; daje nie liczba kontener&oacute;w, lecz <strong>jasna odpowiedzialno&#347;&#263;, odporna komunikacja i mo&#380;liwo&#347;&#263; bezpiecznej zmiany</strong>.</p>]]></content:encoded>
      <author>Bruno Krawczyk</author>
      <category>Architektura i wzorce</category>
      <media:thumbnail url="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/thumbnail/a3627acda89f4de3a216b632332cbf52/mikroserwisy-w-net-bez-chaosu-granice-wzorce-i-wdrozenie.webp"/>
      <pubDate>Tue, 15 Sep 2026 14:15:00 +0200</pubDate>
    </item>
    <item>
      <title>ORM w C# bez pułapek - EF Core, Dapper i wydajne zapytania</title>
      <link>https://kursdotnet.pl/orm-w-c-bez-pulapek-ef-core-dapper-i-wydajne-zapytania</link>
      <description>ORM w C# bez zgadywania: porównaj EF Core i Dappera, poznaj wydajne zapytania, projekcje i pułapki. Sprawdź praktyczny przewodnik.</description>
      <content:encoded><![CDATA[<?xml encoding="utf-8" ?><p>Gdy aplikacja w C# zaczyna korzysta&#263; z wi&#281;kszej liczby tabel, r&#281;czne sk&#322;adanie zapyta&#324; SQL szybko staje si&#281; &#378;r&oacute;d&#322;em powtarzalnego kodu i trudnych do wykrycia b&#322;&#281;d&oacute;w. ORM pomaga po&#322;&#261;czy&#263; klasy, relacje i operacje biznesowe z baz&#261; danych, ale nie zwalnia z rozumienia SQL. Poka&#380;&#281;, jak dzia&#322;a mapowanie obiektowo-relacyjne, kiedy wybra&#263; EF Core albo Dappera, jak pisa&#263; wydajne zapytania oraz kt&oacute;rych pu&#322;apek unika&#263;.</p><div class="short-summary">
  <h2 id="najwazniejsze-decyzje-przy-wyborze-orm-w-c">Najwa&#380;niejsze decyzje przy wyborze ORM w C#</h2>
  <ul>
    <li>
<strong>EF Core</strong> sprawdzi si&#281; jako domy&#347;lny wyb&oacute;r w wi&#281;kszo&#347;ci aplikacji .NET.</li>
    <li>
<strong>Dapper</strong> daje wi&#281;ksz&#261; kontrol&#281; nad SQL-em i niewielki narzut wykonania.</li>
    <li>
<strong>AsNoTracking()</strong> ma sens przy odczytach, kt&oacute;rych nie b&#281;dziemy zapisywa&#263;.</li>
    <li>
<strong>Projekcja do DTO</strong> ogranicza liczb&#281; kolumn i ilo&#347;&#263; danych przesy&#322;anych z bazy.</li>
    <li><strong>ORM nie zast&#281;puje znajomo&#347;ci indeks&oacute;w, plan&oacute;w zapyta&#324; i transakcji.</strong></li>
  </ul>
</div><h2 id="na-czym-polega-orm-w-aplikacji-c">Na czym polega ORM w aplikacji C#</h2><p>ORM, czyli Object-Relational Mapping, t&#322;umaczy obiekty aplikacji na rekordy relacyjnej bazy danych. Klasa <code>Product</code> mo&#380;e odpowiada&#263; tabeli <code>Products</code>, jej w&#322;a&#347;ciwo&#347;&#263; <code>Name</code> kolumnie, a kolekcja zam&oacute;wie&#324; relacji jeden do wielu. Dzi&#281;ki temu programista pracuje g&#322;&oacute;wnie z kodem C#, a framework generuje znaczn&#261; cz&#281;&#347;&#263; SQL-a.</p><p>To wygodne, ale nie jest magi&#261;. ORM musi rozstrzygn&#261;&#263;, jak odwzorowa&#263; typy, <a href="https://kursdotnet.pl/oracle-create-table-od-podstaw-klucze-identity-i-ctas">klucze g&#322;&oacute;wne</a>, warto&#347;ci <code>null</code>, relacje i moment zapisu zmian. Im bardziej model obiektowy r&oacute;&#380;ni si&#281; od schematu bazy, tym wi&#281;ksz&#261; rol&#281; odgrywa konfiguracja mapowania.</p><h3 id="co-zyskujemy">Co zyskujemy</h3><ul>
  <li>mniej powtarzalnego kodu ADO.NET,</li>
  <li>silne typowanie zapyta&#324; dzi&#281;ki LINQ,</li>
  <li>automatyczne mapowanie wynik&oacute;w na obiekty,</li>
  <li>obs&#322;ug&#281; relacji i &#347;ledzenia zmian,</li>
  <li>migracje pozwalaj&#261;ce wersjonowa&#263; schemat bazy.</li>
</ul><p>W praktyce najwi&#281;ksz&#261; korzy&#347;ci&#261; jest <strong>sp&oacute;jno&#347;&#263; kodu dost&#281;pu do danych</strong>. Zamiast r&#281;cznie otwiera&#263; po&#322;&#261;czenie, tworzy&#263; komend&#281;, dodawa&#263; parametry i mapowa&#263; ka&#380;d&#261; kolumn&#281;, opisujemy operacj&#281; na poziomie modelu aplikacji.</p><h3 id="gdzie-pojawia-sie-koszt">Gdzie pojawia si&#281; koszt</h3><p>Warstwa abstrakcji mo&#380;e generowa&#263; zapytania, kt&oacute;rych nie napisano by r&#281;cznie. Problemem bywaj&#261; te&#380; niejawne operacje, na przyk&#322;ad automatyczne doci&#261;ganie powi&#261;zanych danych albo pobieranie ca&#322;ych encji, gdy potrzebne s&#261; tylko dwie kolumny.</p><p>Dlatego moje podej&#347;cie jest proste: <strong>ORM powinien u&#322;atwia&#263; prac&#281;, ale SQL nadal trzeba umie&#263; czyta&#263;</strong>. Gdy zapytanie dzia&#322;a wolno, sprawdzam wygenerowany SQL i plan wykonania zamiast zgadywa&#263;, kt&oacute;ry fragment C# jest winny.</p><h2 id="dlaczego-ef-core-jest-najczestszym-wyborem">Dlaczego EF Core jest najcz&#281;stszym wyborem</h2><p><a href="https://kursdotnet.pl/auto-increment-w-sql-id-luki-i-roznice-miedzy-bazami">Entity Framework Core</a> jest obecnie najbardziej naturalnym wyborem dla aplikacji budowanych w ekosystemie .NET. Jest wieloplatformowy, open source, wsp&oacute;&#322;pracuje z popularnymi silnikami baz danych i integruje si&#281; z ASP.NET Core przez wstrzykiwanie zale&#380;no&#347;ci.</p><p>Centralnym elementem jest <code>DbContext</code>. Reprezentuje sesj&#281; pracy z baz&#261;, udost&#281;pnia zbiory encji przez <code>DbSet<t></t></code> i pilnuje zmian, kt&oacute;re maj&#261; zosta&#263; zapisane.</p><pre><code>public sealed class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public bool IsActive { get; set; }
}

public sealed class ShopDbContext : DbContext
{
    public DbSet<product> Products =&gt; Set<product>();

    public ShopDbContext(DbContextOptions<shopdbcontext> options)
        : base(options)
    {
    }
}</shopdbcontext></product></product></code></pre><p>Proste pobranie danych mo&#380;e wygl&#261;da&#263; tak:</p><pre><code>var products = await db.Products
    .Where(p =&gt; p.IsActive)
    .OrderBy(p =&gt; p.Name)
    .ToListAsync();</code></pre><p>Wyra&#380;enie LINQ nie wykonuje si&#281; od razu. Do momentu wywo&#322;ania metody ko&#324;cz&#261;cej zapytanie, takiej jak <code>ToListAsync()</code>, budowane jest wyra&#380;enie, kt&oacute;re provider pr&oacute;buje przet&#322;umaczy&#263; na SQL. To wa&#380;ne, poniewa&#380; nie ka&#380;da metoda C# ma odpowiednik mo&#380;liwy do wykonania po stronie bazy.</p><h3 id="konfiguracja-modelu">Konfiguracja modelu</h3><p>Mapowanie mo&#380;na opiera&#263; na konwencjach, atrybutach albo Fluent API. Przy ma&#322;ym projekcie konwencje cz&#281;sto wystarczaj&#261;. W wi&#281;kszej aplikacji preferuj&#281; Fluent API, bo konfiguracja relacji, indeks&oacute;w i ogranicze&#324; pozostaje w jednym, &#322;atwiejszym do testowania miejscu.</p><pre><code>protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<product>(entity =&gt;
    {
        entity.Property(p =&gt; p.Name)
            .HasMaxLength(200)
            .IsRequired();

        entity.Property(p =&gt; p.Price)
            .HasPrecision(18, 2);

        entity.HasIndex(p =&gt; new { p.IsActive, p.Name });
    });
}</product></code></pre><p>EF Core obs&#322;uguje te&#380; migracje, czyli kolejne wersje zmian schematu bazy. To wygodne w zespo&#322;ach, ale migracje powinny przechodzi&#263; przez kontrol&#281; kodu i by&#263; testowane na kopii danych. <strong>Migracja nie zast&#281;puje strategii wdro&#380;enia</strong>, szczeg&oacute;lnie gdy zmiana tabeli dotyczy milion&oacute;w rekord&oacute;w.</p><h2 id="ef-core-dapper-czy-nhibernate">EF Core, Dapper czy NHibernate</h2><p>Nie ma jednego ORM odpowiedniego dla ka&#380;dej aplikacji. Najlepszy wyb&oacute;r zale&#380;y od tego, czy wa&#380;niejsza jest szybko&#347;&#263; tworzenia funkcji, kontrola nad SQL-em, rozbudowane mapowanie czy mo&#380;liwie ma&#322;a liczba abstrakcji.</p><table>
  <tbody>
    <tr>
      <th>Narz&#281;dzie</th>
      <th>Najmocniejsza strona</th>
      <th>Dobre zastosowanie</th>
      <th>Ograniczenie</th>
    </tr>
    <tr>
      <td>EF Core</td>
      <td>Pe&#322;ny ORM, LINQ, migracje i &#347;ledzenie zmian</td>
      <td>API, aplikacje biznesowe, typowe systemy CRUD</td>
      <td>Trzeba kontrolowa&#263; generowany SQL i spos&oacute;b &#322;adowania relacji</td>
    </tr>
    <tr>
      <td>Dapper</td>
      <td>Bezpo&#347;rednia kontrola nad zapytaniami SQL</td>
      <td>Raporty, odczyty, procedury i wymagaj&#261;ce zapytania</td>
      <td>Wi&#281;cej r&#281;cznego kodu i brak pe&#322;nego change trackera</td>
    </tr>
    <tr>
      <td>NHibernate</td>
      <td>Rozbudowane mapowanie i dojrza&#322;y model sesji</td>
      <td>Du&#380;e systemy z istniej&#261;cym modelem domenowym</td>
      <td>Wy&#380;szy pr&oacute;g wej&#347;cia i mniej typowa &#347;cie&#380;ka w nowych projektach .NET</td>
    </tr>
  </tbody>
</table><p>Dapper jest mikro-ORM-em, kt&oacute;ry upraszcza prac&#281; z ADO.NET, ale pozostawia SQL w r&#281;kach programisty. Przyk&#322;ad jest kr&oacute;tki i czytelny:</p><pre><code>const string sql = """
    SELECT Id, Name, Price
    FROM Products
    WHERE IsActive = @IsActive
    ORDER BY Name
    """;

var products = await connection.QueryAsync<productdto>(
    sql,
    new { IsActive = true });</productdto></code></pre><p>W tym podej&#347;ciu zapytanie jest jawne, a parametry chroni&#261; przed typowym SQL injection. Dapper nie &#347;ledzi zmian encji i nie zarz&#261;dza relacjami tak jak EF Core, wi&#281;c dobrze pasuje do miejsc, w kt&oacute;rych chcemy <strong>dok&#322;adnie kontrolowa&#263; wynik i koszt zapytania</strong>.</p><p>W wielu systemach rozs&#261;dny jest model mieszany. EF Core obs&#322;uguje standardowe operacje domenowe, a Dapper dostarcza kilka wyspecjalizowanych zapyta&#324; raportowych. Nie widz&#281; sensu wymienia&#263; ca&#322;ego ORM tylko dlatego, &#380;e jeden raport wymaga r&#281;cznie napisanego SQL-a.</p><h2 id="jak-pisac-wydajne-zapytania-w-ef-core">Jak pisa&#263; wydajne zapytania w EF Core</h2><p>Najcz&#281;stszy problem nie wynika z samego ORM, lecz z tego, &#380;e aplikacja pobiera za du&#380;o danych. Tabela z kilkuset tysi&#261;cami wierszy mo&#380;e dzia&#322;a&#263; dobrze w testach, a potem spowolni&#263; endpoint produkcyjny, gdy kto&#347; usunie filtr albo zwr&oacute;ci pe&#322;n&#261; list&#281; bez paginacji.</p><h3 id="pobieraj-tylko-potrzebne-kolumny">Pobieraj tylko potrzebne kolumny</h3><p>Je&#347;li ekran wy&#347;wietla nazw&#281; i cen&#281;, nie ma powodu &#322;adowa&#263; ca&#322;ej encji z opisem, datami, metadanymi i relacjami. Projekcja do DTO ogranicza transfer danych oraz ilo&#347;&#263; obiekt&oacute;w tworzonych w pami&#281;ci.</p><pre><code>var result = await db.Products
    .Where(p =&gt; p.IsActive)
    .Select(p =&gt; new ProductListItem(
        p.Id,
        p.Name,
        p.Price))
    .OrderBy(p =&gt; p.Name)
    .Take(50)
    .ToListAsync();</code></pre><p>To zwykle daje wi&#281;kszy efekt ni&#380; mechaniczne dodawanie kolejnych warstw cache. <strong>Najta&#324;szy rekord to ten, kt&oacute;rego nie trzeba pobra&#263;</strong>.</p><h3 id="wylacz-sledzenie-przy-odczytach">Wy&#322;&#261;cz &#347;ledzenie przy odczytach</h3><p>EF Core domy&#347;lnie &#347;ledzi encje, aby wykry&#263; ich zmiany przy <code>SaveChangesAsync()</code>. Dla list referencyjnych, raport&oacute;w i widok&oacute;w tylko do odczytu ten mechanizm cz&#281;sto nie jest potrzebny.</p><pre><code>var categories = await db.Categories
    .AsNoTracking()
    .OrderBy(c =&gt; c.Name)
    .ToListAsync();</code></pre><p><code>AsNoTracking()</code> mo&#380;e zmniejszy&#263; narzut pami&#281;ci i czas materializacji, ale nie jest uniwersalnym przyspieszaczem. Je&#347;li w ramach jednego kontekstu wielokrotnie pracujemy z tymi samymi encjami albo chcemy je p&oacute;&#378;niej zmieni&#263;, &#347;ledzenie mo&#380;e by&#263; wygodniejsze i czasem bardziej efektywne.</p><h3 id="kontroluj-relacje-i-problem-n1">Kontroluj relacje i problem N+1</h3><p>Problem N+1 pojawia si&#281; wtedy, gdy aplikacja wykonuje jedno zapytanie po list&#281; g&#322;&oacute;wn&#261;, a potem osobne zapytanie dla ka&#380;dego elementu. Przy 100 zam&oacute;wieniach mo&#380;e to oznacza&#263; 101 wywo&#322;a&#324; bazy zamiast jednego dobrze zaprojektowanego zapytania.</p><p>Jawne <code>Include()</code> mo&#380;e pom&oacute;c, ale nie powinno by&#263; dodawane bez zastanowienia. Przy kilku relacjach jeden do wielu wynik mo&#380;e zawiera&#263; wiele powt&oacute;rzonych danych. W takich przypadkach przydaje si&#281; projekcja do DTO albo <code>AsSplitQuery()</code>, kt&oacute;re rozdziela pobieranie relacji na osobne zapytania.</p><pre><code>var orders = await db.Orders
    .Where(o =&gt; o.CustomerId == customerId)
    .Select(o =&gt; new OrderSummary(
        o.Id,
        o.CreatedAt,
        o.Items.Sum(i =&gt; i.Quantity * i.UnitPrice)))
    .ToListAsync();</code></pre><p>Ten zapis jest cz&#281;sto lepszy ni&#380; pobranie ca&#322;ego grafu obiekt&oacute;w i liczenie sum w C#. Baza danych zosta&#322;a stworzona do filtrowania, grupowania i agregowania, wi&#281;c warto pozwoli&#263; jej wykona&#263; t&#281; prac&#281;.</p><h3 id="indeksy-i-paginacja-maja-wieksze-znaczenie-niz-drobne-optymalizacje">Indeksy i paginacja maj&#261; wi&#281;ksze znaczenie ni&#380; drobne optymalizacje</h3><p>Indeks powinien odpowiada&#263; realnym filtrom i sortowaniu. Je&#380;eli u&#380;ytkownicy cz&#281;sto filtruj&#261; produkty po <code>IsActive</code> i sortuj&#261; po <code>Name</code>, taki scenariusz warto sprawdzi&#263; w planie wykonania, a nie tylko w kodzie encji.</p><p>Unikaj r&oacute;wnie&#380; <code>Skip()</code> na bardzo odleg&#322;ych stronach du&#380;ych tabel, poniewa&#380; baza mo&#380;e najpierw odczyta&#263; i odrzuci&#263; wiele wcze&#347;niejszych rekord&oacute;w. Przy du&#380;ych zbiorach lepsza bywa <strong>paginacja keyset</strong>, oparta na ostatnim znanym identyfikatorze lub dacie.</p><h2 id="transakcje-sql-i-granice-abstrakcji">Transakcje, SQL i granice abstrakcji</h2><p>ORM upraszcza dost&#281;p do danych, ale nie usuwa potrzeby projektowania transakcji. Je&#380;eli operacja tworzy zam&oacute;wienie, rezerwuje stan magazynowy i zapisuje p&#322;atno&#347;&#263;, trzeba jasno okre&#347;li&#263;, kt&oacute;re zmiany musz&#261; zosta&#263; zatwierdzone razem.</p><pre><code>await using var transaction = await db.Database.BeginTransactionAsync();

try
{
    db.Orders.Add(order);
    await db.SaveChangesAsync();

    stock.Reserve(order);
    await db.SaveChangesAsync();

    await transaction.CommitAsync();
}
catch
{
    await transaction.RollbackAsync();
    throw;
}</code></pre><p>W prostych przypadkach pojedyncze <code>SaveChangesAsync()</code> samo korzysta z transakcji, ale przy kilku zapisach lub wsp&oacute;&#322;pracy z innymi zasobami potrzebna jest &#347;wiadoma decyzja. Nie warto te&#380; trzyma&#263; transakcji otwartej podczas wywo&#322;ania zewn&#281;trznego API, bo blokady mog&#261; utrzymywa&#263; si&#281; znacznie d&#322;u&#380;ej, ni&#380; zak&#322;adamy.</p><p class="read-more"><strong>Przeczytaj r&oacute;wnie&#380;: <a href="https://kursdotnet.pl/convert-w-sql-server-daty-try-convert-i-wydajnosc">CONVERT w SQL Server - daty, TRY_CONVERT i wydajno&#347;&#263;</a></strong></p><h3 id="kiedy-uzyc-recznego-sql-a">Kiedy u&#380;y&#263; r&#281;cznego SQL-a</h3><p>R&#281;czny SQL ma sens przy skomplikowanych raportach, zapytaniach korzystaj&#261;cych ze specyficznych funkcji silnika bazy oraz miejscach, gdzie profilowanie pokaza&#322;o problem z wygenerowanym zapytaniem. Nie powinien by&#263; jednak pierwsz&#261; reakcj&#261; na ka&#380;dy wolniejszy endpoint.</p><p>Przed przej&#347;ciem na SQL sprawdzam kolejno projekcj&#281;, <a href="https://kursdotnet.pl/convert-w-sql-server-daty-try-convert-i-wydajnosc">indeksy</a>, liczb&#281; zwracanych rekord&oacute;w, spos&oacute;b &#322;adowania relacji i plan wykonania. Dopiero potem oceniam, czy r&#281;cznie napisane zapytanie rzeczywi&#347;cie rozwi&#261;zuje problem. <strong>Raw SQL daje kontrol&#281;, ale zwi&#281;ksza koszt utrzymania</strong> i mo&#380;e mocniej zwi&#261;za&#263; aplikacj&#281; z konkretnym silnikiem bazy.</p><h2 id="bledy-ktore-najczesciej-psuja-projekty-z-orm">B&#322;&#281;dy, kt&oacute;re najcz&#281;&#347;ciej psuj&#261; projekty z ORM</h2><ul>
  <li>
<strong>Jeden DbContext na ca&#322;e &#380;ycie aplikacji.</strong> Kontekst powinien mie&#263; ograniczony czas &#380;ycia, zwykle zwi&#261;zany z &#380;&#261;daniem HTTP albo konkretn&#261; operacj&#261;.</li>
  <li>
<strong>Brak limitu wynik&oacute;w.</strong> Endpoint zwracaj&#261;cy ca&#322;&#261; tabel&#281; pr&#281;dzej czy p&oacute;&#378;niej stanie si&#281; problemem.</li>
  <li>
<strong>Wykonywanie zapytania w p&#281;tli.</strong> Najpierw sprawd&#378;, czy da si&#281; u&#380;y&#263; projekcji, z&#322;&#261;czenia albo pojedynczego zapytania zbiorczego.</li>
  <li>
<strong>Bezrefleksyjne Include().</strong> Du&#380;y graf encji mo&#380;e oznacza&#263; ci&#281;&#380;ki SQL i powielone dane.</li>
  <li>
<strong>Brak obserwowalno&#347;ci.</strong> Loguj czas zapyta&#324; i analizuj SQL, kt&oacute;ry rzeczywi&#347;cie trafi&#322; do bazy.</li>
  <li>
<strong>Traktowanie migracji jak zwyk&#322;ego wdro&#380;enia kodu.</strong> Zmiany schematu mog&#261; wymaga&#263; etapowego wdro&#380;enia i migracji kompatybilnej wstecz.</li>
  <li>
<strong>Przenoszenie ca&#322;ej logiki do zapyta&#324;.</strong> SQL powinien efektywnie pobiera&#263; dane, ale regu&#322;y biznesowe nie powinny znika&#263; w trudnych do testowania konstrukcjach.</li>
</ul><p>W projektach produkcyjnych szczeg&oacute;lnie pilnuj&#281; rozdzielenia modeli. Encja bazy danych, model domenowy i DTO odpowiedzi API nie zawsze powinny by&#263; t&#261; sam&#261; klas&#261;. Taki podzia&#322; ogranicza przypadkowe ujawnianie kolumn i pozwala zmienia&#263; schemat bez &#322;amania kontraktu API.</p><p>Przy wyborze narz&#281;dzia zaczynam od charakteru systemu. Dla typowego API biznesowego wybra&#322;bym EF Core, dla kilku precyzyjnych raport&oacute;w do&#322;o&#380;y&#322;bym Dappera, a przy rozbudowanym istniej&#261;cym modelu sprawdzi&#322;bym, czy NHibernate nie ma ju&#380; gotowych rozwi&#261;za&#324; pasuj&#261;cych do architektury.</p><h2 id="dobry-orm-zaczyna-sie-od-dobrego-modelu-danych">Dobry ORM zaczyna si&#281; od dobrego modelu danych</h2><p>ORM nie naprawi &#378;le zaprojektowanych tabel, brakuj&#261;cych indeks&oacute;w ani niejasnych granic transakcji. Najlepsze rezultaty daje po&#322;&#261;czenie wygodnego mapowania z podstawow&#261; dyscyplin&#261; SQL: ograniczaniem wynik&oacute;w, analiz&#261; plan&oacute;w, &#347;wiadomym &#322;adowaniem relacji i pomiarem zamiast domys&#322;&oacute;w.</p><p>Je&#347;li dopiero zaczynasz, zacznij od EF Core, poznaj <code>DbContext</code>, migracje, projekcje i <code>AsNoTracking()</code>, a potem sprawd&#378; wygenerowany SQL dla kilku rzeczywistych przypadk&oacute;w. Taka kolejno&#347;&#263; pozwala korzysta&#263; z produktywno&#347;ci ORM bez utraty kontroli nad tym, co naprawd&#281; dzieje si&#281; w bazie.</p>]]></content:encoded>
      <author>Przemysław Kwiatkowski</author>
      <category>Bazy danych i SQL</category>
      <media:thumbnail url="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/thumbnail/5e43755a144fbbdaed903131c5962043/orm-w-c-bez-pulapek-ef-core-dapper-i-wydajne-zapytania.webp"/>
      <pubDate>Tue, 15 Sep 2026 14:00:00 +0200</pubDate>
    </item>
    <item>
      <title>Instalacja SQL Server bez typowych problemów</title>
      <link>https://kursdotnet.pl/instalacja-sql-server-bez-typowych-problemow</link>
      <description>Instalacja SQL Server krok po kroku: wybór edycji, wymagania, .NET, uwierzytelnianie i firewall. Sprawdź, jak uniknąć typowych błędów.</description>
      <content:encoded><![CDATA[<?xml encoding="utf-8" ?><p>Nieudana instalacja <a href="https://kursdotnet.pl/pivot-w-sql-serverze-bez-tajemnic-przyklady-i-dobre-praktyki">SQL Server</a> potrafi zabra&#263; wi&#281;cej czasu ni&#380; samo przygotowanie aplikacji. W tym poradniku pokazuj&#281;, jak dobra&#263; edycj&#281;, sprawdzi&#263; wymagania, przej&#347;&#263; przez instalacj&#281; na Windows, skonfigurowa&#263; dost&#281;p z aplikacji .NET i unikn&#261;&#263; problem&oacute;w z uwierzytelnianiem, us&#322;ugami oraz zapor&#261; sieciow&#261;.</p><div class="short-summary">
<h2 id="najwazniejsze-decyzje-przed-uruchomieniem-instalatora">Najwa&#380;niejsze decyzje przed uruchomieniem instalatora</h2>
<ul>
<li>
<strong>SQL Server 2025</strong> jest obecnie najnowsz&#261; g&#322;&oacute;wn&#261; wersj&#261; dla instalacji lokalnych.</li>
<li>Do nauki i developmentu najcz&#281;&#347;ciej wystarczy <strong>Developer</strong> albo bezp&#322;atny <strong>Express</strong>.</li>
<li>Instalacja wymaga systemu <strong>x64</strong>, minimum <strong>6 GB wolnego miejsca</strong> na dysku systemowym i odpowiedniej wersji Windows.</li>
<li>Dla aplikacji lokalnej wybierz <strong>Windows Authentication</strong>, a tryb mieszany w&#322;&#261;cz tylko wtedy, gdy rzeczywi&#347;cie potrzebujesz login&oacute;w SQL.</li>
<li>Po instalacji trzeba sprawdzi&#263; us&#322;ug&#281; silnika, po&#322;&#261;czenie w SSMS oraz protok&oacute;&#322; <strong>TCP/IP</strong>, je&#347;li baza ma by&#263; dost&#281;pna z innego komputera.</li>
</ul>
</div><h2 id="wybierz-edycje-sql-server-do-konkretnego-zastosowania">Wybierz edycj&#281; SQL Server do konkretnego zastosowania</h2><p>Najwi&#281;cej nieporozumie&#324; pojawia si&#281; jeszcze przed klikni&#281;ciem przycisku instalacji. SQL Server ma kilka edycji, a r&oacute;&#380;ni&#261; si&#281; nie tylko funkcjami, lecz tak&#380;e licencj&#261; i limitami sprz&#281;towymi. W 2026 roku dla wi&#281;kszo&#347;ci programist&oacute;w najrozs&#261;dniejszym wyborem b&#281;dzie <strong>SQL Server 2025 Developer</strong> do pracy lokalnej albo <strong>Express</strong> do ma&#322;ych aplikacji.</p><table>
<tbody>
<tr>
<th>Edycja</th>
<th>Kiedy ma sens</th>
<th>Najwa&#380;niejsze ograniczenia</th>
</tr>
<tr>
<td>Developer</td>
<td>Development, testy, nauka, projekty .NET</td>
<td>Nie jest przeznaczona do produkcji</td>
</tr>
<tr>
<td>Express</td>
<td>Nauka, aplikacje desktopowe i ma&#322;e systemy</td>
<td>Limit bazy do 50 GB, ograniczenia pami&#281;ci i CPU</td>
</tr>
<tr>
<td>Standard</td>
<td>Produkcja w ma&#322;ych i &#347;rednich firmach</td>
<td>Wymaga licencji, maksymalnie 256 GB pami&#281;ci dla bufora silnika</td>
</tr>
<tr>
<td>Enterprise</td>
<td>Du&#380;e &#347;rodowiska i wymagania wysokiej dost&#281;pno&#347;ci</td>
<td>Najwy&#380;szy koszt licencji i wi&#281;ksza z&#322;o&#380;ono&#347;&#263; administracyjna</td>
</tr>
<tr>
<td>Evaluation</td>
<td>Kr&oacute;tki test funkcji edycji Enterprise</td>
<td>Okres ewaluacyjny wynosi 180 dni</td>
</tr>
</tbody>
</table><p>Edycja Developer zawiera funkcje por&oacute;wnywalne z wariantem Enterprise, ale jej licencja pozwala u&#380;ywa&#263; jej wy&#322;&#261;cznie do <strong>developmentu i test&oacute;w</strong>. To &#347;wietna opcja dla programisty, lecz nie wolno traktowa&#263; jej jako bezp&#322;atnego serwera produkcyjnego.</p><p>Express jest prostszy i bezp&#322;atny, ale jego limity mog&#261; szybko sta&#263; si&#281; odczuwalne. Dla SQL Server 2025 maksymalny rozmiar pojedynczej bazy Express wynosi <strong>50 GB</strong>, a silnik mo&#380;e wykorzysta&#263; maksymalnie 1 procesor lub 4 rdzenie oraz oko&#322;o 1,4 GB pami&#281;ci bufora. Do nauki SQL i ma&#322;ego API to zwykle wystarcza, ale przy wi&#281;kszym systemie trzeba zaplanowa&#263; migracj&#281; do innej edycji.</p><p>SQL Server Express LocalDB ma jeszcze l&#380;ejsz&#261; form&#281;. Uruchamia si&#281; w kontek&#347;cie u&#380;ytkownika i dobrze pasuje do lokalnych eksperyment&oacute;w, test&oacute;w integracyjnych oraz prostych aplikacji desktopowych. Nie traktowa&#322;bym go jednak jako zamiennika normalnego serwera, je&#347;li aplikacja ma dzia&#322;a&#263; stale albo korzysta&#263; z wielu komputer&oacute;w.</p><h2 id="sprawdz-wymagania-zanim-uruchomisz-setupexe">Sprawd&#378; wymagania zanim uruchomisz setup.exe</h2><p>SQL Server 2025 dzia&#322;a na systemach <strong>Windows 10 lub nowszym</strong> oraz na Windows Server 2019 i nowszych. Instalacja wymaga procesora x64, wi&#281;c stary system 32-bitowy odpada ju&#380; na starcie. W praktyce bardziej ni&#380; minimalne parametry liczy si&#281; ilo&#347;&#263; pami&#281;ci i szybko&#347;&#263; dysku, bo to one wp&#322;ywaj&#261; na komfort pracy z baz&#261;.</p><table>
<tbody>
<tr>
<th>Element</th>
<th>Minimum</th>
<th>Rozs&#261;dna warto&#347;&#263; do developmentu</th>
</tr>
<tr>
<td>Procesor</td>
<td>x64, 1,4 GHz</td>
<td>2 GHz lub wi&#281;cej</td>
</tr>
<tr>
<td>Pami&#281;&#263; RAM</td>
<td>512 MB dla Express, 1 GB dla pozosta&#322;ych edycji</td>
<td>Minimum 4 GB dla silnika, wi&#281;cej przy Visual Studio i Dockerze</td>
</tr>
<tr>
<td>Wolne miejsce</td>
<td>6 GB na dysku systemowym</td>
<td>Kilkana&#347;cie GB z zapasem na bazy, logi i aktualizacje</td>
</tr>
<tr>
<td>Framework</td>
<td>.NET Framework 4.7.2</td>
<td>Aktualny obs&#322;ugiwany framework systemowy</td>
</tr>
<tr>
<td>System</td>
<td>Windows 10 lub nowszy, Windows Server 2019 lub nowszy</td>
<td>Windows 11 albo wspierana wersja Windows Server</td>
</tr>
</tbody>
</table><p>Wym&oacute;g <strong>6 GB wolnego miejsca</strong> dotyczy dysku systemowego, nawet je&#347;li pliki danych zapiszesz na innym woluminie. Instalator korzysta z katalog&oacute;w tymczasowych Windows, dlatego brak miejsca na dysku C cz&#281;sto powoduje b&#322;&#261;d wygl&#261;daj&#261;cy na niezwi&#261;zany z przestrzeni&#261; dyskow&#261;.</p><p>Przed instalacj&#261; zainstaluj wszystkie oczekuj&#261;ce aktualizacje systemu i wykonaj restart. Szczeg&oacute;lnie przy instalacji na Windows Server warto sprawdzi&#263;, czy nie ma oczekuj&#261;cego ponownego uruchomienia. Instalator SQL Server potrafi zatrzyma&#263; si&#281; ju&#380; na kontroli wst&#281;pnej, je&#347;li system uwa&#380;a, &#380;e aktualizacja nie zosta&#322;a jeszcze doko&#324;czona.</p><h3 id="przygotuj-lokalizacje-plikow">Przygotuj lokalizacje plik&oacute;w</h3><p>Na komputerze developerskim domy&#347;lne &#347;cie&#380;ki s&#261; akceptowalne. Na serwerze produkcyjnym oddzieli&#322;bym przynajmniej <strong>pliki danych, logi transakcyjne i kopie zapasowe</strong>. Log transakcyjny to plik, w kt&oacute;rym SQL Server zapisuje operacje potrzebne mi&#281;dzy innymi do odtwarzania i zapewnienia sp&oacute;jno&#347;ci bazy.</p><p>

Nie umieszczaj wszystkiego na jednym powolnym dysku, je&#347;li baza ma obs&#322;ugiwa&#263; realny ruch. Taki uk&#322;ad dzia&#322;a podczas testu, ale przy wi&#281;kszej liczbie zapis&oacute;w szybko staje si&#281; <a href="https://kursdotnet.pl/ienumerable-czy-iqueryable-gdzie-wykonuje-sie-zapytanie-linq">w&#261;skim gard&#322;em</a>.

</p><h2 id="instalacja-sql-server-krok-po-kroku">Instalacja SQL Server krok po kroku</h2><p>Po uruchomieniu instalatora jako administrator otwiera si&#281; SQL Server Installation Center. W przypadku lokalnej instalacji wybierz opcj&#281; utworzenia <strong>nowej autonomicznej instancji</strong> albo dodania funkcji do istniej&#261;cej instalacji. Angielskie okre&#347;lenie sql server installation odnosi si&#281; w&#322;a&#347;nie do tego procesu, obejmuj&#261;cego wyb&oacute;r edycji, funkcji, instancji i us&#322;ug.</p><ol>
<li>
<strong>Uruchom setup.exe</strong> z no&#347;nika instalacyjnego lub rozpakowanego obrazu.</li>
<li>Przejd&#378; do sekcji instalacji i wybierz now&#261; instalacj&#281; samodzieln&#261;.</li>
<li>Zaakceptuj warunki licencyjne i przejd&#378; przez kontrol&#281; regu&#322; instalatora.</li>
<li>Wybierz edycj&#281;, na przyk&#322;ad Developer do developmentu albo Express do ma&#322;ego projektu.</li>
<li>Zaznacz funkcje, kt&oacute;rych naprawd&#281; potrzebujesz.</li>
<li>Ustaw nazw&#281; instancji oraz konta us&#322;ug.</li>
<li>Wybierz tryb uwierzytelniania i skonfiguruj administrator&oacute;w silnika.</li>
<li>Sprawd&#378; podsumowanie i rozpocznij instalacj&#281;.</li>
</ol><h3 id="wybierz-tylko-potrzebne-funkcje">Wybierz tylko potrzebne funkcje</h3><p>Dla typowej aplikacji ASP.NET Core wystarczy <strong>Database Engine Services</strong>. To g&#322;&oacute;wny silnik relacyjnej bazy danych. Nie zaznaczaj bez zastanowienia Analysis Services, Integration Services ani dodatkowych komponent&oacute;w, je&#347;li nie masz konkretnego scenariusza u&#380;ycia.</p><p>W SQL Server 2025 cz&#281;&#347;&#263; dawnych komponent&oacute;w zosta&#322;a usuni&#281;ta lub przeniesiona do innych produkt&oacute;w. Dotyczy to mi&#281;dzy innymi Reporting Services, Data Quality Services, Master Data Services oraz Azure Synapse Link. Je&#380;eli korzystasz ze starszej dokumentacji, sprawd&#378;, czy opisywana funkcja nadal znajduje si&#281; w wybranej wersji.</p><h3 id="instancja-domyslna-czy-nazwana">Instancja domy&#347;lna czy nazwana</h3><p>Instancja to osobne &#347;rodowisko SQL Server dzia&#322;aj&#261;ce na jednym komputerze. <strong>Instancja domy&#347;lna</strong> jest rozpoznawana po samej nazwie hosta, na przyk&#322;ad <code>localhost</code>. Instancja nazwana wymaga dopisania nazwy, na przyk&#322;ad <code>localhost\SQLEXPRESS</code>.</p><p>Do nauki mo&#380;esz zostawi&#263; ustawienia domy&#347;lne. W &#347;rodowisku firmowym nazwana instancja bywa wygodniejsza, gdy na jednym serwerze dzia&#322;aj&#261; r&oacute;&#380;ne wersje lub niezale&#380;ne &#347;rodowiska. Trzeba jednak pami&#281;ta&#263;, &#380;e komplikuje to konfiguracj&#281; sieci, szczeg&oacute;lnie gdy SQL Server korzysta z port&oacute;w dynamicznych.</p><h3 id="uwierzytelnianie-i-konto-administratora">Uwierzytelnianie i konto administratora</h3><p>Je&#347;li aplikacja dzia&#322;a w domenie Windows, preferuj&#281; <strong>Windows Authentication</strong>. Has&#322;a nie s&#261; wtedy przechowywane w konfiguracji aplikacji, a dost&#281;p mo&#380;na kontrolowa&#263; przez u&#380;ytkownik&oacute;w i grupy systemowe.</p><p>Tryb <strong>SQL Server and Windows Authentication</strong>, nazywany te&#380; Mixed Mode, ma sens wtedy, gdy aplikacja lub narz&#281;dzie musi logowa&#263; si&#281; przez u&#380;ytkownika SQL. W takim przypadku ustaw silne has&#322;o dla konta sa, dodaj osobne konto aplikacyjne z minimalnymi uprawnieniami i nie u&#380;ywaj sa w connection stringu aplikacji.</p><p>Podczas instalacji dodaj swoje konto do administrator&oacute;w SQL Server. Bez tego mo&#380;esz wprawdzie uruchomi&#263; us&#322;ug&#281;, ale p&oacute;&#378;niej straci&#263; dost&#281;p do konfiguracji instancji. To jeden z tych drobnych krok&oacute;w, kt&oacute;re oszcz&#281;dzaj&#261; du&#380;o nerw&oacute;w po restarcie komputera.</p><h2 id="po-instalacji-skonfiguruj-polaczenie-z-aplikacja">Po instalacji skonfiguruj po&#322;&#261;czenie z aplikacj&#261;</h2><p>Sama obecno&#347;&#263; us&#322;ugi SQL Server nie oznacza jeszcze, &#380;e aplikacja mo&#380;e si&#281; z ni&#261; po&#322;&#261;czy&#263;. Najpierw uruchom SQL Server Management Studio i sprawd&#378;, czy logowanie dzia&#322;a lokalnie. SSMS jest narz&#281;dziem administracyjnym instalowanym niezale&#380;nie od silnika bazy danych.</p><h3 id="sprawdz-nazwe-serwera">Sprawd&#378; nazw&#281; serwera</h3><p>Dla instancji domy&#347;lnej u&#380;yj nazwy komputera albo <code>localhost</code>. Dla instancji nazwanej wpisz format <code>localhost\NazwaInstancji</code>. W projektach .NET cz&#281;sto spotkasz r&oacute;wnie&#380; po&#322;&#261;czenie do LocalDB zapisane jako <code>(localdb)\MSSQLLocalDB</code>.</p><p>Przyk&#322;adowy connection string dla uwierzytelniania Windows mo&#380;e wygl&#261;da&#263; tak:</p><pre><code>Server=localhost;Database=Sklep;Trusted_Connection=True;TrustServerCertificate=True;</code></pre><p>Dla instancji nazwanej zmie&#324; warto&#347;&#263; serwera, na przyk&#322;ad:</p><pre><code>Server=localhost\SQLEXPRESS;Database=Sklep;Trusted_Connection=True;TrustServerCertificate=True;</code></pre><p>Parametr <code>TrustServerCertificate=True</code> bywa przydatny lokalnie, gdy korzystasz z certyfikatu deweloperskiego. Na produkcji nie powinien zast&#281;powa&#263; poprawnej konfiguracji certyfikat&oacute;w i walidacji po&#322;&#261;czenia.</p><h3 id="wlacz-tcpip-tylko-wtedy-gdy-jest-potrzebne">W&#322;&#261;cz TCP/IP tylko wtedy, gdy jest potrzebne</h3><p>Po&#322;&#261;czenia lokalne mog&#261; korzysta&#263; z Shared Memory, ale aplikacja na innym komputerze potrzebuje zwykle <strong>TCP/IP</strong>. W&#322;&#261;czysz go w SQL Server Configuration Manager, w ustawieniach protoko&#322;&oacute;w dla konkretnej instancji. Po zmianie uruchom ponownie us&#322;ug&#281; SQL Server.</p><p>Dla instancji domy&#347;lnej cz&#281;sto stosuje si&#281; port <strong>1433</strong>. Przy instancjach nazwanych mo&#380;na u&#380;ywa&#263; SQL Server Browser i port&oacute;w dynamicznych, ale w &#347;rodowisku produkcyjnym wol&#281; ustawi&#263; sta&#322;y port i otworzy&#263; w zaporze wy&#322;&#261;cznie wymagany ruch.</p><p>Nie wystarczy wy&#322;&#261;czy&#263; zapory Windows na czas testu. Bezpieczniejszy uk&#322;ad to regu&#322;a przychodz&#261;ca dla konkretnego portu, ograniczona do podsieci albo adres&oacute;w serwer&oacute;w, kt&oacute;re rzeczywi&#347;cie musz&#261; korzysta&#263; z bazy.</p><p class="read-more"><strong>Przeczytaj r&oacute;wnie&#380;: <a href="https://kursdotnet.pl/oracle-create-table-od-podstaw-klucze-identity-i-ctas">Oracle CREATE TABLE od podstaw - klucze, identity i CTAS</a></strong></p><h3 id="ustaw-limity-pamieci">Ustaw limity pami&#281;ci</h3><p>SQL Server ch&#281;tnie wykorzystuje dost&#281;pn&#261; pami&#281;&#263;. Na komputerze deweloperskim mo&#380;e to spowodowa&#263;, &#380;e Visual Studio, przegl&#261;darka i kontenery Docker zaczn&#261; dzia&#322;a&#263; ospale. Ustawienie <strong>Max Server Memory</strong> pozwala zostawi&#263; systemowi i innym procesom rozs&#261;dny zapas.</p><p>Nie ma jednej warto&#347;ci dobrej dla ka&#380;dego komputera. Na maszynie z 16 GB RAM przeznaczenie oko&#322;o 8-10 GB dla SQL Server mo&#380;e by&#263; sensowne, je&#347;li r&oacute;wnolegle uruchamiasz IDE. Na serwerze z jednym przeznaczeniem limit ustawia si&#281; inaczej, zale&#380;nie od obci&#261;&#380;enia, systemu operacyjnego i pozosta&#322;ych us&#322;ug.</p><h2 id="najczestsze-problemy-i-szybka-diagnostyka">Najcz&#281;stsze problemy i szybka diagnostyka</h2><p>Je&#347;li instalator zako&#324;czy prac&#281; b&#322;&#281;dem, nie zaczynaj od ponownej instalacji wszystkiego. Najpierw sprawd&#378; logi instalatora oraz konkretn&#261; regu&#322;&#281;, kt&oacute;ra nie przesz&#322;a kontroli. W wi&#281;kszo&#347;ci przypadk&oacute;w problemem jest brak uprawnie&#324; administratora, oczekuj&#261;cy restart, niezgodny system albo zaj&#281;ty port.</p><table>
<tbody>
<tr>
<th>Objaw</th>
<th>Prawdopodobna przyczyna</th>
<th>Co sprawdzi&#263;</th>
</tr>
<tr>
<td>Nie mo&#380;na po&#322;&#261;czy&#263; si&#281; z serwerem</td>
<td>Zatrzymana us&#322;uga lub b&#322;&#281;dna nazwa instancji</td>
<td>SQL Server Services, nazwa instancji, logi systemowe</td>
</tr>
<tr>
<td>Dzia&#322;a lokalnie, ale nie z innego komputera</td>
<td>Wy&#322;&#261;czony TCP/IP albo blokada zapory</td>
<td>Protok&oacute;&#322; TCP/IP, port, regu&#322;y firewall</td>
</tr>
<tr>
<td>Login failed for user</td>
<td>Z&#322;y tryb uwierzytelniania lub brak uprawnie&#324;</td>
<td>Windows Authentication, login SQL, mapowanie do bazy</td>
</tr>
<tr>
<td>Instalator zatrzymuje si&#281; na kontroli systemu</td>
<td>Brak miejsca, pending restart albo niezgodna wersja Windows</td>
<td>Dysk systemowy, Windows Update, wymagania x64</td>
</tr>
<tr>
<td>Aplikacja dzia&#322;a bardzo wolno</td>
<td>Za ma&#322;o pami&#281;ci, wolny dysk lub brak indeks&oacute;w</td>
<td>Limit pami&#281;ci, plan zapytania, obci&#261;&#380;enie dysku</td>
</tr>
</tbody>
</table><p>Komunikat &bdquo;server not found&rdquo; nie musi oznacza&#263; uszkodzonej bazy. Cz&#281;sto aplikacja wskazuje instancj&#281; domy&#347;ln&#261;, podczas gdy zainstalowano Express jako instancj&#281; nazwan&#261;. Wtedy problem rozwi&#261;zuje poprawienie nazwy serwera w konfiguracji, a nie reinstalacja SQL Server.</p><p>Je&#380;eli u&#380;ywasz konta SQL i logowanie nie dzia&#322;a, sprawd&#378;, czy podczas instalacji w&#322;&#261;czono Mixed Mode. Samo wpisanie loginu i has&#322;a w connection stringu nie aktywuje tego trybu. Trzeba tak&#380;e potwierdzi&#263;, &#380;e u&#380;ytkownik istnieje, jest odblokowany i ma dost&#281;p do wskazanej bazy.</p><h2 id="instalacja-powtarzalna-dla-zespolu-i-ci">Instalacja powtarzalna dla zespo&#322;u i CI</h2><p>Na jednym komputerze wygodny jest kreator graficzny, ale w zespole lepiej d&#261;&#380;y&#263; do instalacji powtarzalnej. SQL Server mo&#380;na instalowa&#263; z wiersza polece&#324; albo za pomoc&#261; pliku konfiguracji, w kt&oacute;rym zapisujesz mi&#281;dzy innymi edycj&#281;, funkcje, &#347;cie&#380;ki i tryb uwierzytelniania.</p><p>To podej&#347;cie ma najwi&#281;ksz&#261; warto&#347;&#263; wtedy, gdy przygotowujesz kilka maszyn developerskich, &#347;rodowisko testowe albo automatyzacj&#281; na serwerze. Zamiast odtwarza&#263; ustawienia z pami&#281;ci, przechowujesz je w kontrolowanym pliku i mo&#380;esz sprawdzi&#263; zmiany podczas code review.</p><pre><code>setup.exe /Q /ACTION=Install /FEATURES=SQLENGINE
/INSTANCENAME=MSSQLSERVER
/SQLSVCACCOUNT="NT Service\MSSQLSERVER"
/IACCEPTSQLSERVERLICENSETERMS</code></pre><p>Dok&#322;adne parametry zale&#380;&#261; od wersji, edycji i wybranych komponent&oacute;w, dlatego przed u&#380;yciem trybu cichego sprawd&#378; sk&#322;adni&#281; dla konkretnego instalatora. Najpierw przetestuj konfiguracj&#281; na maszynie tymczasowej. Cicha instalacja przyspiesza wdro&#380;enia, ale utrudnia znalezienie b&#322;&#281;du, je&#347;li pominiesz wymagany parametr.</p><p>

W &#347;rodowisku .NET cz&#281;sto rozdzielam instalacj&#281; silnika od tworzenia schematu bazy. SQL Server Setup przygotowuje us&#322;ug&#281;, natomiast migracje <a href="https://kursdotnet.pl/orm-w-net-jak-dziala-i-kiedy-warto-go-uzyc">Entity Framework Core</a> albo skrypty SQL tworz&#261; tabele, indeksy i dane. Dzi&#281;ki temu aktualizacja aplikacji nie wymaga ponownego uruchamiania instalatora serwera.

</p><h2 id="co-sprawdzic-przed-rozpoczeciem-pracy-z-baza">Co sprawdzi&#263; przed rozpocz&#281;ciem pracy z baz&#261;</h2><ul>
<li>Us&#322;uga SQL Server uruchamia si&#281; automatycznie po restarcie.</li>
<li>Mo&#380;esz zalogowa&#263; si&#281; lokalnie przez SSMS.</li>
<li>Aplikacja korzysta z w&#322;a&#347;ciwej nazwy instancji.</li>
<li>Uwierzytelnianie odpowiada konfiguracji serwera.</li>
<li>TCP/IP i zapora s&#261; ustawione tylko wtedy, gdy baza ma przyjmowa&#263; po&#322;&#261;czenia sieciowe.</li>
<li>Pliki danych, logi i kopie zapasowe maj&#261; zaplanowane lokalizacje.</li>
<li>Developer nie trafia na produkcj&#281;, a Express nie jest u&#380;ywany bez sprawdzenia limit&oacute;w.</li>
</ul><p>Najbezpieczniejszy schemat dla typowego projektu wygl&#261;da prosto. Do lokalnego developmentu wybierz Developer, uruchom tylko Database Engine, u&#380;ywaj Windows Authentication i trzymaj baz&#281; poza repozytorium kodu. Na serwerze produkcyjnym dodaj kontrol&#281; dost&#281;pu, kopie zapasowe, sta&#322;y port, monitoring oraz osobne konta us&#322;ug.</p><p>Dobra instalacja SQL Server nie ko&#324;czy si&#281; na komunikacie &bdquo;Setup completed successfully&rdquo;. Dopiero poprawne po&#322;&#261;czenie, ograniczenie uprawnie&#324;, przemy&#347;lane &#347;cie&#380;ki plik&oacute;w i sprawdzony scenariusz odtwarzania bazy pokazuj&#261;, &#380;e &#347;rodowisko jest naprawd&#281; gotowe do pracy.</p>]]></content:encoded>
      <author>Przemysław Kwiatkowski</author>
      <category>Bazy danych i SQL</category>
      <media:thumbnail url="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/thumbnail/75c68eb3a5f052af654f47d8c170ae83/instalacja-sql-server-bez-typowych-problemow.webp"/>
      <pubDate>Tue, 15 Sep 2026 08:38:00 +0200</pubDate>
    </item>
    <item>
      <title>.NET MAUI na Windowsie - konfiguracja, publikacja i wybór</title>
      <link>https://kursdotnet.pl/net-maui-na-windowsie-konfiguracja-publikacja-i-wybor</link>
      <description>Poznaj .NET MAUI na Windowsie: konfigurację, publikację MSIX i różnice względem WPF oraz WinUI 3. Wybierz framework świadomie.</description>
      <content:encoded><![CDATA[<?xml encoding="utf-8" ?><p>Budujesz aplikacj&#281; dla Windows, ale nie chcesz zamyka&#263; sobie drogi do Androida i macOS? <strong>.NET MAUI</strong> pozwala tworzy&#263; natywne aplikacje desktopowe i mobilne w C# oraz XAML, korzystaj&#261;c ze wsp&oacute;lnego kodu. Poka&#380;&#281;, jak dzia&#322;a na Windowsie, kiedy ma sens, jak wygl&#261;da konfiguracja, wdra&#380;anie oraz gdzie pojawiaj&#261; si&#281; kompromisy wzgl&#281;dem WPF i <a href="https://kursdotnet.pl/winui-3-czy-wpf-jak-wybrac-framework-do-aplikacji-windows">WinUI 3</a>.

</p><div class="short-summary">
  <h2 id="jedna-baza-kodu-moze-obsluzyc-windows-i-urzadzenia-mobilne">Jedna baza kodu mo&#380;e obs&#322;u&#380;y&#263; Windows i urz&#261;dzenia mobilne</h2>
  <ul>
    <li>
<strong>Windows</strong> korzysta z WinUI 3, wi&#281;c aplikacja nie jest zwyk&#322;&#261; stron&#261; uruchomion&#261; w przegl&#261;darce.</li>
    <li>
<strong>C# i XAML</strong> pozwalaj&#261; wsp&oacute;&#322;dzieli&#263; interfejs, logik&#281; biznesow&#261;, testy i dost&#281;p do us&#322;ug.</li>
    <li>
<strong>Projekt single project</strong> porz&#261;dkuje konfiguracj&#281; wielu platform w jednym projekcie.</li>
    <li>
<strong>MSIX lub publikacja do folderu</strong> daj&#261; dwie g&#322;&oacute;wne drogi dystrybucji aplikacji na Windows.</li>
    <li>
<strong>WPF lub WinUI 3</strong> mog&#261; by&#263; lepszym wyborem, gdy aplikacja ma dzia&#322;a&#263; wy&#322;&#261;cznie na Windowsie.</li>
  </ul>
</div><p><img src="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/post_image/9e45f34c7d192ddd79a6c7a21e9b7568/net-maui-winui-3-windows-desktop-application-architecture-diagram.webp" class="image article-image" loading="lazy" alt="Aplikacja .NET MAUI z Blazor Hybrid, wy&#347;wietlaj&#261;ca przyjaznego robota na ekranach przegl&#261;darki i emulatora Androida."></p><h2 id="czym-naprawde-jest-net-maui-na-windowsie">Czym naprawd&#281; jest .NET MAUI na Windowsie</h2><p>.NET MAUI to framework do budowy aplikacji natywnych dla Androida, iOS, macOS oraz Windows. Wsp&oacute;lny kod obejmuje zwykle widoki, modele, logik&#281; biznesow&#261; i komunikacj&#281; z API, natomiast ka&#380;da platforma mo&#380;e mie&#263; w&#322;asne fragmenty implementacji. <strong>Nie jest to emulacja Windowsa</strong>, tylko warstwa, kt&oacute;ra mapuje kontrolki na elementy w&#322;a&#347;ciwe dla danego systemu.</p><p>W przypadku Windows aplikacja korzysta z <strong>WinUI 3</strong>, czyli nowoczesnej biblioteki interfejsu dzia&#322;aj&#261;cej w ramach <a href="https://kursdotnet.pl/microsoft-remote-desktop-na-macu-konfiguracja-windows-app">Windows App</a> SDK. Dzi&#281;ki temu przyciski, pola tekstowe czy listy s&#261; obs&#322;ugiwane przez mechanizmy systemu Windows, a program mo&#380;e korzysta&#263; tak&#380;e z platformowych API.</p><p>Najwi&#281;ksz&#261; zalet&#261; jest model <strong>single project</strong>. Zamiast utrzymywa&#263; osobny projekt dla Windows, Androida i iOS, pracuj&#281; w jednej strukturze, w kt&oacute;rej konfiguracja oraz kod wsp&oacute;lny znajduj&#261; si&#281; obok katalog&oacute;w platformowych. Microsoft opisuje to jako jeden system zarz&#261;dzania konfiguracj&#261; aplikacji wieloplatformowej.</p><p>W praktyce wsp&oacute;&#322;dzielenie kodu nie oznacza, &#380;e wszystko powinno by&#263; identyczne. Interfejs aplikacji magazynowej mo&#380;e wygl&#261;da&#263; podobnie na telefonie i komputerze, ale spos&oacute;b obs&#322;ugi myszy, klawiatury, menu kontekstowego czy wielu okien b&#281;dzie ju&#380; inny. <strong>Wsp&oacute;lna baza jest punktem wyj&#347;cia</strong>, a nie zakazem korzystania z natywnych mo&#380;liwo&#347;ci Windows.</p><h2 id="co-trzeba-przygotowac-przed-utworzeniem-projektu">Co trzeba przygotowa&#263; przed utworzeniem projektu</h2><p>Do rozpocz&#281;cia pracy potrzebujesz komputera z Windowsem, zgodnej wersji .NET SDK oraz &#347;rodowiska z workloadem <strong>.NET Multi-platform App UI development</strong>. Najwygodniejszym wyborem pozostaje Visual Studio, poniewa&#380; zapewnia szablony projekt&oacute;w, debugowanie, emulator oraz obs&#322;ug&#281; narz&#281;dzi <a href="https://kursdotnet.pl/winui-3-czy-wpf-jak-wybrac-framework-do-aplikacji-windows">Windows App SDK</a>.</p><p>Aplikacje Windows budowane za pomoc&#261; tej technologii wymagaj&#261; systemu <strong>Windows 10 w wersji 1809 lub nowszej</strong>. Do samego tworzenia aplikacji najlepiej u&#380;ywa&#263; aktualnego Windows 11, poniewa&#380; &#322;atwiej wtedy testowa&#263; wygl&#261;d, skalowanie i zachowanie kontrolek zgodne z obecnym &#347;rodowiskiem u&#380;ytkownika.</p><h3 id="minimalny-przebieg-konfiguracji">Minimalny przebieg konfiguracji</h3><ol>
  <li>Zainstaluj Visual Studio z workloadem przeznaczonym do tworzenia aplikacji .NET MAUI.</li>
  <li>Utw&oacute;rz projekt typu <strong>.NET MAUI App</strong>.</li>
  <li>Wybierz Windows jako cel uruchomienia.</li>
  <li>Uruchom aplikacj&#281; bezpo&#347;rednio z Visual Studio i sprawd&#378; zachowanie przy zmianie rozmiaru okna.</li>
  <li>Dodaj dopiero p&oacute;&#378;niej kod specyficzny dla Windows, je&#347;li wsp&oacute;lne API nie wystarcza.</li>
</ol><p>W nowym projekcie znajdziesz mi&#281;dzy innymi katalogi <strong>Platforms/Windows</strong>, pliki XAML dla widok&oacute;w oraz klasy us&#322;ug. Ja zwykle zaczynam od uruchomienia pustej aplikacji i sprawdzenia procesu publikacji, zanim do&#322;o&#380;&#281; logowanie, baz&#281; danych czy rozbudowan&#261; nawigacj&#281;. Wczesne wykrycie problemu z SDK oszcz&#281;dza p&oacute;&#378;niej sporo czasu.</p><h3 id="prosty-widok-i-logika-w-c">Prosty widok i logika w C#</h3><p>Interfejs mo&#380;na opisa&#263; w XAML, a zachowanie kontrolowa&#263; z poziomu C#. Przyk&#322;adowy widok mo&#380;e zawiera&#263; pole tekstowe oraz przycisk:</p><pre><code><verticalstacklayout padding="24" spacing="12">
    <entry x:name="NameEntry" placeholder="Podaj nazw&#281;"></entry>
    <button text="Zapisz" clicked="OnSaveClicked"></button>
    <label x:name="StatusLabel"></label>
</verticalstacklayout></code></pre><p>W kodzie obs&#322;ugi wystarczy zmieni&#263; zawarto&#347;&#263; etykiety:</p><pre><code>private void OnSaveClicked(object sender, EventArgs e)
{
    StatusLabel.Text = $"Zapisano: {NameEntry.Text}";
}</code></pre><p>Taki przyk&#322;ad jest prosty, ale pokazuje wa&#380;n&#261; rzecz. <strong>Widok nie musi wiedzie&#263;, gdzie trafiaj&#261; dane</strong>. W wi&#281;kszej aplikacji logik&#281; przenosz&#281; do ViewModelu, a zapis do osobnego serwisu. Dzi&#281;ki temu ten sam kod mo&#380;e obs&#322;u&#380;y&#263; Windows i urz&#261;dzenie mobilne bez kopiowania ca&#322;ych ekran&oacute;w.</p><h2 id="do-jakich-aplikacji-desktopowych-ten-framework-pasuje-najlepiej">Do jakich aplikacji desktopowych ten framework pasuje najlepiej</h2><p>Najlepiej sprawdza si&#281; tam, gdzie aplikacja ma mie&#263; kilka wyda&#324;, ale zesp&oacute;&#322; chce utrzymywa&#263; jedn&#261; logik&#281; biznesow&#261;. Dobrym przyk&#322;adem jest panel klienta, aplikacja do obs&#322;ugi zam&oacute;wie&#324;, prosty system magazynowy, narz&#281;dzie terenowe z wersj&#261; mobiln&#261; albo wewn&#281;trzny program korzystaj&#261;cy z firmowego API.</p><p>Je&#380;eli aplikacja dzia&#322;a g&#322;&oacute;wnie na formularzach, listach, kartach, wykresach i ekranach ustawie&#324;, <strong>produktywno&#347;&#263; mo&#380;e by&#263; bardzo dobra</strong>. Szczeg&oacute;lnie doceniam mo&#380;liwo&#347;&#263; wsp&oacute;&#322;dzielenia modeli, walidacji, autoryzacji i komunikacji z backendem napisanym w ASP.NET Core.</p><p>Trudniej robi si&#281; wtedy, gdy program ma by&#263; typowym narz&#281;dziem windowsowym. Rozbudowane menu aplikacji, wiele niezale&#380;nych okien, zaawansowana integracja z paskiem zada&#324;, obs&#322;uga drukowania, globalne skr&oacute;ty klawiszowe czy g&#322;&#281;boka integracja z systemem mog&#261; wymaga&#263; kodu platformowego.</p><p>Nie traktuj&#281; tego jako wady samej w sobie. Problem pojawia si&#281; dopiero wtedy, gdy kto&#347; zak&#322;ada, &#380;e aplikacja wieloplatformowa b&#281;dzie w 100 procentach wsp&oacute;lna. W realnym projekcie rozs&#261;dny podzia&#322; to cz&#281;sto <strong>80-95 procent kodu wsp&oacute;&#322;dzielonego</strong>, a reszta przeznaczona na r&oacute;&#380;nice systemowe. Dok&#322;adny wynik zale&#380;y od charakteru interfejsu.</p><h2 id="net-maui-wpf-czy-winui-3">.NET MAUI, WPF czy WinUI 3</h2><p>Wyb&oacute;r technologii powinien wynika&#263; z docelowych platform, wieku istniej&#261;cego kodu i rodzaju interfejsu. Sam fakt, &#380;e aplikacja dzia&#322;a na Windowsie, nie oznacza jeszcze, &#380;e framework wieloplatformowy b&#281;dzie najlepsz&#261; opcj&#261;.</p><table>
  <tbody>
    <tr>
      <th>Technologia</th>
      <th>Najlepsze zastosowanie</th>
      <th>Najwi&#281;ksza zaleta</th>
      <th>Ograniczenie</th>
    </tr>
    <tr>
      <td>.NET MAUI</td>
      <td>Windows plus Android, iOS lub macOS</td>
      <td>Wsp&oacute;lny kod C# i XAML</td>
      <td>R&oacute;&#380;nice mi&#281;dzy platformami wymagaj&#261; dodatkowej pracy</td>
    </tr>
    <tr>
      <td>WPF</td>
      <td>Rozbudowane aplikacje wy&#322;&#261;cznie dla Windows</td>
      <td>Dojrza&#322;y ekosystem i du&#380;a liczba bibliotek</td>
      <td>Brak naturalnej &#347;cie&#380;ki do aplikacji mobilnych</td>
    </tr>
    <tr>
      <td>WinUI 3</td>
      <td>Nowoczesne aplikacje skupione na Windows</td>
      <td>Bezpo&#347;redni dost&#281;p do aktualnego stosu Windows</td>
      <td>Windows pozostaje jedyn&#261; g&#322;&oacute;wn&#261; platform&#261;</td>
    </tr>
    <tr>
      <td>Blazor Hybrid</td>
      <td>Aplikacje z interfejsem HTML, CSS i Razor</td>
      <td>Mo&#380;liwo&#347;&#263; wykorzystania kompetencji webowych</td>
      <td>Trzeba &#347;wiadomie kontrolowa&#263; wydajno&#347;&#263; i zachowanie WebView</td>
    </tr>
  </tbody>
</table><p>Do nowego systemu firmowego dzia&#322;aj&#261;cego tylko na komputerach wybra&#322;bym WPF albo WinUI 3, je&#347;li najwa&#380;niejsza jest pe&#322;na zgodno&#347;&#263; z Windows. Gdy jednak ten sam produkt ma trafi&#263; tak&#380;e na telefony, <strong>MAUI daje wyra&#378;n&#261; przewag&#281; organizacyjn&#261;</strong>, bo model domenowy, API i znaczna cz&#281;&#347;&#263; widok&oacute;w pozostaj&#261; wsp&oacute;lne.</p><p>Blazor Hybrid jest ciekawym wariantem, gdy zesp&oacute;&#322; dobrze zna Razor i CSS. Nie wybiera&#322;bym go jednak automatycznie. Dla aplikacji mocno formularzowej mo&#380;e przyspieszy&#263; prac&#281;, ale dla interfejsu wymagaj&#261;cego bardzo p&#322;ynnych animacji i typowo natywnego zachowania klasyczne kontrolki XAML bywaj&#261; prostszym rozwi&#261;zaniem.</p><h2 id="jak-zaprojektowac-aplikacje-zeby-dobrze-dzialala-na-komputerze">Jak zaprojektowa&#263; aplikacj&#281;, &#380;eby dobrze dzia&#322;a&#322;a na komputerze</h2><p>Najcz&#281;stszy b&#322;&#261;d polega na przeniesieniu uk&#322;adu mobilnego na du&#380;y monitor. Na Windowsie u&#380;ytkownik oczekuje sensownego wykorzystania szeroko&#347;ci, wygodnej pracy klawiatur&#261; i przewidywalnego zachowania okna. Dlatego projektuj&#281; ekran tak, aby dobrze reagowa&#322; na <strong>rozmiar okna, skalowanie DPI i orientacj&#281; tre&#347;ci</strong>.</p><h3 id="uklad-ktory-skaluje-sie-razem-z-oknem">Uk&#322;ad, kt&oacute;ry skaluje si&#281; razem z oknem</h3><p>Zamiast ustawia&#263; sztywne szeroko&#347;ci, lepiej korzysta&#263; z uk&#322;ad&oacute;w opartych na proporcjach, marginesach i ograniczeniach maksymalnej szeroko&#347;ci. Formularz rozci&#261;gni&#281;ty od lewej do prawej kraw&#281;dzi monitora zwykle wygl&#261;da &#378;le, wi&#281;c centraln&#261; zawarto&#347;&#263; warto ograniczy&#263;, a obok niej przeznaczy&#263; miejsce na list&#281; lub panel szczeg&oacute;&#322;&oacute;w.</p><p>Na komputerze przydaj&#261; si&#281; tak&#380;e <strong>skr&oacute;t klawiszowy, fokus i nawigacja klawiszem Tab</strong>. Interfejs mo&#380;e wygl&#261;da&#263; atrakcyjnie, ale je&#347;li u&#380;ytkownik musi za ka&#380;dym razem si&#281;ga&#263; po mysz, praca z du&#380;&#261; liczb&#261; rekord&oacute;w szybko staje si&#281; m&#281;cz&#261;ca.</p><h3 id="gdzie-umiescic-kod-windowsowy">Gdzie umie&#347;ci&#263; kod windowsowy</h3><p>Do zwyk&#322;ych scenariuszy u&#380;ywam wsp&oacute;lnych abstrakcji frameworka. Gdy potrzebuj&#281; zmieni&#263; spos&oacute;b dzia&#322;ania kontrolki tylko w Windows, korzystam z <strong>handler&oacute;w</strong>. Handler to warstwa &#322;&#261;cz&#261;ca kontrolk&#281; .NET MAUI z jej natywnym odpowiednikiem, dzi&#281;ki czemu mo&#380;na dostosowa&#263; wygl&#261;d lub zachowanie bez kopiowania ca&#322;ego widoku.</p><p>Inn&#261; drog&#261; jest kod w katalogu <strong>Platforms/Windows</strong>. To dobre miejsce na funkcje zale&#380;ne od systemu, takie jak integracja z plikami, powiadomieniami, oknem aplikacji czy mechanizmami Windows App SDK. Wa&#380;ne, aby nie rozrzuca&#263; takich wywo&#322;a&#324; po ca&#322;ym kodzie biznesowym.</p><p>Najczytelniejszy uk&#322;ad to interfejs us&#322;ugi w kodzie wsp&oacute;lnym i implementacje platformowe poni&#380;ej. Dzi&#281;ki temu reszta aplikacji wie, &#380;e ma na przyk&#322;ad otworzy&#263; plik lub wy&#347;wietli&#263; powiadomienie, ale nie musi zna&#263; szczeg&oacute;&#322;&oacute;w Windows.</p><h2 id="publikowanie-aplikacji-na-windows">Publikowanie aplikacji na Windows</h2><p>Po zako&#324;czeniu programowania zostaj&#261; dwie g&#322;&oacute;wne decyzje. Mo&#380;esz opublikowa&#263; aplikacj&#281; do folderu razem z zale&#380;no&#347;ciami albo przygotowa&#263; pakiet <strong>MSIX</strong>, kt&oacute;ry u&#322;atwia instalacj&#281;, aktualizacje i dystrybucj&#281; w &#347;rodowisku Windows.</p><h3 id="publikacja-do-folderu">Publikacja do folderu</h3><p>Wariant folderowy bywa wygodny w firmowej sieci, na serwerze plik&oacute;w albo podczas wdra&#380;ania wewn&#281;trznego. Trzeba jednak zadba&#263; o dostarczenie wszystkich zale&#380;no&#347;ci oraz dobra&#263; w&#322;a&#347;ciw&#261; architektur&#281;, na przyk&#322;ad <strong>win-x64</strong> albo win-arm64.</p><p>Publikacja samodzielna zawiera r&oacute;wnie&#380; runtime .NET, wi&#281;c u&#380;ytkownik nie musi instalowa&#263; go osobno. Zwi&#281;ksza to rozmiar paczki, ale upraszcza wdro&#380;enie. Wersja zale&#380;na od frameworka jest mniejsza, lecz wymaga odpowiedniego &#347;rodowiska na komputerze odbiorcy.</p><p class="read-more"><strong>Przeczytaj r&oacute;wnie&#380;: <a href="https://kursdotnet.pl/execution-policy-w-powershellu-jak-bezpiecznie-uruchamiac-skrypty">Execution policy w PowerShellu - jak bezpiecznie uruchamia&#263; skrypty</a></strong></p><h3 id="pakiet-msix">Pakiet MSIX</h3><p>MSIX lepiej pasuje do aplikacji, kt&oacute;ra ma by&#263; instalowana przez wi&#281;ksz&#261; grup&#281; u&#380;ytkownik&oacute;w. Oferuje uporz&#261;dkowany proces instalacji i mo&#380;e wsp&oacute;&#322;pracowa&#263; z kana&#322;ami dystrybucji Windows. Trzeba jednak pami&#281;ta&#263; o <strong>certyfikacie podpisu</strong>, konfiguracji pakietu oraz uprawnieniach wymaganych przez aplikacj&#281;.</p><p>W dokumentacji Microsoftu publikowanie aplikacji Windows obejmuje zar&oacute;wno wdro&#380;enie do folderu, jak i przygotowanie pakietu MSIX. Ja przed wyborem sprawdzam, kto b&#281;dzie instalowa&#322; program, jak cz&#281;sto ma si&#281; aktualizowa&#263; i czy &#347;rodowisko firmowe akceptuje instalatory pakietowe.</p><p>Sam proces publikacji nie powinien by&#263; testowany dopiero w dniu premiery. Ju&#380; na pocz&#261;tku warto sprawdzi&#263; instalacj&#281; na czystej maszynie, brakuj&#261;ce runtime&rsquo;y, dost&#281;p do plik&oacute;w, uprawnienia oraz dzia&#322;anie aktualizacji. <strong>Aplikacja uruchamiaj&#261;ca si&#281; w Visual Studio nie zawsze jest gotowa do dystrybucji</strong>.</p><h2 id="kiedy-wybrac-te-technologie-a-kiedy-odpuscic">Kiedy wybra&#263; t&#281; technologi&#281;, a kiedy odpu&#347;ci&#263;</h2><p>Wybra&#322;bym j&#261;, gdy planujesz co najmniej dwie platformy, zesp&oacute;&#322; zna C# i XAML, a aplikacja ma wsp&oacute;&#322;dzieli&#263; logik&#281; biznesow&#261;. To tak&#380;e dobry kierunek dla projektu, kt&oacute;ry zaczyna si&#281; od Windowsa, ale w roadmapie ma wersj&#281; mobiln&#261;.</p><p>Zachowa&#322;bym ostro&#380;no&#347;&#263; przy aplikacji wy&#322;&#261;cznie windowsowej, kt&oacute;ra ma dzia&#322;a&#263; jak klasyczne narz&#281;dzie administracyjne z wieloma oknami, rozbudowanym drukowaniem i g&#322;&#281;bok&#261; integracj&#261; z systemem. W takim przypadku <strong>WPF mo&#380;e oznacza&#263; mniej ryzyka</strong>, szczeg&oacute;lnie gdy organizacja ma ju&#380; do&#347;wiadczenie i gotowe biblioteki.</p><p>Nie wybiera&#322;bym te&#380; frameworka tylko dlatego, &#380;e obiecuje jeden kod na wszystko. Przed decyzj&#261; buduj&#281; ma&#322;y prototyp z najtrudniejszym ekranem, logowaniem, dost&#281;pem do plik&oacute;w oraz publikacj&#261;. Je&#347;li prototyp przejdzie te testy, dopiero wtedy warto inwestowa&#263; w pe&#322;n&#261; aplikacj&#281;.</p><h2 id="windows-jako-pierwszy-cel-bez-zamykania-drogi-na-przyszlosc">Windows jako pierwszy cel bez zamykania drogi na przysz&#322;o&#347;&#263;</h2><p>Najrozs&#261;dniejsze podej&#347;cie polega na rozpocz&#281;ciu od do&#347;wiadczenia u&#380;ytkownika na Windowsie, ale od pocz&#261;tku oddzieleniu logiki biznesowej od platformy. Dzi&#281;ki temu aplikacja mo&#380;e wygl&#261;da&#263; jak porz&#261;dny program desktopowy, a nie jak mobilny ekran powi&#281;kszony do rozmiaru monitora.</p><p>W 2026 roku .NET MAUI jest sensownym wyborem dla zespo&#322;&oacute;w, kt&oacute;re potrzebuj&#261; wsp&oacute;lnej aplikacji C# dla Windows i innych system&oacute;w. Nie zast&#281;puje WPF ani WinUI 3 w ka&#380;dym scenariuszu, lecz dobrze &#322;&#261;czy &#347;wiat aplikacji desktopowych i mobilnych. <strong>Najwa&#380;niejsza decyzja nie dotyczy samego frameworka, tylko zakresu platform i oczekiwanej integracji z Windowsem</strong>.</p><p>Je&#380;eli masz jedn&#261; aplikacj&#281;, kilka docelowych system&oacute;w i kontrol&#281; nad architektur&#261;, zacznij od ma&#322;ego prototypu oraz testu wdro&#380;enia. To najszybciej poka&#380;e, czy wsp&oacute;lny kod rzeczywi&#347;cie skr&oacute;ci prac&#281;, czy wymagania desktopowe b&#281;d&#261; wymaga&#322;y technologii stricte windowsowej.</p>]]></content:encoded>
      <author>Radosław Krajewski</author>
      <category>Windows i aplikacje desktopowe</category>
      <media:thumbnail url="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/thumbnail/4ce2b766c8363fdd9a4dadc22ec7d39f/net-maui-na-windowsie-konfiguracja-publikacja-i-wybor.webp"/>
      <pubDate>Mon, 14 Sep 2026 18:50:00 +0200</pubDate>
    </item>
    <item>
      <title>CONVERT w SQL Server - daty, TRY_CONVERT i wydajność</title>
      <link>https://kursdotnet.pl/convert-w-sql-server-daty-try-convert-i-wydajnosc</link>
      <description>CONVERT w SQL Server: poznaj style dat, TRY_CONVERT, długość tekstu i pułapki wydajności. Sprawdź praktyczne przykłady.</description>
      <content:encoded><![CDATA[<?xml encoding="utf-8" ?><p>Kiedy tekst z pliku CSV ma sta&#263; si&#281; liczb&#261;, a data z formularza poprawn&#261; warto&#347;ci&#261; <code>date</code>, samo przechowywanie danych nie wystarczy. W <a href="https://kursdotnet.pl/entity-framework-code-first-bez-pulapek-model-i-migracje">SQL Server</a> funkcja <code>CONVERT</code> pozwala kontrolowa&#263; takie zmiany, w tym format daty, d&#322;ugo&#347;&#263; tekstu i spos&oacute;b obs&#322;ugi nieprawid&#322;owych warto&#347;ci. Poka&#380;&#281; sk&#322;adni&#281;, praktyczne przyk&#322;ady, <a href="https://kursdotnet.pl/partition-by-w-sql-skladnia-przyklady-i-roznice-wzgledem-group-by">r&oacute;&#380;nice wzgl&#281;dem</a> <code>CAST</code>, u&#380;ycie <code>TRY_CONVERT</code> oraz b&#322;&#281;dy, kt&oacute;re potrafi&#261; zepsu&#263; wyniki albo wydajno&#347;&#263; zapytania.

</p><div class="short-summary">
  <h2 id="najwazniejsze-zasady-konwersji-danych-w-sql-server">Najwa&#380;niejsze zasady konwersji danych w SQL Server</h2>
  <ul>
    <li>
<strong><code>CONVERT</code></strong> zmienia typ danych i pozwala dodatkowo okre&#347;li&#263; styl formatowania.</li>
    <li>
<strong>Styl 23, 112 lub 126</strong> jest bezpieczniejszy dla dat ni&#380; niejednoznaczne formaty lokalne.</li>
    <li>
<strong><code>TRY_CONVERT</code></strong> zwraca <code>NULL</code>, gdy zmiana typu si&#281; nie powiedzie, zamiast przerywa&#263; ca&#322;e zapytanie.</li>
    <li>
<strong>Jawna d&#322;ugo&#347;&#263; tekstu</strong> chroni przed obci&#281;ciem warto&#347;ci podczas konwersji.</li>
    <li>
<strong>Konwersja kolumny w filtrze</strong> mo&#380;e zablokowa&#263; u&#380;ycie indeksu i znacz&#261;co spowolni&#263; zapytanie.</li>
  </ul>
</div><h2 id="czym-jest-convert-i-kiedy-warto-go-uzywac">Czym jest CONVERT i kiedy warto go u&#380;ywa&#263;</h2><p><code>CONVERT</code> s&#322;u&#380;y do jawnej zmiany typu danych w wyra&#380;eniu. Mo&#380;na na przyk&#322;ad zamieni&#263; tekst na liczb&#281;, dat&#281; na tekst albo warto&#347;&#263; typu <code>datetime</code> na sam&#261; dat&#281;. Dla mnie najwa&#380;niejsza zaleta tej funkcji to mo&#380;liwo&#347;&#263; okre&#347;lenia <strong>stylu konwersji</strong>, szczeg&oacute;lnie przy pracy z datami.</p><pre><code>CONVERT (typ_danych [(d&#322;ugo&#347;&#263;)], wyra&#380;enie [, styl])</code></pre><p>Prosty przyk&#322;ad wygl&#261;da tak:</p><pre><code>SELECT CONVERT(int, '2026') AS Rok,
       CONVERT(varchar(10), 12345) AS Numer;</code></pre><p>W pierwszym przypadku tekst zostaje zamieniony na liczb&#281; ca&#322;kowit&#261;. W drugim liczba trafia do tekstu, kt&oacute;rego maksymalna d&#322;ugo&#347;&#263; wynosi <strong>10 znak&oacute;w</strong>. Ten pozornie drobny parametr ma znaczenie, bo pomini&#281;cie d&#322;ugo&#347;ci dla typ&oacute;w tekstowych mo&#380;e prowadzi&#263; do nieoczywistych rezultat&oacute;w.</p><h3 id="convert-a-cast">CONVERT a CAST</h3><p>Obie funkcje wykonuj&#261; konwersj&#281; typu, ale maj&#261; inn&#261; sk&#322;adni&#281;. <code>CAST</code> jest bardziej uniwersalny i czytelny, natomiast <code>CONVERT</code> daje dodatkowy parametr <strong>style</strong>, kt&oacute;ry steruje mi&#281;dzy innymi formatem daty.</p><table>
  <tbody>
    <tr>
      <th>Funkcja</th>
      <th>Przyk&#322;ad</th>
      <th>Kiedy wybra&#263;</th>
    </tr>
    <tr>
      <td><code>CAST</code></td>
      <td><code>CAST(Cena AS decimal(10,2))</code></td>
      <td>Gdy potrzebujesz prostej i przeno&#347;nej sk&#322;adni.</td>
    </tr>
    <tr>
      <td><code>CONVERT</code></td>
      <td><code>CONVERT(varchar(10), Data, 23)</code></td>
      <td>Gdy kontrolujesz format daty lub godzin&#281;.</td>
    </tr>
    <tr>
      <td><code>TRY_CONVERT</code></td>
      <td><code>TRY_CONVERT(int, Tekst)</code></td>
      <td>Gdy dane mog&#261; by&#263; niepoprawne i nie chcesz przerwa&#263; zapytania.</td>
    </tr>
  </tbody>
</table><p>W kodzie aplikacji .NET zwykle nie ma sensu u&#380;ywa&#263; <code>CONVERT</code> tylko dlatego, &#380;e jest bardziej rozbudowany. Je&#347;li nie potrzebuj&#281; stylu, wybieram <code>CAST</code>. Gdy jednak obrabiam daty pochodz&#261;ce z integracji, raport&oacute;w lub eksport&oacute;w, dodatkowy parametr cz&#281;sto decyduje o tym, czy wynik b&#281;dzie jednoznaczny.</p><h2 id="konwersja-liczb-i-tekstu-bez-nieprzyjemnych-niespodzianek">Konwersja liczb i tekstu bez nieprzyjemnych niespodzianek</h2><p>Najcz&#281;stszy scenariusz to konwersja warto&#347;ci tekstowej na typ liczbowy. <a href="https://kursdotnet.pl/update-z-join-w-sql-server-postgresql-i-mysql-praktyczny-poradnik">SQL Server</a> zaakceptuje tekst zawieraj&#261;cy poprawn&#261; liczb&#281;, ale odrzuci mi&#281;dzy innymi litery, nieprawid&#322;owe separatory oraz warto&#347;ci wykraczaj&#261;ce poza zakres danego typu.</p><pre><code>SELECT CONVERT(int, '42') AS Liczba,
       CONVERT(decimal(10,2), '1234.50') AS Kwota,
       CONVERT(varchar(20), 9876) AS TekstowaWartosc;</code></pre><p>Przy kwotach u&#380;ywam zawsze typu <code>decimal</code> z okre&#347;lon&#261; precyzj&#261; i skal&#261;. <strong>Nie stosuj&#281; <code>float</code> do pieni&#281;dzy</strong>, poniewa&#380; jest typem przybli&#380;onym i mo&#380;e przechowywa&#263; warto&#347;ci z drobnymi r&oacute;&#380;nicami wynikaj&#261;cymi ze sposobu zapisu binarnego.</p><h3 id="dlugosc-tekstu-ma-znaczenie">D&#322;ugo&#347;&#263; tekstu ma znaczenie</h3><p>Podczas konwersji do <code>varchar</code> albo <code>nvarchar</code> warto poda&#263; d&#322;ugo&#347;&#263; jawnie. W przeciwnym razie SQL Server mo&#380;e przyj&#261;&#263; domy&#347;ln&#261; d&#322;ugo&#347;&#263;, kt&oacute;ra nie pasuje do danego zastosowania, a przy zapisie do kr&oacute;tszej kolumny warto&#347;&#263; mo&#380;e zosta&#263; obci&#281;ta albo zako&#324;czy&#263; si&#281; b&#322;&#281;dem.</p><pre><code>SELECT CONVERT(varchar(5), 'Kursdotnet.pl') AS SkroconyTekst,
       CONVERT(varchar(30), 'Kursdotnet.pl') AS PelnyTekst;</code></pre><p>Pierwszy wynik zostanie ograniczony do pi&#281;ciu znak&oacute;w. To dobry przyk&#322;ad, dlaczego sama poprawno&#347;&#263; sk&#322;adni nie oznacza jeszcze poprawno&#347;ci biznesowej. W raportach takie obci&#281;cie bywa trudne do zauwa&#380;enia, szczeg&oacute;lnie gdy wynik jest eksportowany dalej.</p><h3 id="konwersja-znakow-dziesietnych">Konwersja znak&oacute;w dziesi&#281;tnych</h3><p>Format liczby musi odpowiada&#263; regu&#322;om interpretowanym przez SQL Server. Tekst <code>'1234.50'</code> jest typowym bezpiecznym przyk&#322;adem, ale dane w polskim formacie, na przyk&#322;ad <code>'1 234,50'</code>, mog&#261; wymaga&#263; wcze&#347;niejszego oczyszczenia.</p><pre><code>SELECT CONVERT(decimal(12,2),
       REPLACE(REPLACE('1 234,50', ' ', ''), ',', '.')) AS Kwota;</code></pre><p>Takie czyszczenie traktuj&#281; jako etap przygotowania danych, a nie uniwersaln&#261; metod&#281; dla ka&#380;dej lokalizacji. Przy wi&#281;kszej liczbie &#378;r&oacute;de&#322; lepiej ustali&#263; jeden format wej&#347;ciowy ju&#380; na granicy systemu, zamiast powtarza&#263; skomplikowane operacje w ka&#380;dym zapytaniu.</p><h2 id="daty-godziny-i-style-ktore-naprawde-maja-znaczenie">Daty, godziny i style, kt&oacute;re naprawd&#281; maj&#261; znaczenie</h2><p>Przy datach funkcja <code>CONVERT</code> jest szczeg&oacute;lnie u&#380;yteczna, bo pozwala oddzieli&#263; warto&#347;&#263; daty od jej prezentacji. Dla przyk&#322;adu styl <strong>23</strong> zwraca format <code>yyyy-mm-dd</code>, styl <strong>112</strong> daje <code>yyyymmdd</code>, a styl <strong>126</strong> zapisuje dat&#281; i czas w formacie zbli&#380;onym do ISO.</p><table>
  <tbody>
    <tr>
      <th>Styl</th>
      <th>Przyk&#322;adowy wynik</th>
      <th>Zastosowanie</th>
    </tr>
    <tr>
      <td><code>23</code></td>
      <td><code>2026-09-16</code></td>
      <td>Czytelna data bez godziny.</td>
    </tr>
    <tr>
      <td><code>112</code></td>
      <td><code>20260916</code></td>
      <td>Sortowanie i identyfikatory oparte na dacie.</td>
    </tr>
    <tr>
      <td><code>120</code></td>
      <td><code>2026-09-16 14:30:00</code></td>
      <td>Raporty i format techniczny bez strefy czasowej.</td>
    </tr>
    <tr>
      <td><code>126</code></td>
      <td><code>2026-09-16T14:30:00</code></td>
      <td>Integracje i wymiana danych w stylu ISO.</td>
    </tr>
    <tr>
      <td><code>103</code></td>
      <td><code>16/09/2026</code></td>
      <td>Prezentacja w popularnym formacie europejskim.</td>
    </tr>
  </tbody>
</table><pre><code>DECLARE @Data datetime2 = '2026-09-16T14:30:45.123';

SELECT CONVERT(varchar(10), @Data, 23) AS Data,
       CONVERT(varchar(8), @Data, 108) AS Godzina,
       CONVERT(varchar(23), @Data, 126) AS DataISO;</code></pre><p>W praktyce rozdzielam dwie rzeczy. <strong>Dat&#281; przechowuj&#281; jako typ daty</strong>, a do tekstu konwertuj&#281; j&#261; dopiero przy wy&#347;wietlaniu, eksporcie lub budowaniu komunikatu. Przechowywanie daty w kolumnie tekstowej utrudnia sortowanie, filtrowanie i walidacj&#281;.</p><h3 id="najwieksza-pulapka-to-niejednoznaczny-zapis">Najwi&#281;ksza pu&#322;apka to niejednoznaczny zapis</h3><p>Zapis typu <code>03/04/2026</code> mo&#380;e oznacza&#263; 3 kwietnia albo 4 marca, zale&#380;nie od przyj&#281;tej interpretacji. Dlatego przy danych technicznych preferuj&#281; format <strong><code>yyyy-MM-dd</code> lub ISO</strong>, a przy konwersji wej&#347;ciowego tekstu jawnie wskazuj&#281; styl, je&#347;li jest dost&#281;pny dla danego przypadku.</p><pre><code>SELECT CONVERT(date, '20260403', 112) AS BezpiecznaData,
       CONVERT(date, '03/04/2026', 103) AS DataEuropejska;</code></pre><p>Unikam r&oacute;wnie&#380; dwucyfrowych lat. SQL Server ma regu&#322;&#281; interpretacji takich warto&#347;ci, ale wynik mo&#380;e nie odpowiada&#263; intencji autora danych. Czterocyfrowy rok usuwa problem i kosztuje tylko dwa dodatkowe znaki.</p><h2 id="try-convert-pomaga-przezyc-kontakt-z-nieidealnymi-danymi">TRY_CONVERT pomaga prze&#380;y&#263; kontakt z nieidealnymi danymi</h2><p>Je&#347;li zwyk&#322;y <code>CONVERT</code> nie potrafi zmieni&#263; warto&#347;ci, zwraca b&#322;&#261;d i mo&#380;e przerwa&#263; ca&#322;e zapytanie. W przypadku import&oacute;w, formularzy i danych pochodz&#261;cych z zewn&#281;trznych system&oacute;w cz&#281;sto lepszym wyborem jest <strong><code>TRY_CONVERT</code></strong>, kt&oacute;ry zwraca <code>NULL</code>, gdy konwersja si&#281; nie powiedzie.</p><pre><code>SELECT Wartosc,
       TRY_CONVERT(int, Wartosc) AS Liczba
FROM (VALUES ('100'), ('250'), ('brak danych'), ('')) AS Dane(Wartosc);</code></pre><p>Dzi&#281;ki temu mo&#380;na odseparowa&#263; poprawne rekordy od problematycznych bez przerywania ca&#322;ego procesu. Samo <code>NULL</code> nie m&oacute;wi jednak, dlaczego konwersja si&#281; nie uda&#322;a, wi&#281;c przy imporcie warto zachowa&#263; warto&#347;&#263; &#378;r&oacute;d&#322;ow&#261; i oznaczy&#263; rekord do dalszej kontroli.</p><h3 id="filtrowanie-blednych-wartosci">Filtrowanie b&#322;&#281;dnych warto&#347;ci</h3><pre><code>SELECT Wartosc
FROM ImportDanych
WHERE TRY_CONVERT(decimal(12,2), Wartosc) IS NULL
  AND NULLIF(LTRIM(RTRIM(Wartosc)), '') IS NOT NULL;</code></pre><p>Ten warunek pomija puste warto&#347;ci, ale pokazuje teksty, kt&oacute;rych nie mo&#380;na zamieni&#263; na kwot&#281;. To praktyczniejszy wzorzec ni&#380; pr&oacute;ba konwersji ca&#322;ej tabeli za pomoc&#261; funkcji, kt&oacute;ra zako&#324;czy si&#281; b&#322;&#281;dem przy pierwszym niepoprawnym rekordzie.</p><p><code>TRY_CONVERT</code> nie rozwi&#261;zuje ka&#380;dego problemu. Nieprawid&#322;owy typ docelowy albo konwersja ca&#322;kowicie niedozwolona przez SQL Server nadal mo&#380;e zako&#324;czy&#263; si&#281; b&#322;&#281;dem. Funkcja chroni g&#322;&oacute;wnie przed <strong>niepoprawn&#261; zawarto&#347;ci&#261; danych</strong>, a nie przed b&#322;&#281;dn&#261; konstrukcj&#261; zapytania.</p><h2 id="wydajnosc-i-bledy-ktore-pojawiaja-sie-w-produkcji">Wydajno&#347;&#263; i b&#322;&#281;dy, kt&oacute;re pojawiaj&#261; si&#281; w produkcji</h2><p>Konwersja jest prosta sk&#322;adniowo, ale mo&#380;e mie&#263; du&#380;y wp&#322;yw na plan wykonania. Najcz&#281;&#347;ciej problem pojawia si&#281; wtedy, gdy funkcja obejmuje kolumn&#281; u&#380;ywan&#261; w filtrze, poniewa&#380; SQL Server mo&#380;e nie wykorzysta&#263; efektywnie indeksu.</p><pre><code>-- Mniej korzystny wzorzec
SELECT *
FROM Zamowienia
WHERE CONVERT(date, DataUtworzenia) = '2026-09-16';

-- Lepszy wzorzec dla kolumny datetime
SELECT *
FROM Zamowienia
WHERE DataUtworzenia &gt;= '2026-09-16'
  AND DataUtworzenia &lt;  '2026-09-17';</code></pre><p>Drugi wariant nie konwertuje ka&#380;dej warto&#347;ci w kolumnie i zachowuje przedzia&#322;, kt&oacute;ry mo&#380;e zosta&#263; obs&#322;u&#380;ony przez indeks. Przy du&#380;ych tabelach r&oacute;&#380;nica potrafi by&#263; bardzo wyra&#378;na, dlatego przed u&#380;yciem funkcji w <code>WHERE</code> sprawdzam plan wykonania.</p><h3 id="konwersje-niejawne">Konwersje niejawne</h3><p>SQL Server potrafi konwertowa&#263; typy automatycznie. Je&#380;eli por&oacute;wnuj&#281; kolumn&#281; liczbow&#261; z tekstem albo mieszam typy <code>varchar</code> i <code>nvarchar</code>, silnik mo&#380;e wykona&#263; konwersj&#281; niejawn&#261;, kt&oacute;rej nie wida&#263; bez analizy planu.</p><pre><code>-- Kolumna KodProduktu ma typ int
SELECT *
FROM Produkty
WHERE KodProduktu = '100';</code></pre><p>Taki kod mo&#380;e zadzia&#322;a&#263;, ale jawne dopasowanie typu jest czytelniejsze i bezpieczniejsze. Zamiast polega&#263; na automatyce, przekazuj&#281; parametr jako <code>int</code> albo jawnie konwertuj&#281; warto&#347;&#263; wej&#347;ciow&#261; przed por&oacute;wnaniem.</p><p class="read-more"><strong>Przeczytaj r&oacute;wnie&#380;: <a href="https://kursdotnet.pl/funkcja-sql-skalarna-tabelaryczna-czy-procedura">Funkcja SQL - skalarna, tabelaryczna czy procedura?</a></strong></p><h3 id="konwersja-nie-naprawia-zlego-modelu-danych">Konwersja nie naprawia z&#322;ego modelu danych</h3><p>Je&#380;eli data jest przechowywana jako tekst, a kwota jako tekst z lokalnym separatorem, ka&#380;da kwerenda musi wykonywa&#263; dodatkow&#261; prac&#281;. <strong>Najlepsza konwersja to cz&#281;sto ta, kt&oacute;rej nie trzeba wykonywa&#263;</strong>, bo dane ju&#380; maj&#261; w&#322;a&#347;ciwy typ w tabeli.</p><p>W aplikacjach .NET pilnuj&#281; te&#380;, aby parametry zapyta&#324; mia&#322;y odpowiednie typy. Parametr tekstowy por&oacute;wnywany z kolumn&#261; liczbow&#261; mo&#380;e wymusi&#263; dodatkowe przekszta&#322;cenia, a przy du&#380;ych zbiorach danych prze&#322;o&#380;y&#263; si&#281; na niepotrzebne obci&#261;&#380;enie serwera.</p><h2 id="najbezpieczniejszy-schemat-pracy-z-convert">Najbezpieczniejszy schemat pracy z CONVERT</h2><p>Najpierw ustalam, czy zmiana typu jest potrzebna do oblicze&#324;, filtrowania, czy tylko do prezentacji. Do prostych konwersji wybieram <code>CAST</code>, do formatowania dat i godzin <strong><code>CONVERT</code> ze wskazanym stylem</strong>, a przy danych niepewnych u&#380;ywam <code>TRY_CONVERT</code>.</p><p>Daty zapisuj&#281; w typach <code>date</code>, <code>datetime2</code> albo innym typie dobranym do wymaga&#324;, liczby finansowe w <code>decimal</code>, a tekst konwertuj&#281; z jawn&#261; d&#322;ugo&#347;ci&#261;. Taki zestaw zasad ogranicza b&#322;&#281;dy, poprawia czytelno&#347;&#263; kodu i pozwala unikn&#261;&#263; sytuacji, w kt&oacute;rej format prezentacji zaczyna decydowa&#263; o poprawno&#347;ci danych.</p><p>Je&#380;eli zapytanie dzia&#322;a wolno, sprawdzam przede wszystkim, czy funkcja nie zosta&#322;a na&#322;o&#380;ona na kolumn&#281; filtrowan&#261; lub &#322;&#261;czon&#261;. Sama sk&#322;adnia <code>CONVERT</code> jest prosta, ale dopiero w&#322;a&#347;ciwe miejsce jej u&#380;ycia decyduje o tym, czy rozwi&#261;zanie b&#281;dzie stabilne w realnej bazie.</p>]]></content:encoded>
      <author>Radosław Krajewski</author>
      <category>Bazy danych i SQL</category>
      <media:thumbnail url="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/thumbnail/9eb4b12b9ca72cfcd54db3fd15aeddfe/convert-w-sql-server-daty-try-convert-i-wydajnosc.webp"/>
      <pubDate>Mon, 14 Sep 2026 10:38:00 +0200</pubDate>
    </item>
    <item>
      <title>JOIN ON w SQL - jak łączyć tabele bez błędów?</title>
      <link>https://kursdotnet.pl/join-on-w-sql-jak-laczyc-tabele-bez-bledow</link>
      <description>JOIN ON w SQL wyjaśnione praktycznie: poznaj różnice między ON i WHERE, rodzaje złączeń oraz sposoby na duplikaty. Sprawdź poradnik!</description>
      <content:encoded><![CDATA[<?xml encoding="utf-8" ?><p>Gdy dane s&#261; roz&#322;o&#380;one na kilka tabel, samo pobranie rekord&oacute;w rzadko wystarcza. Trzeba jeszcze okre&#347;li&#263;, kt&oacute;re wiersze opisuj&#261; ten sam obiekt, a za to odpowiada warunek <code>ON</code> w konstrukcji <code>JOIN</code>, cz&#281;sto okre&#347;lanej skr&oacute;towo jako <code>join on</code>. Poka&#380;&#281;, jak budowa&#263; takie zapytania, czym r&oacute;&#380;ni si&#281; <code>ON</code> od <code>WHERE</code>, jak dzia&#322;aj&#261; rodzaje z&#322;&#261;cze&#324; oraz sk&#261;d bior&#261; si&#281; duplikaty i nieoczekiwane wyniki.</p><div class="short-summary">
<h2 id="warunek-on-decyduje-ktore-rekordy-zostana-polaczone">Warunek ON decyduje, kt&oacute;re rekordy zostan&#261; po&#322;&#261;czone</h2>
<ul>
<li>
<strong><code>JOIN ... ON</code></strong> opisuje relacj&#281; mi&#281;dzy tabelami.</li>
<li>Najcz&#281;&#347;ciej &#322;&#261;czy si&#281; <strong>klucz g&#322;&oacute;wny</strong> z odpowiadaj&#261;cym mu kluczem obcym.</li>
<li>
<strong><code>LEFT JOIN</code></strong> zachowuje wszystkie rekordy z lewej tabeli, nawet bez dopasowania.</li>
<li>Przeniesienie warunku z <strong><code>ON</code> do <code>WHERE</code></strong> mo&#380;e zmieni&#263; wynik <a href="https://kursdotnet.pl/mysql-outer-join-left-join-right-join-i-brakujace-dane">z&#322;&#261;czenia zewn&#281;trzne</a>go.</li>
<li>Duplikaty zwykle wynikaj&#261; z relacji <strong>jeden do wielu</strong>, a nie z b&#322;&#281;du samego SQL.</li>
</ul>
</div><p><img src="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/post_image/0e1524a842cbe8b1df761662d0127707/schemat-sql-join-on-relacja-miedzy-tabelami-diagram.webp" class="image article-image" loading="lazy" alt="Schemat typ&oacute;w SQL JOIN: INNER JOIN, CROSS JOIN, OUTER JOIN (LEFT OUTER JOIN, RIGHT OUTER JOIN) i ich podtypy."></p><h2 id="jak-dziala-warunek-on-w-sql">Jak dzia&#322;a warunek ON w SQL</h2><p>Najprostszy przyk&#322;ad &#322;&#261;czenia tabel wygl&#261;da tak:</p><pre><code>SELECT
    o.id,
    o.order_date,
    c.name
FROM orders AS o
JOIN customers AS c
    ON c.id = o.customer_id;</code></pre><p>Warunek <code>ON c.id = o.customer_id</code> m&oacute;wi silnikowi, &#380;e rekord zam&oacute;wienia nale&#380;y po&#322;&#261;czy&#263; z klientem o odpowiadaj&#261;cym identyfikatorze. <strong>Nie filtruj&#281; tutaj jeszcze zam&oacute;wie&#324; po dacie ani warto&#347;ci</strong>. Okre&#347;lam wy&#322;&#261;cznie, w jaki spos&oacute;b dwa &#378;r&oacute;d&#322;a danych s&#261; ze sob&#261; powi&#261;zane.</p><p>Alias, taki jak <code>o</code> lub <code>c</code>, nie jest obowi&#261;zkowy, ale bardzo poprawia czytelno&#347;&#263;. Szczeg&oacute;lnie przy kilku tabelach zawsze kwalifikuj&#281; kolumny nazw&#261; tabeli lub aliasem, poniewa&#380; zapis <code>id = id</code> mo&#380;e by&#263; niejednoznaczny albo prowadzi&#263; do pomy&#322;ki.</p><h3 id="co-musi-znajdowac-sie-w-warunku">Co musi znajdowa&#263; si&#281; w warunku</h3><p>Wyra&#380;enie po <code>ON</code> musi zwraca&#263; warto&#347;&#263; logiczn&#261;, czyli okre&#347;la&#263;, czy dana para wierszy spe&#322;nia warunek dopasowania. Najcz&#281;&#347;ciej jest to por&oacute;wnanie r&oacute;wno&#347;ci, ale SQL pozwala tak&#380;e na bardziej z&#322;o&#380;one regu&#322;y.</p><pre><code>ON p.category_id = c.id
AND p.is_active = 1</code></pre><p>W tym przypadku produkt jest &#322;&#261;czony z kategori&#261; tylko wtedy, gdy identyfikator kategorii pasuje, a produkt jest aktywny. Taki dodatkowy warunek ma znaczenie zw&#322;aszcza przy <strong>z&#322;&#261;czeniach zewn&#281;trznych</strong>, gdzie miejsce jego u&#380;ycia wp&#322;ywa na to, czy rekord bez dopasowania pozostanie w wyniku.</p><h2 id="inner-join-left-join-i-pozostale-warianty">INNER JOIN, LEFT JOIN i pozosta&#322;e warianty</h2><p>Samo s&#322;owo <code>JOIN</code> zwykle oznacza <code>INNER JOIN</code>. Zwraca ono wy&#322;&#261;cznie te wiersze, dla kt&oacute;rych warunek po obu stronach zosta&#322; spe&#322;niony.</p><pre><code>SELECT c.name, o.id
FROM customers AS c
INNER JOIN orders AS o
    ON o.customer_id = c.id;</code></pre><p>Klient bez zam&oacute;wie&#324; nie pojawi si&#281; w takim wyniku. To dobre rozwi&#261;zanie, gdy interesuj&#261; mnie wy&#322;&#261;cznie rekordy posiadaj&#261;ce powi&#261;zanie, na przyk&#322;ad lista zam&oacute;wie&#324; wraz z danymi klient&oacute;w.</p><p><code>LEFT JOIN</code> dzia&#322;a inaczej. Zachowuje wszystkie rekordy z lewej tabeli, a gdy nie znajdzie dopasowania po prawej stronie, wstawia warto&#347;ci <code>NULL</code>.</p><pre><code>SELECT
    c.name,
    o.id AS order_id
FROM customers AS c
LEFT JOIN orders AS o
    ON o.customer_id = c.id;</code></pre><p>To zapytanie poka&#380;e tak&#380;e klient&oacute;w, kt&oacute;rzy nigdy niczego nie zam&oacute;wili. Mog&#281; wykorzysta&#263; ten fakt do znalezienia takich os&oacute;b:</p><pre><code>SELECT c.id, c.name
FROM customers AS c
LEFT JOIN orders AS o
    ON o.customer_id = c.id
WHERE o.id IS NULL;</code></pre><p><strong><code>RIGHT JOIN</code></strong> zachowuje wszystkie rekordy z prawej tabeli, ale w praktyce cz&#281;sto zamieniam kolejno&#347;&#263; tabel i u&#380;ywam <code>LEFT JOIN</code>. <code>FULL OUTER JOIN</code> zachowuje rekordy z obu stron, r&oacute;wnie&#380; te bez dopasowania. Dost&#281;pno&#347;&#263; tego wariantu zale&#380;y od konkretnego systemu bazodanowego, dlatego przed u&#380;yciem sprawdzam dokumentacj&#281; danego silnika.</p><table>
<tbody>
<tr>
<th>Rodzaj <a href="https://kursdotnet.pl/join-w-mysql-bez-bledow-kiedy-uzyc-inner-left-i-cross-join">z&#322;&#261;czenia</a></th>
<th>Co zachowuje</th>
<th>Typowe zastosowanie</th>
</tr>
<tr>
<td><code>INNER JOIN</code></td>
<td>Tylko dopasowane rekordy</td>
<td>Raporty dotycz&#261;ce istniej&#261;cych relacji</td>
</tr>
<tr>
<td><code>LEFT JOIN</code></td>
<td>Wszystkie rekordy z lewej tabeli</td>
<td>Wyszukiwanie brakuj&#261;cych powi&#261;za&#324;</td>
</tr>
<tr>
<td><code>RIGHT JOIN</code></td>
<td>Wszystkie rekordy z prawej tabeli</td>
<td>Rzadziej u&#380;ywane odpowiedniki <code>LEFT JOIN</code>
</td>
</tr>
<tr>
<td><code>FULL OUTER JOIN</code></td>
<td>Rekordy z obu tabel</td>
<td>Por&oacute;wnywanie dw&oacute;ch zbior&oacute;w danych</td>
</tr>
</tbody>
</table><h2 id="on-a-where-przy-zlaczeniach-zewnetrznych">ON a WHERE przy z&#322;&#261;czeniach zewn&#281;trznych</h2><p>Najcz&#281;stszy b&#322;&#261;d polega na za&#322;o&#380;eniu, &#380;e warunek w <code>ON</code> i identyczny warunek w <code>WHERE</code> zawsze daj&#261; ten sam rezultat. Przy <code>INNER JOIN</code> r&oacute;&#380;nica cz&#281;sto nie jest widoczna, ale przy <code>LEFT JOIN</code> mo&#380;e ca&#322;kowicie zmieni&#263; sens zapytania.</p><p>Za&#322;&oacute;&#380;my, &#380;e chc&#281; wy&#347;wietli&#263; wszystkich klient&oacute;w oraz ich zam&oacute;wienia z 2026 roku. Warunek daty umieszczony w <code>ON</code> zachowa tak&#380;e klient&oacute;w bez takiego zam&oacute;wienia:</p><pre><code>SELECT c.name, o.id, o.order_date
FROM customers AS c
LEFT JOIN orders AS o
    ON o.customer_id = c.id
   AND o.order_date &gt;= '2026-01-01';</code></pre><p>Je&#347;li t&#281; sam&#261; dat&#281; przenios&#281; do <code>WHERE</code>, rekordy z <code>NULL</code> zostan&#261; odrzucone:</p><pre><code>SELECT c.name, o.id, o.order_date
FROM customers AS c
LEFT JOIN orders AS o
    ON o.customer_id = c.id
WHERE o.order_date &gt;= '2026-01-01';</code></pre><p>Drugie zapytanie zachowuje si&#281; praktycznie jak <strong><code>INNER JOIN</code></strong>, poniewa&#380; klient bez zam&oacute;wienia ma w kolumnie daty warto&#347;&#263; <code>NULL</code>, a warunek w <code>WHERE</code> nie jest dla niego spe&#322;niony. Moj&#261; prost&#261; zasad&#261; jest taka: <code>ON</code> opisuje, co ma zosta&#263; dopasowane, a <code>WHERE</code> okre&#347;la, co ma zosta&#263; pokazane po wykonaniu z&#322;&#261;czenia.</p><h2 id="laczenie-po-wielu-warunkach-i-innych-operatorach">&#321;&#261;czenie po wielu warunkach i innych operatorach</h2><p>Nie ka&#380;da relacja opiera si&#281; na jednej kolumnie. Przy kluczach z&#322;o&#380;onych trzeba por&oacute;wna&#263; wszystkie cz&#281;&#347;ci klucza, inaczej mo&#380;na po&#322;&#261;czy&#263; rekordy nale&#380;&#261;ce do r&oacute;&#380;nych okres&oacute;w, oddzia&#322;&oacute;w albo wersji danych.</p><pre><code>SELECT s.employee_id, s.work_date, e.name
FROM schedules AS s
JOIN employees AS e
    ON e.employee_id = s.employee_id
   AND e.branch_id = s.branch_id;</code></pre><p>Warunek mo&#380;e r&oacute;wnie&#380; u&#380;ywa&#263; operator&oacute;w innych ni&#380; <code>=</code>. Przyk&#322;adem jest z&#322;&#261;czenie przedzia&#322;owe, przydatne przy przypisywaniu ceny lub stawki obowi&#261;zuj&#261;cej w okre&#347;lonym czasie:</p><pre><code>SELECT o.id, r.rate
FROM orders AS o
JOIN tax_rates AS r
    ON o.order_date &gt;= r.valid_from
   AND o.order_date &lt; r.valid_to;</code></pre><p>Spotykam te&#380; z&#322;&#261;czenia oparte na przedzia&#322;ach liczbowych, na przyk&#322;ad przypisanie klienta do segmentu wydatk&oacute;w. Trzeba jednak zachowa&#263; ostro&#380;no&#347;&#263;, bo jeden rekord mo&#380;e pasowa&#263; do kilku przedzia&#322;&oacute;w i wtedy wynik zawiera wi&#281;cej wierszy, ni&#380; oczekujemy.</p><p class="read-more"><strong>Przeczytaj r&oacute;wnie&#380;: <a href="https://kursdotnet.pl/partition-by-w-sql-skladnia-przyklady-i-roznice-wzgledem-group-by">PARTITION BY w SQL - sk&#322;adnia, przyk&#322;ady i r&oacute;&#380;nice wzgl&#281;dem GROUP BY</a></strong></p><h3 id="relacja-jeden-do-wielu-i-pozorne-duplikaty">Relacja jeden do wielu i pozorne duplikaty</h3><p>Je&#380;eli jeden klient ma pi&#281;&#263; zam&oacute;wie&#324;, po&#322;&#261;czenie tabel zwr&oacute;ci pi&#281;&#263; wierszy tego klienta. To nie jest duplikat w sensie logicznym, tylko poprawny wynik relacji <strong>jeden do wielu</strong>.</p><pre><code>SELECT c.name, o.id
FROM customers AS c
JOIN orders AS o
    ON o.customer_id = c.id;</code></pre><p>Gdy potrzebuj&#281; jednego wiersza na klienta, agreguj&#281; dane zamiast usuwa&#263; rekordy przez przypadkowe <code>DISTINCT</code>:</p><pre><code>SELECT
    c.id,
    c.name,
    COUNT(o.id) AS order_count
FROM customers AS c
LEFT JOIN orders AS o
    ON o.customer_id = c.id
GROUP BY c.id, c.name;</code></pre><p><strong><code>COUNT(o.id)</code></strong>, a nie <code>COUNT(*)</code>, pozwala poprawnie otrzyma&#263; zero dla klienta bez zam&oacute;wie&#324;. To drobny szczeg&oacute;&#322;, kt&oacute;ry cz&#281;sto decyduje o poprawno&#347;ci raportu.</p><h2 id="najczestsze-bledy-przy-join-on">Najcz&#281;stsze b&#322;&#281;dy przy JOIN ... ON</h2><p>Najgro&#378;niejszy b&#322;&#261;d to pomini&#281;cie w&#322;a&#347;ciwego warunku &#322;&#261;czenia. Je&#347;li po&#322;&#261;cz&#281; tabele bez ograniczenia, mog&#281; uzyska&#263; iloczyn kartezja&#324;ski, czyli ka&#380;d&#261; kombinacj&#281; wiersza z pierwszej tabeli z ka&#380;dym wierszem z drugiej.</p><pre><code>SELECT c.name, o.id
FROM customers AS c
CROSS JOIN orders AS o;</code></pre><p>Przy 10 000 klient&oacute;w i 50 000 zam&oacute;wie&#324; taki wynik mo&#380;e teoretycznie zawiera&#263; nawet <strong>500 milion&oacute;w kombinacji</strong>. <code>CROSS JOIN</code> ma legalne zastosowania, ale przypadkowe uzyskanie takiego efektu zwykle oznacza brak warunku albo pomy&#322;k&#281; w aliasie.</p><p>Drugim problemem jest &#322;&#261;czenie kolumn o niezgodnych typach lub r&oacute;&#380;nych znaczeniach. Identyfikator klienta nie powinien by&#263; por&oacute;wnywany z numerem zam&oacute;wienia tylko dlatego, &#380;e obie kolumny s&#261; liczbami. Sprawdzam tak&#380;e, czy po obu stronach relacji wyst&#281;puj&#261; warto&#347;ci <code>NULL</code>, poniewa&#380; por&oacute;wnanie <code>NULL = NULL</code> nie zwraca warto&#347;ci prawda.</p><ul>
<li>U&#380;ywaj <strong>jednoznacznych alias&oacute;w</strong> tabel.</li>
<li>Sprawd&#378;, czy &#322;&#261;czysz klucz g&#322;&oacute;wny z w&#322;a&#347;ciwym kluczem obcym.</li>
<li>Policz oczekiwan&#261; liczb&#281; rekord&oacute;w przed i po z&#322;&#261;czeniu.</li>
<li>Nie dodawaj <code>DISTINCT</code>, dop&oacute;ki nie ustalisz &#378;r&oacute;d&#322;a powt&oacute;rze&#324;.</li>
<li>Przy <code>LEFT JOIN</code> &#347;wiadomie wybieraj miejsce dla filtr&oacute;w.</li>
</ul><p>W praktyce zaczynam od ma&#322;ego zapytania, wybieram tylko identyfikatory i dopiero po sprawdzeniu relacji dodaj&#281; kolejne kolumny. Taka metoda szybciej ujawnia b&#322;&#281;dne dopasowanie ni&#380; rozbudowany raport z kilkunastoma tabelami.</p><h2 id="wydajnosc-i-czytelnosc-zapytan">Wydajno&#347;&#263; i czytelno&#347;&#263; zapyta&#324;</h2><p>Sam zapis <code>JOIN ... ON</code> nie gwarantuje dobrego czasu wykonania. Du&#380;e znaczenie ma indeks na kolumnie u&#380;ywanej do &#322;&#261;czenia, zw&#322;aszcza po stronie klucza obcego. W tabelach zawieraj&#261;cych miliony rekord&oacute;w brak odpowiedniego indeksu mo&#380;e oznacza&#263; odczyt du&#380;ej cz&#281;&#347;ci danych zamiast szybkiego wyszukania dopasowania.</p><p>Nie zak&#322;adam jednak, &#380;e ka&#380;dy indeks automatycznie rozwi&#261;&#380;e problem. Sprawdzam plan wykonania zapytania i zwracam uwag&#281; na selektywno&#347;&#263; warunku, liczb&#281; przetwarzanych wierszy oraz to, czy filtr jest stosowany odpowiednio wcze&#347;nie.</p><pre><code>SELECT
    o.id,
    c.name
FROM orders AS o
JOIN customers AS c
    ON c.id = o.customer_id
WHERE o.status = 'paid';</code></pre><p>W tym przyk&#322;adzie indeks na <code>orders.customer_id</code> wspiera z&#322;&#261;czenie, a indeks zwi&#261;zany ze statusem mo&#380;e pom&oacute;c w filtrowaniu. Ostateczny efekt zale&#380;y od rozmiaru tabel, rozk&#322;adu warto&#347;ci i konkretnego silnika bazy, dlatego plan wykonania jest bardziej wiarygodny ni&#380; og&oacute;lna regu&#322;a.</p><p>Gdy kolumny maj&#261; t&#281; sam&#261; nazw&#281;, nie musz&#281; pisa&#263; d&#322;ugiego warunku. Niekt&oacute;re systemy obs&#322;uguj&#261; skr&oacute;con&#261; sk&#322;adni&#281; <code>USING</code>:</p><pre><code>SELECT c.name, o.id
FROM customers AS c
JOIN orders AS o
USING (customer_id);</code></pre><p>U&#380;ywam jej tylko wtedy, gdy kolumny rzeczywi&#347;cie maj&#261; identyczn&#261; nazw&#281; i znaczenie. W kodzie wsp&oacute;&#322;dzielonym przez kilka zespo&#322;&oacute;w jawne <code>ON c.id = o.customer_id</code> bywa czytelniejsze, a tak&#380;e lepiej pokazuje, kt&oacute;re pola tworz&#261; relacj&#281;.</p><h2 id="jak-szybko-sprawdzic-czy-zlaczenie-jest-poprawne">Jak szybko sprawdzi&#263;, czy z&#322;&#261;czenie jest poprawne</h2><p>Najpierw ustalam, jaki wynik ma reprezentowa&#263; jeden wiersz. Je&#347;li ma to by&#263; jedno zam&oacute;wienie, identyfikator zam&oacute;wienia powinien pozosta&#263; unikalny. Je&#347;li po dodaniu tabeli produkt&oacute;w liczba wierszy ro&#347;nie, prawdopodobnie wszed&#322;em w relacj&#281; jeden do wielu i musz&#281; zdecydowa&#263;, czy chc&#281; szczeg&oacute;&#322;y, czy agregat.</p><ol>
<li>Uruchom zapytanie tylko z identyfikatorami obu tabel.</li>
<li>Sprawd&#378; kilka rekord&oacute;w, dla kt&oacute;rych relacja powinna istnie&#263;.</li>
<li>Sprawd&#378; przypadek bez dopasowania, szczeg&oacute;lnie przy <code>LEFT JOIN</code>.</li>
<li>Por&oacute;wnaj liczb&#281; wierszy z oczekiwan&#261; kardynalno&#347;ci&#261; relacji.</li>
<li>Dopiero potem dodaj filtry, <a href="https://kursdotnet.pl/coalesce-w-postgresql-jak-bezpiecznie-obslugiwac-null">agregacje</a> i kolumny opisowe.</li>
</ol><p>Przydatny jest tak&#380;e test kontrolny z <code>COUNT</code>:</p><pre><code>SELECT
    COUNT(*) AS rows_after_join,
    COUNT(DISTINCT o.id) AS unique_orders
FROM orders AS o
JOIN customers AS c
    ON c.id = o.customer_id;</code></pre><p>Je&#380;eli liczba wierszy po z&#322;&#261;czeniu wyra&#378;nie przewy&#380;sza liczb&#281; unikalnych zam&oacute;wie&#324;, trzeba sprawdzi&#263;, czy tabela po prawej stronie nie zawiera kilku pasuj&#261;cych rekord&oacute;w. Najcz&#281;&#347;ciej oznacza to brak unikalno&#347;ci danych, zbyt szeroki warunek albo niepe&#322;ne po&#322;&#261;czenie po kluczu z&#322;o&#380;onym.</p><h2 id="dobry-warunek-laczenia-zaczyna-sie-od-modelu-danych">Dobry warunek &#322;&#261;czenia zaczyna si&#281; od modelu danych</h2><p><code>JOIN ... ON</code> jest prost&#261; sk&#322;adni&#261;, ale jego poprawno&#347;&#263; zale&#380;y od tego, czy rozumiem relacje zapisane w bazie. Najpierw identyfikuj&#281; klucze, kardynalno&#347;&#263; i oczekiwany poziom szczeg&oacute;&#322;owo&#347;ci wyniku, a dopiero p&oacute;&#378;niej wybieram typ z&#322;&#261;czenia i dodatkowe warunki.</p><p>Je&#347;li zapami&#281;tasz jedn&#261; zasad&#281;, niech b&#281;dzie ni&#261; ta: <strong><code>ON</code> okre&#347;la relacj&#281;, a <code>WHERE</code> filtruje rezultat</strong>. Rozdzielenie tych dw&oacute;ch odpowiedzialno&#347;ci chroni przed utrat&#261; rekord&oacute;w w <code>LEFT JOIN</code>, przypadkowymi multiplikacjami i raportami, kt&oacute;re wygl&#261;daj&#261; wiarygodnie, ale pokazuj&#261; niepoprawne dane.</p>]]></content:encoded>
      <author>Przemysław Kwiatkowski</author>
      <category>Bazy danych i SQL</category>
      <media:thumbnail url="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/thumbnail/ee3d4e455a7fd2c2c27b0762d1bfc00e/join-on-w-sql-jak-laczyc-tabele-bez-bledow.webp"/>
      <pubDate>Sun, 13 Sep 2026 16:16:00 +0200</pubDate>
    </item>
    <item>
      <title>Blazor vs Razor - co wybrać w aplikacji .NET?</title>
      <link>https://kursdotnet.pl/blazor-vs-razor-co-wybrac-w-aplikacji-net</link>
      <description>Blazor vs Razor Pages: poznaj różnice w renderowaniu, interaktywności i SEO. Sprawdź, kiedy wybrać Razor, a kiedy Blazora.</description>
      <content:encoded><![CDATA[<?xml encoding="utf-8" ?><p>Budujesz aplikacj&#281; w .NET i zastanawiasz si&#281;, czy wybra&#263; klasyczne Razor Pages, widoki MVC, czy Blazora? Por&oacute;wnanie Blazor vs Razor szybko pokazuje, &#380;e nie s&#261; to technologie tego samego rodzaju. Wyja&#347;niam, czym naprawd&#281; si&#281; r&oacute;&#380;ni&#261;, jak dzia&#322;a renderowanie, kiedy lepiej postawi&#263; na prostot&#281; Razor Pages, a kiedy na interaktywne komponenty Blazora.</p><div class="short-summary">
  <h2 id="najwazniejsze-roznice-widac-w-sposobie-renderowania-aplikacji">Najwa&#380;niejsze r&oacute;&#380;nice wida&#263; w sposobie renderowania aplikacji</h2>
  <ul>
    <li>
<strong>Razor</strong> jest sk&#322;adni&#261; &#322;&#261;cz&#261;c&#261; HTML z C#, a nie osobnym frameworkiem.</li>
    <li>
<strong>Razor Pages</strong> i widoki MVC generuj&#261; HTML na serwerze po &#380;&#261;daniu przegl&#261;darki.</li>
    <li>
<strong>Blazor</strong> buduje aplikacj&#281; z komponent&oacute;w, kt&oacute;re mog&#261; dzia&#322;a&#263; interaktywnie na serwerze lub w przegl&#261;darce.</li>
    <li>
<strong>Razor Pages</strong> zwykle wygrywa przy prostych formularzach, stronach contentowych i aplikacjach opartych na &#380;&#261;daniach HTTP.</li>
    <li>
<strong>Blazor</strong> lepiej pasuje do rozbudowanych paneli, dashboard&oacute;w i interfejs&oacute;w przypominaj&#261;cych aplikacje desktopowe.</li>
    <li>
<strong>Blazor Web App</strong> pozwala &#322;&#261;czy&#263; statyczne renderowanie z interaktywno&#347;ci&#261; tylko tam, gdzie jest potrzebna.</li>
  </ul>
</div><h2 id="blazor-i-razor-rozwiazuja-rozne-problemy">Blazor i Razor rozwi&#261;zuj&#261; r&oacute;&#380;ne problemy</h2><p>Najwi&#281;ksze nieporozumienie polega na traktowaniu Razor jako konkurenta dla Blazora. <strong>Razor to sk&#322;adnia</strong>, kt&oacute;ra pozwala umieszcza&#263; kod C# w znacznikach HTML. Korzystaj&#261; z niej mi&#281;dzy innymi widoki MVC, Razor Pages oraz komponenty Blazora.</p><p>Gdy widz&#281; pytanie o r&oacute;&#380;nic&#281; mi&#281;dzy tymi technologiami, najpierw rozdzielam trzy poj&#281;cia. Razor mo&#380;e oznacza&#263; sam&#261; sk&#322;adni&#281;, Razor Pages jako model budowania stron albo widoki MVC zapisane w plikach <code>.cshtml</code>. Blazor jest natomiast <strong>modelem komponentowym</strong> dla aplikacji webowych, zwykle opartym na plikach <code>.razor</code>.</p><h3 id="razor-pages-i-widoki-mvc">Razor Pages i widoki MVC</h3><p>W Razor Pages strona sk&#322;ada si&#281; zazwyczaj z pliku <code>.cshtml</code> oraz powi&#261;zanego pliku <code>.cshtml.cs</code>, czyli klasy PageModel. Kod serwerowy przygotowuje dane, a Razor generuje z nich HTML. Po wys&#322;aniu odpowiedzi instancja strony nie musi zachowywa&#263; swojego stanu mi&#281;dzy &#380;&#261;daniami.</p><p>To podej&#347;cie dobrze pasuje do witryn firmowych, blog&oacute;w, paneli administracyjnych i formularzy. <strong>Ka&#380;da akcja mo&#380;e by&#263; zwyk&#322;ym &#380;&#261;daniem HTTP</strong>, co upraszcza obs&#322;ug&#281;, testowanie i wdra&#380;anie aplikacji.</p><h3 id="blazor-jako-framework-komponentow">Blazor jako framework komponent&oacute;w</h3><p>Blazor dzieli interfejs na komponenty. Komponent mo&#380;e by&#263; stron&#261;, <a href="https://kursdotnet.pl/naglowek-h2-w-html-jak-budowac-dobra-hierarchie">formularze</a>m, tabel&#261;, modalem albo niewielkim fragmentem interfejsu wielokrotnie u&#380;ywanym w r&oacute;&#380;nych miejscach. Mo&#380;e te&#380; przechowywa&#263; stan i reagowa&#263; na zdarzenia u&#380;ytkownika bez prze&#322;adowywania ca&#322;ej strony.</p><pre><code><button>Klikni&#281;to @count razy</button>

@code {
    private int count;

    private void Increase()
    {
        count++;
    }
}</code></pre><p>W tym przyk&#322;adzie nie wysy&#322;amy formularza do serwera po ka&#380;dym klikni&#281;ciu. <strong>Zmienia si&#281; stan komponentu</strong>, a Blazor aktualizuje potrzebny fragment interfejsu. To w&#322;a&#347;nie interaktywno&#347;&#263;, a nie sama obecno&#347;&#263; sk&#322;adni Razor, jest najwa&#380;niejsz&#261; cech&#261; tej technologii.</p><h2 id="najwieksza-roznica-dotyczy-sposobu-renderowania">Najwi&#281;ksza r&oacute;&#380;nica dotyczy sposobu renderowania</h2><p>Razor Pages i klasyczne widoki MVC generuj&#261; stron&#281; na serwerze. Przegl&#261;darka wysy&#322;a &#380;&#261;danie, serwer pobiera dane, wykonuje kod Razor i zwraca gotowy HTML. Przy kolejnej akcji, na przyk&#322;ad wys&#322;aniu formularza, proces mo&#380;e rozpocz&#261;&#263; si&#281; ponownie.</p><p>W Blazorze sytuacja zale&#380;y od wybranego modelu renderowania. W aktualnym Blazor Web App mo&#380;na u&#380;y&#263; statycznego SSR, interaktywnego Blazor Server, WebAssembly albo trybu automatycznego. Dzi&#281;ki temu nie trzeba wybiera&#263; jednego sposobu dla ca&#322;ej aplikacji.</p><table>
  <tbody>
    <tr>
      <th>Kryterium</th>
      <th>Razor Pages lub MVC</th>
      <th>Blazor</th>
    </tr>
    <tr>
      <td>Podstawowa jednostka</td>
      <td>Strona lub widok</td>
      <td>Komponent</td>
    </tr>
    <tr>
      <td>Typowy plik</td>
      <td><code>.cshtml</code></td>
      <td><code>.razor</code></td>
    </tr>
    <tr>
      <td>Renderowanie</td>
      <td>HTML generowany na serwerze</td>
      <td>SSR, Server, WebAssembly lub tryb mieszany</td>
    </tr>
    <tr>
      <td>Obs&#322;uga klikni&#281;cia</td>
      <td>Najcz&#281;&#347;ciej nowe &#380;&#261;danie HTTP</td>
      <td>Zdarzenie obs&#322;ugiwane przez komponent</td>
    </tr>
    <tr>
      <td>Stan interfejsu</td>
      <td>Zwykle odtwarzany przy kolejnym &#380;&#261;daniu</td>
      <td>Mo&#380;e by&#263; utrzymywany w komponencie</td>
    </tr>
    <tr>
      <td>JavaScript</td>
      <td>Cz&#281;sto u&#380;ywany do interakcji w przegl&#261;darce</td>
      <td>Mo&#380;e by&#263; ograniczony, ale nadal dost&#281;pny przez JS interop</td>
    </tr>
  </tbody>
</table><p>W Razor Pages ka&#380;da linia kodu ostatecznie emituje tekst HTML. W Blazorze komponent tworzy struktur&#281; renderowania, a framework mo&#380;e obliczy&#263; r&oacute;&#380;nic&#281; mi&#281;dzy poprzednim i nowym stanem interfejsu. <strong>Nie ka&#380;da zmiana wymaga od&#347;wie&#380;enia ca&#322;ego dokumentu</strong>, co robi du&#380;&#261; r&oacute;&#380;nic&#281; w z&#322;o&#380;onych widokach.</p><p>Trzeba jednak uwa&#380;a&#263; na uproszczenie, &#380;e Blazor zawsze dzia&#322;a jak aplikacja SPA. Wsp&oacute;&#322;czesny Blazor potrafi rozpocz&#261;&#263; od statycznego HTML, a interaktywno&#347;&#263; w&#322;&#261;czy&#263; tylko dla wybranych komponent&oacute;w. To cz&#281;sto rozs&#261;dniejszy kompromis ni&#380; budowanie ca&#322;ej aplikacji jako jednego klienta WebAssembly.</p><h2 id="kiedy-lepiej-wybrac-razor-pages-lub-mvc">Kiedy lepiej wybra&#263; Razor Pages lub MVC</h2><p>Razor Pages wybra&#322;bym wtedy, gdy aplikacja jest przede wszystkim zbiorem stron i formularzy, a interakcje nie wymagaj&#261; rozbudowanego lokalnego stanu. Dla wielu paneli administracyjnych, serwis&oacute;w informacyjnych i aplikacji biznesowych to nadal <strong>najprostsza droga do dzia&#322;aj&#261;cego rozwi&#261;zania</strong>.</p><ul>
  <li>Strony marketingowe i serwisy z du&#380;&#261; ilo&#347;ci&#261; tre&#347;ci.</li>
  <li>Blogi, dokumentacje i portale, w kt&oacute;rych wa&#380;ne s&#261; szybkie odpowiedzi HTML.</li>
  <li>Formularze rejestracji, edycji danych i obs&#322;ugi proces&oacute;w biznesowych.</li>
  <li>Proste panele administracyjne z niewielk&#261; liczb&#261; element&oacute;w interaktywnych.</li>
  <li>Aplikacje, w kt&oacute;rych zesp&oacute;&#322; dobrze zna MVC i nie chce wprowadza&#263; kolejnego modelu komponent&oacute;w.</li>
</ul><p>Razor Pages ma te&#380; praktyczn&#261; zalet&#281; po stronie infrastruktury. Nie trzeba utrzymywa&#263; sta&#322;ego obwodu interaktywnego dla ka&#380;dego u&#380;ytkownika, a obs&#322;uga &#380;&#261;da&#324; jest dobrze znana z klasycznego <a href="https://kursdotnet.pl/aspnet-core-mvc-od-podstaw-routing-widoki-i-architektura">ASP.NET Core</a>. Przy du&#380;ej liczbie kr&oacute;tkich wizyt mo&#380;e to u&#322;atwi&#263; skalowanie.</p><p>Nie oznacza to, &#380;e Razor Pages jest zawsze szybsze. Je&#347;li strona wykonuje wiele pe&#322;nych prze&#322;adowa&#324; i u&#380;ytkownik cz&#281;sto przechodzi mi&#281;dzy formularzami, odczuwalna p&#322;ynno&#347;&#263; mo&#380;e by&#263; gorsza ni&#380; w aplikacji komponentowej. W prostym projekcie ten koszt bywa jednak mniejszy ni&#380; koszt dodatkowej z&#322;o&#380;ono&#347;ci Blazora.</p><h2 id="kiedy-blazor-daje-wyrazna-przewage">Kiedy Blazor daje wyra&#378;n&#261; przewag&#281;</h2><p>Blazor ma sens, gdy interfejs zachowuje si&#281; bardziej jak aplikacja ni&#380; dokument. Mam na my&#347;li dashboardy, konfiguratory, systemy obs&#322;ugi zg&#322;osze&#324;, edytory danych i ekrany, na kt&oacute;rych wiele element&oacute;w reaguje na siebie nawzajem.</p><h3 id="interfejs-z-duza-liczba-zdarzen">Interfejs z du&#380;&#261; liczb&#261; zdarze&#324;</h3><p>Je&#380;eli u&#380;ytkownik filtruje tabel&#281;, wybiera kilka opcji, otwiera modal i od razu widzi przeliczon&#261; warto&#347;&#263;, model komponentowy jest naturalny. <strong>Stan znajduje si&#281; bli&#380;ej interfejsu</strong>, a logika reakcji nie musi by&#263; rozrzucona mi&#281;dzy kontrolerem, formularzem, JavaScriptem i osobnymi endpointami.</p><h3 id="wspoldzielenie-komponentow">Wsp&oacute;&#322;dzielenie komponent&oacute;w</h3><p>W Blazorze mo&#380;na budowa&#263; w&#322;asne komponenty z parametrami, zdarzeniami i cyklem &#380;ycia. Raz przygotowana tabela, walidator czy selektor mo&#380;e by&#263; u&#380;ywana w wielu ekranach. To szczeg&oacute;lnie dobrze dzia&#322;a w systemach wewn&#281;trznych, gdzie wygl&#261;d i zachowanie interfejsu musz&#261; by&#263; sp&oacute;jne.</p><h3 id="wspolny-kod-c">Wsp&oacute;lny kod C#</h3><p>Je&#380;eli zesp&oacute;&#322; pracuje g&#322;&oacute;wnie w C#, Blazor pozwala ograniczy&#263; ilo&#347;&#263; kodu JavaScript potrzebnego do typowych interakcji. Nie usuwa to JavaScriptu ca&#322;kowicie, bo biblioteki przegl&#261;darkowe nadal mog&#261; by&#263; przydatne, ale <strong>logika aplikacji mo&#380;e pozosta&#263; w jednym ekosystemie</strong>.</p><p>Blazor nie jest jednak magicznym sposobem na zamian&#281; ka&#380;dej strony w szybsz&#261; aplikacj&#281;. Du&#380;y komponent z nieprzemy&#347;lanym stanem mo&#380;e by&#263; trudniejszy w utrzymaniu ni&#380; kilka prostych stron Razor Pages. Ja zwracam szczeg&oacute;ln&#261; uwag&#281; na granice komponent&oacute;w, bo to one cz&#281;sto decyduj&#261; o jako&#347;ci ca&#322;ego projektu.</p><h2 id="wydajnosc-seo-i-koszty-utrzymania-maja-rozne-profile">Wydajno&#347;&#263;, SEO i koszty utrzymania maj&#261; r&oacute;&#380;ne profile</h2><p>Nie istnieje jedna uniwersalna odpowied&#378; na pytanie, kt&oacute;re rozwi&#261;zanie jest szybsze. Wynik zale&#380;y od rozmiaru aplikacji, sposobu renderowania, zapyta&#324; do bazy, sieci i liczby r&oacute;wnoczesnych u&#380;ytkownik&oacute;w. Mo&#380;na jednak wskaza&#263; typowe kompromisy.</p><h3 id="razor-pages-i-szybki-pierwszy-widok">Razor Pages i szybki pierwszy widok</h3><p>W Razor Pages serwer zwraca gotowy HTML, wi&#281;c przegl&#261;darka mo&#380;e szybko pokaza&#263; zawarto&#347;&#263; bez pobierania pe&#322;nego runtime aplikacji. To korzystne dla stron publicznych, SEO i urz&#261;dze&#324; o s&#322;abszych parametrach. <strong>Pierwsze renderowanie jest zwykle &#322;atwe do przewidzenia</strong>.</p><h3 id="blazor-server-i-polaczenie-z-serwerem">Blazor Server i po&#322;&#261;czenie z serwerem</h3><p>W trybie interaktywnym Server kod komponentu dzia&#322;a na serwerze, a zdarzenia i aktualizacje interfejsu przechodz&#261; przez po&#322;&#261;czenie SignalR. Ka&#380;da aktywna sesja ma utrzymywany stan po stronie serwera, dlatego trzeba planowa&#263; pami&#281;&#263;, limity po&#322;&#261;cze&#324; i zachowanie aplikacji po utracie sieci.</p><p>To rozwi&#261;zanie mo&#380;e dawa&#263; bardzo szybki start, ale op&oacute;&#378;nienie sieci wp&#322;ywa na reakcj&#281; interfejsu. Dobrze dzia&#322;a w aplikacjach u&#380;ywanych przez pracownik&oacute;w w stabilnej sieci, natomiast w aplikacji publicznej z u&#380;ytkownikami mobilnymi trzeba dok&#322;adnie przetestowa&#263; zachowanie przy s&#322;abym po&#322;&#261;czeniu.</p><h3 id="blazor-webassembly-i-zasoby-klienta">Blazor WebAssembly i zasoby klienta</h3><p>W WebAssembly cz&#281;&#347;&#263; aplikacji dzia&#322;a w przegl&#261;darce. U&#380;ytkownik musi pobra&#263; runtime, biblioteki i kod aplikacji, wi&#281;c pierwszy start mo&#380;e by&#263; ci&#281;&#380;szy ni&#380; w Razor Pages. P&oacute;&#378;niej wiele operacji mo&#380;e odbywa&#263; si&#281; lokalnie, bez sta&#322;ej komunikacji z serwerem.</p><p>Nie podaj&#281; jednej liczby jako typowego rozmiaru aplikacji WebAssembly, bo zale&#380;y on od wersji .NET, bibliotek, kompresji i trimming&rsquo;u. Rozs&#261;dna decyzja powinna wynika&#263; z pomiaru konkretnego projektu, a nie z por&oacute;wnania przypadkowych przyk&#322;ad&oacute;w.</p><p class="read-more"><strong>Przeczytaj r&oacute;wnie&#380;: <a href="https://kursdotnet.pl/jak-sprawdzac-czy-plik-istnieje-w-nodejs">Jak sprawdza&#263;, czy plik istnieje w Node.js?</a></strong></p><h3 id="seo-i-bezpieczenstwo">SEO i bezpiecze&#324;stwo</h3><p>Razor Pages od pocz&#261;tku dostarcza wyszukiwarkom gotow&#261; tre&#347;&#263; HTML. Blazor Web App z renderowaniem po stronie serwera mo&#380;e osi&#261;gn&#261;&#263; podobny efekt, ale aplikacja oparta wy&#322;&#261;cznie na kliencie WebAssembly wymaga wi&#281;kszej ostro&#380;no&#347;ci przy indeksowaniu i pierwszym renderowaniu.</p><p>

W <a href="https://kursdotnet.pl/blazor-webassembly-kiedy-warto-wybrac-ten-model">Blazor WebAssembly</a> kod klienta nale&#380;y traktowa&#263; jako publiczny. <strong>Sekrety, klucze i regu&#322;y autoryzacji nie mog&#261; trafi&#263; do przegl&#261;darki</strong>. Niezale&#380;nie od wybranego modelu wra&#380;liwa logika powinna pozosta&#263; na serwerze.

</p><h2 id="najrozsadniejsza-decyzja-czesto-laczy-oba-podejscia">Najrozs&#261;dniejsza decyzja cz&#281;sto &#322;&#261;czy oba podej&#347;cia</h2><p>W nowych projektach nie zak&#322;ada&#322;bym automatycznie, &#380;e trzeba wybra&#263; wy&#322;&#261;cznie Blazora albo wy&#322;&#261;cznie Razor Pages. Blazor Web App pozwala renderowa&#263; cz&#281;&#347;&#263; aplikacji statycznie, a interaktywno&#347;&#263; w&#322;&#261;cza&#263; tylko dla tych komponent&oacute;w, kt&oacute;re naprawd&#281; jej potrzebuj&#261;.</p><p>Przyk&#322;adowo strona produktu mo&#380;e zosta&#263; wyrenderowana jako SSR, formularz konfiguracji mo&#380;e dzia&#322;a&#263; w trybie interaktywnym Server, a rozbudowany edytor mo&#380;e korzysta&#263; z WebAssembly. <strong>Tryb renderowania warto dobiera&#263; per komponent</strong>, a nie traktowa&#263; go jak decyzj&#281; podejmowan&#261; raz dla ca&#322;ej aplikacji.</p><p>Przy migracji z Razor Pages dobrze zacz&#261;&#263; od jednego ekranu o wysokiej interaktywno&#347;ci. Je&#380;eli komponent poprawia czytelno&#347;&#263; kodu i do&#347;wiadczenie u&#380;ytkownika, mo&#380;na przenosi&#263; kolejne fragmenty. Migracja wszystkiego naraz zwykle utrudnia ocen&#281;, czy zysk rzeczywi&#347;cie rekompensuje dodatkow&#261; z&#322;o&#380;ono&#347;&#263;.</p><ol>
  <li>Spisz ekrany, kt&oacute;re wymagaj&#261; cz&#281;stych aktualizacji bez prze&#322;adowania strony.</li>
  <li>Zostaw proste strony i formularze w Razor Pages, je&#347;li dzia&#322;aj&#261; dobrze.</li>
  <li>Wydziel komponenty Blazora tam, gdzie wyst&#281;puje du&#380;o lokalnego stanu.</li>
  <li>Wybierz Server, WebAssembly lub SSR na podstawie sieci, SEO i obci&#261;&#380;enia.</li>
  <li>Zmierz czas pierwszego renderowania, op&oacute;&#378;nienia interakcji i zu&#380;ycie pami&#281;ci.</li>
</ol><p>Takie podej&#347;cie ogranicza ryzyko architektoniczne. Zesp&oacute;&#322; nie musi prowadzi&#263; wojny technologicznej, bo oba modele mog&#261; wsp&oacute;&#322;pracowa&#263; w jednej aplikacji ASP.NET Core.</p><h2 id="moja-praktyczna-regula-wyboru-miedzy-blazorem-a-razor">Moja praktyczna regu&#322;a wyboru mi&#281;dzy Blazorem a Razor</h2><p>Gdy tworz&#281; stron&#281; opart&#261; g&#322;&oacute;wnie na tre&#347;ci, formularzach i klasycznym przep&#322;ywie &#380;&#261;da&#324;, zaczynam od Razor Pages. Daje prosty model mentalny, dobrze wsp&oacute;&#322;pracuje z SEO i nie wymaga utrzymywania interaktywnego stanu dla ka&#380;dego otwartego ekranu.</p><p>Gdy interfejs ma zachowywa&#263; si&#281; jak rozbudowane narz&#281;dzie, wybieram Blazora. Szczeg&oacute;lnie dobrze sprawdza si&#281; tam, gdzie u&#380;ytkownik stale filtruje dane, zmienia ustawienia, pracuje na wielu kontrolkach i oczekuje p&#322;ynnej reakcji bez pe&#322;nych prze&#322;adowa&#324;.</p><p>Najwa&#380;niejsze jest to, by nie myli&#263; sk&#322;adni z architektur&#261;. <strong>Razor mo&#380;e by&#263; wsp&oacute;lnym j&#281;zykiem obu podej&#347;&#263;</strong>, ale Razor Pages i Blazor organizuj&#261; aplikacj&#281; w zupe&#322;nie inny spos&oacute;b. Je&#347;li zaczniesz decyzj&#281; od potrzeb interfejsu, modelu ruchu i warunk&oacute;w wdro&#380;enia, wyb&oacute;r stanie si&#281; znacznie prostszy.</p>]]></content:encoded>
      <author>Radosław Krajewski</author>
      <category>Aplikacje webowe</category>
      <media:thumbnail url="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/thumbnail/88390adc2f9840ae7548816bf5bee640/blazor-vs-razor-co-wybrac-w-aplikacji-net.webp"/>
      <pubDate>Sun, 13 Sep 2026 14:55:00 +0200</pubDate>
    </item>
    <item>
      <title>Drzewo binarne w C# - jak działa i kiedy użyć BST?</title>
      <link>https://kursdotnet.pl/drzewo-binarne-w-c-jak-dziala-i-kiedy-uzyc-bst</link>
      <description>Drzewo binarne w C# bez tajemnic: poznaj BST, złożoność operacji, przejścia i SortedSet. Sprawdź, kiedy pisać własną implementację.</description>
      <content:encoded><![CDATA[<?xml encoding="utf-8" ?><p>Gdy dane maj&#261; naturaln&#261; hierarchi&#281;, zwyk&#322;a lista szybko przestaje by&#263; wygodna. <strong>Drzewo binarne</strong> porz&#261;dkuje elementy tak, aby ka&#380;dy w&#281;ze&#322; mia&#322; najwy&#380;ej dwoje dzieci, a odpowiednio zaprojektowana struktura pozwala sprawnie wyszukiwa&#263;, dodawa&#263; i przegl&#261;da&#263; dane. Poka&#380;&#281;, jak dzia&#322;a, czym r&oacute;&#380;ni si&#281; zwyk&#322;a wersja od BST oraz jak zaimplementowa&#263; j&#261; w C# i kiedy lepiej skorzysta&#263; z gotowych kolekcji .NET.</p><div class="short-summary">
<h2 id="najwazniejsze-informacje-o-strukturze-i-jej-zastosowaniach">Najwa&#380;niejsze informacje o strukturze i jej zastosowaniach</h2>
<ul>
<li>
<strong>Maksymalnie dwoje dzieci</strong> odr&oacute;&#380;nia t&#281; struktur&#281; od drzew o dowolnej liczbie ga&#322;&#281;zi.</li>
<li>
<strong>BST</strong> wykorzystuje regu&#322;&#281; porz&#261;dku, dzi&#281;ki kt&oacute;rej wyszukiwanie mo&#380;e dzia&#322;a&#263; w czasie O(log n).</li>
<li>
<strong>Niezr&oacute;wnowa&#380;enie</strong> mo&#380;e pogorszy&#263; <a href="https://kursdotnet.pl/dlugosc-listy-w-c-count-length-czy-capacity">wydajno&#347;&#263;</a> do O(n), dlatego kszta&#322;t drzewa ma du&#380;e znaczenie.</li>
<li>
<strong>Przej&#347;cia preorder, inorder i postorder</strong> s&#322;u&#380;&#261; do r&oacute;&#380;nych zada&#324;, od wy&#347;wietlania danych po usuwanie ca&#322;ej struktury.</li>
<li>
<strong>SortedSet<t></t></strong> w .NET jest gotow&#261;, samobalansuj&#261;c&#261; kolekcj&#261; opart&#261; na drzewie czerwono-czarnym.</li>
</ul>
</div><p><img src="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/post_image/72f43eb469b4244c514e5882012c2fd7/drzewo-binarne-bst-schemat-wezly-lewy-prawy-c.webp" class="image article-image" loading="lazy" alt="Schemat ilustruje drzewo binarne poszukiwa&#324; z w&#281;z&#322;ami 1, 10, 15, 16, 19, 37. Po prawej zasady budowy drzewa."></p><h2 id="jak-dziala-struktura-z-dwoma-dziecmi">Jak dzia&#322;a struktura z dwoma dzie&#263;mi</h2><p>Podstawowym elementem jest <strong>w&#281;ze&#322;</strong>, kt&oacute;ry przechowuje warto&#347;&#263; oraz odwo&#322;ania do lewego i prawego dziecka. Pierwszy w&#281;ze&#322; to korze&#324;, a element bez dzieci nazywamy li&#347;ciem. W przeciwie&#324;stwie do tablicy dane nie musz&#261; le&#380;e&#263; obok siebie w pami&#281;ci, bo relacje mi&#281;dzy nimi wynikaj&#261; z po&#322;&#261;cze&#324; mi&#281;dzy w&#281;z&#322;ami.</p><p>Sama zasada jest prosta: jeden w&#281;ze&#322; mo&#380;e mie&#263; <strong>zero, jedno albo dwa dzieci</strong>. Nie oznacza to jednak, &#380;e warto&#347;ci musz&#261; by&#263; u&#322;o&#380;one rosn&#261;co. Zwyk&#322;a struktura mo&#380;e przechowywa&#263; dowolne zale&#380;no&#347;ci, na przyk&#322;ad wyra&#380;enie matematyczne, gdzie operator znajduje si&#281; wy&#380;ej, a jego argumenty s&#261; dzie&#263;mi.</p><table>
<tbody>
<tr>
<th>Poj&#281;cie</th>
<th>Znaczenie</th>
</tr>
<tr>
<td>Korze&#324;</td>
<td>Pocz&#261;tkowy w&#281;ze&#322;, od kt&oacute;rego zaczyna si&#281; ca&#322;a struktura.</td>
</tr>
<tr>
<td>Rodzic</td>
<td>W&#281;ze&#322;, kt&oacute;ry wskazuje na jedno lub dwoje dzieci.</td>
</tr>
<tr>
<td>Li&#347;&#263;</td>
<td>W&#281;ze&#322; bez dzieci.</td>
</tr>
<tr>
<td>Wysoko&#347;&#263;</td>
<td>Liczba poziom&oacute;w lub kraw&#281;dzi na najd&#322;u&#380;szej &#347;cie&#380;ce od korzenia do li&#347;cia, zale&#380;nie od przyj&#281;tej definicji.</td>
</tr>
<tr>
<td>Poddrzewo</td>
<td>Dowolny w&#281;ze&#322; wraz z ca&#322;&#261; struktur&#261; znajduj&#261;c&#261; si&#281; pod nim.</td>
</tr>
</tbody>
</table><p>W praktyce spotykam kilka okre&#347;le&#324; opisuj&#261;cych kszta&#322;t drzewa. <strong>Drzewo pe&#322;ne</strong> ma w&#281;z&#322;y posiadaj&#261;ce albo dok&#322;adnie dwoje dzieci, albo &#380;adnego. <strong>Drzewo kompletne</strong> wype&#322;nia poziomy od lewej do prawej, a <strong>drzewo zr&oacute;wnowa&#380;one</strong> utrzymuje podobn&#261; wysoko&#347;&#263; po obu stronach. To w&#322;a&#347;nie wysoko&#347;&#263;, a nie sama liczba element&oacute;w, cz&#281;sto decyduje o szybko&#347;ci operacji.</p><h2 id="dlaczego-bst-jest-czyms-wiecej-niz-zwyklym-drzewem">Dlaczego BST jest czym&#347; wi&#281;cej ni&#380; zwyk&#322;ym drzewem</h2><p>Najcz&#281;&#347;ciej na zaj&#281;ciach i rozmowach rekrutacyjnych pojawia si&#281; <strong>drzewo wyszukiwa&#324; binarnych</strong>, czyli BST. Tutaj ka&#380;dy element w lewym poddrzewie jest mniejszy od warto&#347;ci w bie&#380;&#261;cym w&#281;&#378;le, a elementy po prawej stronie s&#261; wi&#281;ksze. Dla przyk&#322;adu po wstawieniu liczb 8, 3, 10, 1 i 6 wyszukanie liczby 6 nie wymaga sprawdzania wszystkich element&oacute;w.</p><p>Algorytm por&oacute;wnuje szukan&#261; warto&#347;&#263; z korzeniem. Gdy jest mniejsza, schodzi w lewo, gdy wi&#281;ksza, wybiera praw&#261; ga&#322;&#261;&#378;. W idealnie zr&oacute;wnowa&#380;onej strukturze po ka&#380;dym kroku pomijamy mniej wi&#281;cej po&#322;ow&#281; pozosta&#322;ych danych, wi&#281;c operacja ma koszt <strong>O(log n)</strong>.</p><p>Problem pojawia si&#281; wtedy, gdy warto&#347;ci trafiaj&#261; do drzewa w ju&#380; posortowanej kolejno&#347;ci. Wstawienie 1, 2, 3, 4 i 5 mo&#380;e utworzy&#263; struktur&#281; przypominaj&#261;c&#261; list&#281; jednokierunkow&#261;. Wtedy wyszukiwanie, dodawanie i usuwanie spadaj&#261; do <strong>O(n)</strong>. Z tego powodu sam fakt u&#380;ycia dw&oacute;ch dzieci nie gwarantuje dobrej wydajno&#347;ci.</p><h3 id="najwazniejsze-warianty">Najwa&#380;niejsze warianty</h3><ul>
<li>
<strong>Niezr&oacute;wnowa&#380;one BST</strong> jest &#322;atwe do napisania, ale wra&#380;liwe na kolejno&#347;&#263; danych.</li>
<li>
<strong>AVL</strong> pilnuje r&oacute;&#380;nicy wysoko&#347;ci poddrzew i cz&#281;sto zapewnia bardzo szybkie wyszukiwanie.</li>
<li>
<strong>Drzewo czerwono-czarne</strong> stosuje s&#322;absze ograniczenia r&oacute;wnowagi, ale sprawnie obs&#322;uguje cz&#281;ste modyfikacje.</li>
<li>
<strong>Kopiec binarny</strong> nie s&#322;u&#380;y do og&oacute;lnego wyszukiwania, tylko do szybkiego pobierania minimum albo maksimum.</li>
</ul><p>Te warianty nie s&#261; wymiennymi nazwami. Kopiec ma inn&#261; regu&#322;&#281; ni&#380; BST, a AVL i drzewa czerwono-czarne wymagaj&#261; rotacji, czyli kontrolowanych zmian po&#322;&#261;cze&#324; mi&#281;dzy w&#281;z&#322;ami. Sam zaczynam od prostego BST, gdy celem jest nauka, ale w kodzie produkcyjnym nie implementuj&#281; balansu bez wyra&#378;nej potrzeby.</p><h2 id="prosta-implementacja-w-c">Prosta implementacja w C#</h2><p>Minimalny model mo&#380;na zbudowa&#263; za pomoc&#261; generycznego w&#281;z&#322;a. Ograniczenie <strong>IComparable<t></t></strong> pozwala por&oacute;wnywa&#263; warto&#347;ci bez uzale&#380;niania struktury od konkretnego typu.</p><pre><code>public sealed class Node<t>
    where T : IComparable<t>
{
    public T Value { get; }
    public Node<t>? Left { get; set; }
    public Node<t>? Right { get; set; }

    public Node(T value)
    {
        Value = value;
    }
}</t></t></t></t></code></pre><p>Metoda wstawiaj&#261;ca schodzi rekurencyjnie w lewo lub w prawo, a&#380; znajdzie puste miejsce. W tym przyk&#322;adzie duplikaty s&#261; pomijane, ale w realnym projekcie mo&#380;na zamiast tego przechowywa&#263; licznik wyst&#261;pie&#324; albo kierowa&#263; r&oacute;wne warto&#347;ci do ustalonej ga&#322;&#281;zi.</p><pre><code><a href="https://kursdotnet.pl/public-static-w-c-jak-dziala-i-kiedy-tego-uzywac">public static</a> Node<t> Insert<t>(Node<t>? node, T value)
    where T : IComparable<t>
{
    if (node is null)
        return new Node<t>(value);

    int comparison = value.CompareTo(node.Value);

    if (comparison &lt; 0)
        node.Left = Insert(node.Left, value);
    else if (comparison &gt; 0)
        node.Right = Insert(node.Right, value);

    return node;
}</t></t></t></t></t></code></pre><p>Wyszukiwanie wygl&#261;da podobnie, ale nie tworzy nowych w&#281;z&#322;&oacute;w. Zwracam warto&#347;&#263; logiczn&#261;, poniewa&#380; w aplikacji cz&#281;sto nie potrzebuj&#281; ca&#322;ego w&#281;z&#322;a, tylko informacji, czy element istnieje.</p><pre><code>public static bool Contains<t>(Node<t>? node, T value)
    where T : IComparable<t>
{
    while (node is not null)
    {
        int comparison = value.CompareTo(node.Value);

        if (comparison == 0)
            return true;

        node = comparison &lt; 0
            ? node.Left
            : node.Right;
    }

    return false;
}</t></t></t></code></pre><p>Wersja iteracyjna ma tu praktyczn&#261; przewag&#281;. Przy bardzo wysokim, &#378;le zbalansowanym drzewie rekurencja mo&#380;e doprowadzi&#263; do przepe&#322;nienia stosu, a p&#281;tla nie zwi&#281;ksza g&#322;&#281;boko&#347;ci wywo&#322;a&#324;. To drobny szczeg&oacute;&#322;, kt&oacute;ry &#322;atwo przeoczy&#263; w przyk&#322;adzie edukacyjnym.</p><p class="read-more"><strong>Przeczytaj r&oacute;wnie&#380;: <a href="https://kursdotnet.pl/static-w-c-metody-pola-i-klasy-bez-typowych-pulapek">static w C# - metody, pola i klasy bez typowych pu&#322;apek</a></strong></p><h3 id="usuwanie-wymaga-obslugi-trzech-przypadkow">Usuwanie wymaga obs&#322;ugi trzech przypadk&oacute;w</h3><p>Usuni&#281;cie li&#347;cia jest proste. Gdy w&#281;ze&#322; ma jedno dziecko, jego rodzic przejmuje to dziecko. Najtrudniejszy jest w&#281;ze&#322; z dw&oacute;jk&#261; dzieci, poniewa&#380; trzeba zast&#261;pi&#263; jego warto&#347;&#263; najmniejszym elementem z prawego poddrzewa albo najwi&#281;kszym elementem z lewego poddrzewa, a potem usun&#261;&#263; ten zast&#281;pczy w&#281;ze&#322;.</p><p>To w&#322;a&#347;nie usuwanie najcz&#281;&#347;ciej ujawnia, czy implementacja naprawd&#281; rozumie struktur&#281;, czy tylko odtwarza schemat z podr&#281;cznika. Trzeba uwa&#380;a&#263; na usuwanie korzenia, warto&#347;ci duplikowane oraz zmian&#281; referencji w rodzicu.</p><h2 id="przechodzenie-po-wezlach-daje-rozne-wyniki">Przechodzenie po w&#281;z&#322;ach daje r&oacute;&#380;ne wyniki</h2><p>Przej&#347;cie, czyli traversal, okre&#347;la kolejno&#347;&#263; odwiedzania element&oacute;w. Dla drzewa z korzeniem 8, lewym poddrzewem 3 i prawym 10 wynik zale&#380;y od tego, czy odwiedzimy rodzica przed dzie&#263;mi, pomi&#281;dzy nimi czy po nich.</p><table>
<tbody>
<tr>
<th>Rodzaj przej&#347;cia</th>
<th>Kolejno&#347;&#263;</th>
<th>Typowe zastosowanie</th>
</tr>
<tr>
<td>Preorder</td>
<td>W&#281;ze&#322;, lewe poddrzewo, prawe poddrzewo</td>
<td>Kopiowanie struktury lub zapis drzewa.</td>
</tr>
<tr>
<td>Inorder</td>
<td>Lewe poddrzewo, w&#281;ze&#322;, prawe poddrzewo</td>
<td>Odczyt warto&#347;ci BST w kolejno&#347;ci rosn&#261;cej.</td>
</tr>
<tr>
<td>Postorder</td>
<td>Lewe poddrzewo, prawe poddrzewo, w&#281;ze&#322;</td>
<td>Usuwanie struktury lub obliczanie wyra&#380;e&#324;.</td>
</tr>
<tr>
<td>BFS</td>
<td>Poziomami od korzenia</td>
<td>Analiza poziom&oacute;w i najkr&oacute;tszych &#347;cie&#380;ek w strukturze.</td>
</tr>
</tbody>
</table><p>Najbardziej u&#380;yteczne w BST jest przej&#347;cie <strong>inorder</strong>, bo dzi&#281;ki regule porz&#261;dku zwraca dane rosn&#261;co. Rekurencyjna implementacja jest kr&oacute;tka, ale do du&#380;ych struktur mo&#380;na u&#380;y&#263; stosu jawnego, aby mie&#263; pe&#322;n&#261; kontrol&#281; nad pami&#281;ci&#261;.</p><pre><code>public static void InOrder<t>(Node<t>? node, List<t> result)
    where T : IComparable<t>
{
    if (node is null)
        return;

    InOrder(node.Left, result);
    result.Add(node.Value);
    InOrder(node.Right, result);
}</t></t></t></t></code></pre><p>Do przechodzenia poziomami wykorzystuj&#281; kolejk&#281;. Ka&#380;dy zdj&#281;ty z kolejki w&#281;ze&#322; dodaje swoje dzieci, wi&#281;c struktura odwiedzana jest warstwami, a nie w g&#322;&#261;b.</p><pre><code>public static IEnumerable<t> BreadthFirst<t>(Node<t> root)
    where T : IComparable<t>
{
    var queue = new Queue<node>&gt;();
    queue.Enqueue(root);

    while (queue.Count &gt; 0)
    {
        var node = queue.Dequeue();
        yield return node.Value;

        if (node.Left is not null)
            queue.Enqueue(node.Left);

        if (node.Right is not null)
            queue.Enqueue(node.Right);
    }
}</node></t></t></t></t></code></pre><h2 id="zlozonosc-operacji-i-wybor-kolekcji-w-net">Z&#322;o&#380;ono&#347;&#263; operacji i wyb&oacute;r kolekcji w .NET</h2><p>Dla zr&oacute;wnowa&#380;onej struktury wyszukiwanie, wstawianie i usuwanie maj&#261; koszt <strong>O(log n)</strong>. Przej&#347;cie po wszystkich elementach zawsze wymaga O(n), bo ka&#380;dy w&#281;ze&#322; musi zosta&#263; odwiedzony. Pami&#281;&#263; potrzebna na same w&#281;z&#322;y wynosi O(n), a dodatkowa pami&#281;&#263; zale&#380;y od rekurencji lub u&#380;ytego stosu i kolejki.</p><table>
<tbody>
<tr>
<th>Operacja</th>
<th>Drzewo zr&oacute;wnowa&#380;one</th>
<th>Najgorszy przypadek</th>
</tr>
<tr>
<td>Wyszukiwanie</td>
<td>O(log n)</td>
<td>O(n)</td>
</tr>
<tr>
<td>Wstawianie</td>
<td>O(log n)</td>
<td>O(n)</td>
</tr>
<tr>
<td>Usuwanie</td>
<td>O(log n)</td>
<td>O(n)</td>
</tr>
<tr>
<td>Przej&#347;cie po wszystkich elementach</td>
<td>O(n)</td>
<td>O(n)</td>
</tr>
</tbody>
</table><p>W aplikacji .NET rzadko potrzebuj&#281; pisa&#263; w&#322;asny balansuj&#261;cy wariant. <strong>SortedSet<t></t></strong> przechowuje unikalne elementy w uporz&#261;dkowany spos&oacute;b i zapewnia operacje o koszcie O(log n) dzi&#281;ki samobalansuj&#261;cej implementacji. Gdy potrzebuj&#281; par klucz-warto&#347;&#263;, naturalnym wyborem jest cz&#281;sto <strong>SortedDictionary<tkey tvalue></tkey></strong>.</p><p>Nie wybieram jednak kolekcji sortowanej automatycznie. Je&#347;li liczy si&#281; wy&#322;&#261;cznie szybkie sprawdzanie obecno&#347;ci elementu i kolejno&#347;&#263; nie ma znaczenia, <strong>HashSet<t></t></strong> zwykle b&#281;dzie prostszym rozwi&#261;zaniem. Je&#347;li dane s&#261; ju&#380; posortowane i wystarczy jednorazowe wyszukiwanie, mo&#380;na rozwa&#380;y&#263; tablic&#281; lub list&#281; z BinarySearch. Ka&#380;da z tych opcji rozwi&#261;zuje troch&#281; inny problem.</p><h2 id="typowe-bledy-podczas-pracy-z-ta-struktura">Typowe b&#322;&#281;dy podczas pracy z t&#261; struktur&#261;</h2><p>Pocz&#261;tkuj&#261;cy cz&#281;sto zak&#322;adaj&#261;, &#380;e ka&#380;de drzewo z dwoma dzie&#263;mi automatycznie umo&#380;liwia szybkie wyszukiwanie. To nieprawda. <strong>Regu&#322;a porz&#261;dku BST</strong> musi by&#263; zachowana dla ca&#322;ych poddrzew, nie tylko dla bezpo&#347;rednich dzieci.</p><p>Drugim b&#322;&#281;dem jest pomijanie warto&#347;ci null. Puste lewe lub prawe dziecko jest normalnym stanem, a kod powinien jasno go obs&#322;ugiwa&#263;. W C# nullable reference types pomagaj&#261; oznaczy&#263; ten fakt typem <strong>Node<t>?</t></strong>, dzi&#281;ki czemu kompilator mo&#380;e wychwyci&#263; cz&#281;&#347;&#263; pomy&#322;ek.</p><ul>
<li>Nie zak&#322;adaj, &#380;e dane wej&#347;ciowe b&#281;d&#261; losowe i dobrze zbalansuj&#261; struktur&#281;.</li>
<li>Ustal z g&oacute;ry, co zrobi&#263; z duplikatami.</li>
<li>Nie mieszaj zasad BST z zasadami kopca binarnego.</li>
<li>Przy du&#380;ych danych rozwa&#380; wersj&#281; iteracyjn&#261; albo gotow&#261; kolekcj&#281;.</li>
<li>Testuj osobno pust&#261; struktur&#281;, jeden element, usuwanie korzenia i warto&#347;ci powtarzaj&#261;ce si&#281;.</li>
</ul><p>W testach warto sprawdza&#263; nie tylko wynik wyszukiwania, lecz tak&#380;e w&#322;asno&#347;&#263; uporz&#261;dkowania. Najpro&#347;ciej wykona&#263; przej&#347;cie inorder i upewni&#263; si&#281;, &#380;e otrzymana lista jest rosn&#261;ca. Taki test &#322;apie b&#322;&#281;dy, kt&oacute;rych pojedyncze wywo&#322;anie metody <strong>Contains</strong> mog&#322;oby nie ujawni&#263;.</p><h2 id="jak-podejsc-do-tego-tematu-w-projekcie-net">Jak podej&#347;&#263; do tego tematu w projekcie .NET</h2><p>Do nauki algorytm&oacute;w w&#322;asna implementacja jest bardzo dobrym &#263;wiczeniem, bo pokazuje dzia&#322;anie referencji, rekurencji, rotacji i z&#322;o&#380;ono&#347;ci obliczeniowej. W kodzie biznesowym zacz&#261;&#322;bym jednak od pytania, czy naprawd&#281; potrzebuj&#281; r&#281;cznie zarz&#261;dza&#263; w&#281;z&#322;ami.</p><p>Je&#347;li potrzebujesz unikalnych, uporz&#261;dkowanych warto&#347;ci, wybierz <strong>SortedSet<t></t></strong>. Je&#347;li przechowujesz relacj&#281; klucz-warto&#347;&#263;, sprawd&#378; SortedDictionary. W&#322;asne BST ma sens wtedy, gdy tworzysz struktur&#281; edukacyjn&#261;, implementujesz specjalistyczny algorytm albo musisz obs&#322;u&#380;y&#263; regu&#322;y, kt&oacute;rych standardowa kolekcja nie oferuje.</p><p>Najwa&#380;niejsza lekcja jest prosta: dwa odwo&#322;ania w w&#281;&#378;le to dopiero pocz&#261;tek. O wydajno&#347;ci decyduj&#261; <strong>porz&#261;dek danych, wysoko&#347;&#263; struktury i spos&oacute;b obs&#322;ugi przypadk&oacute;w brzegowych</strong>. Kiedy te trzy elementy s&#261; &#347;wiadomie zaprojektowane, drzewa binarne przestaj&#261; by&#263; abstrakcyjnym tematem z algorytmiki i staj&#261; si&#281; praktycznym narz&#281;dziem w C#.</p>]]></content:encoded>
      <author>Radosław Krajewski</author>
      <category>C# i .NET</category>
      <media:thumbnail url="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/thumbnail/2b3c957d72eda24118bb33633a6b2659/drzewo-binarne-w-c-jak-dziala-i-kiedy-uzyc-bst.webp"/>
      <pubDate>Sun, 13 Sep 2026 14:25:00 +0200</pubDate>
    </item>
    <item>
      <title>Dependency injection w C# bez pułapek - cykle życia i testy</title>
      <link>https://kursdotnet.pl/dependency-injection-w-c-bez-pulapek-cykle-zycia-i-testy</link>
      <description>C# dependency injection w praktyce: konfiguracja DI, cykle życia usług i testowanie. Poznaj pułapki i uprość kod.</description>
      <content:encoded><![CDATA[<head></head>Klasa, która sama tworzy logger, klienta HTTP i repozytorium, szybko staje się trudna do testowania oraz zmiany. Temat <strong>c# dependency injection</strong> pomaga uporządkować ten problem: pokazuję, jak działa <a href="https://kursdotnet.pl/zasada-otwarte-zamkniete-w-c-i-net-praktyczne-ocp">wstrzykiwanie zależności</a> w C#, jak skonfigurować wbudowany kontener .NET, dobrać cykl życia usług i uniknąć pułapek, które wychodzą dopiero na produkcji.

<div class="short-summary">
<h2 id="najwazniejsze-zasady-di-w-c-w-jednym-miejscu">Najważniejsze zasady DI w C# w jednym miejscu</h2>
<ul>
<li>
<strong>Wstrzykuj zależności przez konstruktor</strong>, zamiast tworzyć je operatorem <code>new</code> wewnątrz klasy.</li>
<li>
<strong>Rejestruj usługi w Program.cs</strong> przy użyciu <code>AddTransient</code>, <code>AddScoped</code> lub <code>AddSingleton</code>.</li>
<li>
<strong>Scoped</strong> pasuje zwykle do usług powiązanych z jednym żądaniem HTTP, a <strong>Singleton</strong> wymaga bezpiecznej obsługi współbieżności.</li>
<li>
<strong>Interfejs ułatwia testowanie</strong>, ale nie każda klasa potrzebuje osobnego abstrakcyjnego interfejsu.</li>
<li>
<strong>Service locator i zbyt wiele zależności</strong> często oznaczają problem z projektem klasy, a nie brak kolejnej rejestracji.</li>
</ul>
</div>

<h2 id="na-czym-polega-wstrzykiwanie-zaleznosci-w-c">Na czym polega wstrzykiwanie zależności w C#</h2>

<p>Zależność to obiekt, którego inna klasa potrzebuje do wykonania swojej pracy. Serwis zamawiający płatność może korzystać z klienta API, repozytorium i loggera. W podejściu bez DI tworzy je sam, przez co zostaje mocno związany z konkretnymi implementacjami.</p>

<pre><code>public class OrderService
{
    private readonly PaymentClient _paymentClient;

    public OrderService()
    {
        _paymentClient = new PaymentClient();
    }
}</code></pre>

<p>Ten kod działa, ale test jednostkowy musi korzystać z prawdziwego klienta albo z trudnych obejść. Gdy przestawiamy się na <strong>odwrócenie zależności</strong>, klasa otrzymuje gotowy obiekt z zewnątrz i nie musi wiedzieć, jak go utworzyć.</p>

<pre><code>public interface IPaymentClient
{
    Task<bool> ChargeAsync(decimal amount, CancellationToken cancellationToken);
}

public class OrderService(IPaymentClient paymentClient)
{
    public Task<bool> PayAsync(
        decimal amount,
        CancellationToken cancellationToken)
    {
        return paymentClient.ChargeAsync(amount, cancellationToken);
    }
}</bool></bool></code></pre>

<p>Najczęściej używam tu <strong>constructor injection</strong>, czyli wstrzykiwania przez konstruktor. Zależności są wtedy widoczne od razu, obiekt nie może powstać w niepełnym stanie, a test otrzymuje prostą możliwość podstawienia atrap lub mocków.</p>

<p>Sam kontener DI nie jest architekturą aplikacji. To mechanizm, który buduje obiekty i dostarcza ich zależności. Dobra architektura zaczyna się wcześniej, od jasnego podziału odpowiedzialności i sensownych granic między modułami.</p>

<h2 id="jak-skonfigurowac-di-w-praktycznym-przykladzie">Jak skonfigurować DI w praktycznym przykładzie</h2>

<p>W aplikacji ASP.NET Core usługi rejestruje się zwykle w pliku <code>Program.cs</code>. Wbudowany kontener korzysta z interfejsu <code>IServiceCollection</code>, a później rozwiązuje pełny <strong>graf obiektów</strong>, czyli wszystkie zależności potrzebne do utworzenia żądanej klasy.</p>

<p>Załóżmy, że aplikacja pobiera dane kursów z repozytorium. Najpierw definiuję kontrakt i implementację.</p>

<pre><code>public interface ICourseRepository
{
    Task<course> GetAsync(
        int id,
        CancellationToken cancellationToken);
}

public sealed class SqlCourseRepository(
    AppDbContext dbContext) : ICourseRepository
{
    public Task<course> GetAsync(
        int id,
        CancellationToken cancellationToken)
    {
        return dbContext.Courses
            .SingleOrDefaultAsync(x =&gt; x.Id == id, cancellationToken);
    }
}</course></course></code></pre>

Serwis aplikacyjny zależy już tylko od interfejsu. Dzięki temu nie zna <a href="https://kursdotnet.pl/architektura-cebulowa-w-net-jak-poukladac-warstwy">Entity Framework Core</a> ani szczegółów bazy danych.

<pre><code>public sealed class CourseService(
    ICourseRepository repository,
    ILogger<courseservice> logger)
{
    public async Task<course> GetAsync(
        int id,
        CancellationToken cancellationToken)
    {
        logger.LogInformation("Pobieram kurs {CourseId}", id);

        return await repository.GetAsync(id, cancellationToken);
    }
}</course></courseservice></code></pre>

<p>Rejestracja może wyglądać tak:</p>

<pre><code>var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<appdbcontext>();
builder.Services.AddScoped<icourserepository sqlcourserepository="">();
builder.Services.AddScoped<courseservice>();

var app = builder.Build();

app.MapGet("/courses/{id:int}",
    async (
        int id,
        CourseService courseService,
        CancellationToken cancellationToken) =&gt;
    {
        var course = await courseService.GetAsync(id, cancellationToken);

        return course is null
            ? Results.NotFound()
            : Results.Ok(course);
    });

app.Run();</courseservice></icourserepository></appdbcontext></code></pre>

<p>W tym przykładzie framework tworzy <code>CourseService</code>, wykrywa jego dwa parametry konstruktora, a potem dostarcza repozytorium i logger. Jeśli którejś usługi zabraknie, błąd pojawi się przy uruchomieniu lub podczas rozwiązywania zależności, zamiast zostać ukryty w przypadkowym miejscu kodu.</p>

<h3 id="trzy-sposoby-rejestracji-uslug">Trzy sposoby rejestracji usług</h3>

<p>Najczęściej wystarcza rejestracja typu interfejs plus implementacja. Kontener obsługuje jednak także fabrykę oraz bezpośrednio utworzoną instancję.</p>

<table>
<tbody>
<tr>
<th>Rejestracja</th>
<th>Kiedy jej użyć</th>
</tr>
<tr>
<td><code>AddScoped<irepo sqlrepo="">()</irepo></code></td>
<td>Gdy implementacja ma być tworzona przez kontener na podstawie konstruktora.</td>
</tr>
<tr>
<td><code>AddSingleton<icache>(sp =&gt; ...)</icache></code></td>
<td>Gdy utworzenie obiektu wymaga konfiguracji albo zależności pobranej z kontenera.</td>
</tr>
<tr>
<td><code>AddSingleton<icache>(cache)</icache></code></td>
<td>Gdy masz już gotową instancję i świadomie chcesz udostępnić ją przez cały czas działania aplikacji.</td>
</tr>
</tbody>
</table>

<p>Fabryka jest użyteczna, gdy trzeba przekazać wartość z konfiguracji, ale nie powinna stać się miejscem ręcznego budowania całej aplikacji. Jeśli w lambdzie pojawia się kilkanaście instrukcji i wiele wywołań <code>GetRequiredService</code>, zwykle lepiej wydzielić osobną fabrykę albo zmienić projekt zależności.</p>

<h2 id="jak-dobrac-cykl-zycia-uslugi">Jak dobrać cykl życia usługi</h2>

<p>Najwięcej błędów w DI nie wynika z samej rejestracji, tylko z niewłaściwego czasu życia obiektu. W .NET masz trzy podstawowe opcje: <strong>Transient</strong>, <strong>Scoped</strong> i <strong>Singleton</strong>.</p>

<table>
<tbody>
<tr>
<th>Cykl życia</th>
<th>Co oznacza</th>
<th>Typowe zastosowanie</th>
</tr>
<tr>
<td><strong>Transient</strong></td>
<td>Nowa instancja powstaje przy każdym pobraniu usługi.</td>
<td>Lekkie, bezstanowe serwisy i formatery.</td>
</tr>
<tr>
<td><strong>Scoped</strong></td>
<td>Jedna instancja działa w obrębie konkretnego zakresu.</td>
<td>Repozytoria i kontekst bazy danych w aplikacji webowej.</td>
</tr>
<tr>
<td><strong>Singleton</strong></td>
<td>Jedna instancja żyje tak długo, jak proces aplikacji.</td>
<td>Bezstanowa konfiguracja, cache lub współdzielony klient, jeśli jest bezpieczny współbieżnie.</td>
</tr>
</tbody>
</table>

<h3 id="transient-sprawdza-sie-przy-prostych-uslugach">Transient sprawdza się przy prostych usługach</h3>

<p>Serwis zarejestrowany jako transient nie przechowuje stanu między wywołaniami. To dobry wybór dla walidatora, mappera albo kalkulatora, szczególnie gdy utworzenie obiektu jest tanie i nie ma powodu, aby współdzielić instancję.</p>

<pre><code>builder.Services.AddTransient<ipricecalculator pricecalculator="">();</ipricecalculator></code></pre>

<p>Nie oznacza to jednak, że transient jest zawsze bezpiecznym ustawieniem domyślnym. Jeśli usługa otwiera kosztowne zasoby albo ma stan, tworzenie jej przy każdym pobraniu może zwiększyć zużycie pamięci i obciążenie aplikacji.</p>

<h3 id="scoped-pasuje-do-zadania-http">Scoped pasuje do żądania HTTP</h3>

<p>W aplikacji webowej zakres scoped jest zwykle związany z jednym żądaniem. Dlatego <strong>DbContext i repozytoria</strong> często rejestruje się właśnie w ten sposób. Wszystkie komponenty obsługujące dane żądanie mogą wtedy pracować na tej samej instancji kontekstu.</p>

<p>Trzeba pamiętać o różnicy między aplikacją webową a zadaniem działającym w tle. Worker nie ma automatycznie zakresu żądania, więc przed użyciem usługi scoped powinien utworzyć własny zakres przez <code>IServiceScopeFactory</code>.</p>

<h3 id="singleton-wymaga-ostroznosci">Singleton wymaga ostrożności</h3>

<p>Singleton żyje przez cały proces, dlatego nie powinien zależeć bezpośrednio od usługi scoped. Taki błąd nazywa się <strong>captive dependency</strong>. Singleton zatrzymuje wtedy zależność o krótszym życiu na zbyt długo, co może prowadzić do nieaktualnych danych, problemów z wątkami albo wyjątku przy walidacji zakresów.</p>

<p>Singleton musi także poprawnie obsługiwać współbieżność. Kontener gwarantuje bezpieczne tworzenie i rozwiązywanie usług, ale nie sprawia, że własne pola i kolekcje singletonu stają się automatycznie bezpieczne dla wielu wątków.</p>

<h2 id="jak-obslugiwac-wiele-implementacji-i-konfiguracje">Jak obsługiwać wiele implementacji i konfigurację</h2>

<p>W większej aplikacji jedna abstrakcja może mieć kilka implementacji. Przykładowo system wysyła powiadomienia e-mailem i SMS-em. Można zarejestrować obie klasy pod tym samym interfejsem, a potem wstrzyknąć kolekcję.</p>

<pre><code>builder.Services.AddTransient<inotificationsender emailnotificationsender="">();
builder.Services.AddTransient<inotificationsender smsnotificationsender="">();

public sealed class NotificationService(
    IEnumerable<inotificationsender> senders)
{
    public async Task SendAsync(
        Notification notification,
        CancellationToken cancellationToken)
    {
        foreach (var sender in senders)
        {
            await sender.SendAsync(notification, cancellationToken);
        }
    }
}</inotificationsender></inotificationsender></inotificationsender></code></pre>

<p>To rozwiązanie pasuje do wzorca <strong>strategii</strong>, gdy wszystkie implementacje mają wykonać podobny etap pracy. Jeżeli trzeba wybrać dokładnie jedną implementację na podstawie kanału, można użyć własnej fabryki lub, w nowszych wersjach .NET, usług kluczowanych.</p>

<pre><code>builder.Services.AddKeyedTransient<ipaymentprovider stripepaymentprovider="">("stripe");

builder.Services.AddKeyedTransient<ipaymentprovider paypalpaymentprovider="">("paypal");

public sealed class CheckoutService(
    [FromKeyedServices("stripe")] IPaymentProvider provider)
{
    public Task PayAsync(
        Payment payment,
        CancellationToken cancellationToken)
    {
        return provider.PayAsync(payment, cancellationToken);
    }
}</ipaymentprovider></ipaymentprovider></code></pre>

<p>Usługi kluczowane są wygodne, gdy wybór implementacji jest stabilny i czytelny. Przy bardziej dynamicznych regułach wolę jawną fabrykę, ponieważ logika decyzji pozostaje wtedy w jednym miejscu, a konstruktor nie zaczyna pełnić roli konfiguracji biznesowej.</p>

<h3 id="konfiguracje-tez-wstrzykuj-przez-opcje">Konfigurację też wstrzykuj przez opcje</h3>

<p>Zamiast przekazywać do serwisu całe <code>IConfiguration</code>, lepiej związać konkretną sekcję z klasą opcji. Dzięki temu serwis zależy od <strong>typowanej konfiguracji</strong>, a nie od nazw tekstowych rozsianych po kodzie.</p>

<pre><code>public sealed class PaymentOptions
{
    public required string BaseUrl { get; init; }
    public int TimeoutSeconds { get; init; } = 10;
}

builder.Services
    .AddOptions<paymentoptions>()
    .Bind(builder.Configuration.GetSection("Payment"))
    .ValidateDataAnnotations()
    .ValidateOnStart();

public sealed class PaymentClient(
    IOptions<paymentoptions> options,
    HttpClient httpClient)
{
    private readonly PaymentOptions _options = options.Value;
}</paymentoptions></paymentoptions></code></pre>

<p>Walidacja przy starcie jest szczególnie cenna w środowisku produkcyjnym. Aplikacja kończy uruchamianie od razu z jasnym błędem, zamiast przyjąć pierwsze żądanie i dopiero wtedy odkryć brakujący adres endpointu.</p>

<h2 id="najczestsze-bledy-przy-uzywaniu-di">Najczęstsze błędy przy używaniu DI</h2>

<h3 id="wstrzykiwanie-iserviceprovider-do-kazdej-klasy">Wstrzykiwanie IServiceProvider do każdej klasy</h3>

<p>Kod poniżej wygląda elastycznie, ale ukrywa prawdziwe zależności:</p>

<pre><code>public class ReportService(IServiceProvider provider)
{
    public void Generate()
    {
        var exporter = provider.GetRequiredService<ireportexporter>();
        exporter.Export();
    }
}</ireportexporter></code></pre>

<p>To odmiana wzorca <strong>service locator</strong>. Klasa nie pokazuje w konstruktorze, czego naprawdę potrzebuje, a błąd pojawia się dopiero podczas wykonania konkretnej ścieżki. Zwykle lepiej wstrzyknąć <code>IReportExporter</code> bezpośrednio.</p>

<h3 id="zbyt-wiele-zaleznosci-w-jednym-konstruktorze">Zbyt wiele zależności w jednym konstruktorze</h3>

<p>Konstruktor z ośmioma lub dziesięcioma parametrami nie jest automatycznie błędem, ale często sygnalizuje, że klasa ma zbyt wiele odpowiedzialności. Sam spotykam się z tym najczęściej w kontrolerach, które zaczynają pełnić rolę warstwy aplikacyjnej, walidatora i koordynatora integracji jednocześnie.</p>

<p>Rozwiązaniem nie powinno być mechaniczne pogrupowanie parametrów w jeden obiekt typu <code>Dependencies</code>. To tylko chowa problem. Lepiej rozdzielić scenariusze biznesowe albo wydzielić mniejsze serwisy o jednej wyraźnej odpowiedzialności.</p>

<h3 id="rejestrowanie-wszystkiego-jako-singleton">Rejestrowanie wszystkiego jako singleton</h3>

<p>Singleton ogranicza liczbę alokacji, ale nie jest uniwersalną optymalizacją. Usługa przechowująca dane użytkownika, wynik zapytania albo zmienny kontekst żądania może wtedy przypadkowo udostępnić stan innej osobie.</p>

<p>Najpierw określam, czy obiekt ma stan, kto jest jego właścicielem i jak długo ten stan powinien istnieć. Dopiero później wybieram cykl życia. <strong>Wydajność nie powinna zastępować poprawnego modelu stanu.</strong></p>

<p class="read-more"><strong>Przeczytaj również: <a href="https://kursdotnet.pl/yagni-w-net-kiedy-upraszcza-architekture-a-kiedy-szkodzi">YAGNI w .NET - kiedy upraszcza architekturę, a kiedy szkodzi?</a></strong></p><h3 id="brak-testu-grafu-zaleznosci">Brak testu grafu zależności</h3>

<p>Warto uruchamiać walidację zakresów w środowisku deweloperskim i testowym:</p>

<pre><code>var provider = services.BuildServiceProvider(
    new ServiceProviderOptions
    {
        ValidateScopes = true,
        ValidateOnBuild = true
    });</code></pre>

<p>Opcja <code>ValidateOnBuild</code> pomaga wykryć brakujące rejestracje, a <code>ValidateScopes</code> ujawnia część problemów z mieszaniem singletonów i scoped. Nie zastępuje testów integracyjnych, ale skraca drogę od pomyłki w konfiguracji do czytelnego komunikatu.</p>

<h2 id="jak-wdrozyc-di-bez-nadmiarowej-abstrakcji">Jak wdrożyć DI bez nadmiarowej abstrakcji</h2>

<p>Nie każda klasa musi mieć interfejs. Dla prostego serwisu używanego w jednym miejscu bezstanowa klasa konkretna może być czytelniejsza niż para <code>IFoo</code> i <code>Foo</code>. Interfejs daje największą wartość wtedy, gdy istnieje realna granica, kilka implementacji albo potrzeba izolacji testu.</p>

<p>W praktyce stosuję prosty proces:</p>

<ol>
<li>
<strong>Wypisuję odpowiedzialność klasy</strong> i sprawdzam, czy sama tworzy obiekty, których nie powinna znać.</li>
<li>
<strong>Wydzielam kontrakt</strong> tylko dla zależności, którą chcę wymieniać, testować lub ukryć za granicą modułu.</li>
<li>
<strong>Rejestruję implementację w kompozycji aplikacji</strong>, najczęściej w <code>Program.cs</code> albo w metodzie rozszerzającej.</li>
<li>
<strong>Dobieram cykl życia</strong> na podstawie stanu i zasobów, a nie przyzwyczajenia.</li>
<li>
<strong>Dodaję test jednostkowy</strong>, w którym zależność zastępuję prostą atrapą lub mockiem.</li>
</ol>

<p>Rejestracje związane z jednym modułem dobrze zamknąć w metodzie rozszerzającej:</p>

<pre><code>public static class CourseModule
{
    public static IServiceCollection AddCourseModule(
        this IServiceCollection services)
    {
        services.AddScoped<icourserepository sqlcourserepository="">();
        services.AddScoped<courseservice>();

        return services;
    }
}</courseservice></icourserepository></code></pre>

<p>Wtedy <code>Program.cs</code> zachowuje rolę miejsca składania aplikacji, a szczegóły modułu nie rozlewają się po całym pliku. Nie przesadzałbym jednak z liczbą takich metod. Dla małego projektu kilka jawnych rejestracji jest często bardziej zrozumiałe niż rozbudowana automatyczna rejestracja przez skanowanie assembly.</p>

<h2 id="co-sprawdzic-przed-wdrozeniem-aplikacji">Co sprawdzić przed wdrożeniem aplikacji</h2>

<ul>
<li>Czy każda klasa ma tylko te zależności, których naprawdę używa?</li>
<li>Czy usługi scoped nie są pobierane bezpośrednio przez singletony?</li>
<li>Czy singletony nie przechowują niezabezpieczonego współdzielonego stanu?</li>
<li>Czy zadania w tle tworzą zakres dla usług scoped?</li>
<li>Czy konfiguracja jest typowana i walidowana przy starcie?</li>
<li>Czy testy sprawdzają rejestrację kluczowych komponentów oraz ich rzeczywisty graf zależności?</li>
</ul>

<p>Jeżeli odpowiedzi są pozytywne, wbudowany kontener .NET zwykle w zupełności wystarcza. Zewnętrzny kontener może dodać dekoratory, bardziej zaawansowane konwencje lub specjalne scenariusze, ale zwiększa też liczbę pojęć i miejsc, w których można popełnić błąd.</p>

<h2 id="di-ktore-pomaga-zamiast-komplikowac-kod">DI, które pomaga zamiast komplikować kod</h2>

Najlepsze wdrożenie <a href="https://kursdotnet.pl/projektowanie-obiektowe-w-c-bez-nadmiarowej-komplikacji">dependency injection</a> nie polega na tym, aby każdą klasę obudować interfejsem i zarejestrować jako singleton. Chodzi o <strong>jawne zależności, kontrolowany cykl życia i łatwe testowanie</strong>. Gdy klasa mówi w konstruktorze, czego potrzebuje, a kompozycja aplikacji decyduje, jakie implementacje otrzyma, kod staje się prostszy do rozwijania.

<p>Na początek wystarczy constructor injection, trzy świadomie dobrane cykle życia i rejestracje w <code>Program.cs</code>. Dopiero gdy projekt rzeczywiście tego wymaga, warto dodawać fabryki, usługi kluczowane czy zewnętrzny kontener. Taka kolejność pozwala korzystać z zalet DI bez zamieniania architektury w dodatkową warstwę ceremonii.</p>]]></content:encoded>
      <author>Radosław Krajewski</author>
      <category>Architektura i wzorce</category>
      <media:thumbnail url="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/thumbnail/e71d0dea9169590ce7d65314b717ac98/dependency-injection-w-c-bez-pulapek-cykle-zycia-i-testy.webp"/>
      <pubDate>Sun, 13 Sep 2026 14:14:00 +0200</pubDate>
    </item>
    <item>
      <title>MVVM w .NET - jak oddzielić UI od logiki aplikacji?</title>
      <link>https://kursdotnet.pl/mvvm-w-net-jak-oddzielic-ui-od-logiki-aplikacji</link>
      <description>MVVM w .NET porządkuje logikę aplikacji. Poznaj Model, ViewModel, bindingi, testy i sprawdź, kiedy ten wzorzec ma sens.</description>
      <content:encoded><![CDATA[<?xml encoding="utf-8" ?><p>Gdy ekran aplikacji zaczyna obs&#322;ugiwa&#263; &#322;adowanie danych, walidacj&#281;, komunikaty b&#322;&#281;d&oacute;w i kilka akcji u&#380;ytkownika, kod-behind szybko przestaje by&#263; wygodnym miejscem na ca&#322;&#261; logik&#281;. Architektura MVVM porz&#261;dkuje ten problem, oddzielaj&#261;c interfejs od stanu i operacji aplikacji. Poka&#380;&#281;, jak dzia&#322;aj&#261; Model, View i ViewModel, jak po&#322;&#261;czy&#263; je w aplikacji .NET oraz kiedy ten wzorzec rzeczywi&#347;cie pomaga.</p><div class="short-summary">
<h2 id="mvvm-oddziela-wyglad-ekranu-od-jego-stanu-i-zachowania">MVVM oddziela wygl&#261;d ekranu od jego stanu i zachowania</h2>
<ul>
<li>
<strong>Model</strong> reprezentuje dane i regu&#322;y domenowe, ale nie powinien zna&#263; interfejsu u&#380;ytkownika.</li>
<li>
<strong>View</strong> wy&#347;wietla dane i przekazuje akcje u&#380;ytkownika przez bindingi oraz komendy.</li>
<li>
<strong>ViewModel</strong> udost&#281;pnia w&#322;a&#347;ciwo&#347;ci, statusy i operacje, kt&oacute;re mo&#380;na testowa&#263; bez uruchamiania UI.</li>
<li>
<strong>Data binding</strong> synchronizuje stan ViewModelu z kontrolkami w XAML.</li>
<li>Najwi&#281;ksze korzy&#347;ci pojawiaj&#261; si&#281; przy aplikacjach <strong>WPF, WinUI i .NET MAUI</strong>, kt&oacute;re maj&#261; wi&#281;cej ni&#380; kilka prostych ekran&oacute;w.</li>
</ul>
</div><p><img src="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/post_image/495efb7068e5be4b218862596c3c7385/mvvm-architecture-model-view-viewmodel-data-flow-diagram-net-maui.webp" class="image article-image" loading="lazy" alt="Diagram przedstawiaj&#261;cy architektur&#281; MVVM: View, ViewModel i Model z ich komponentami, pokazuj&#261;cy przep&#322;yw danych i logik&#281;."></p><h2 id="na-czym-polega-podzial-model-view-viewmodel">Na czym polega podzia&#322; Model View ViewModel</h2><p>MVVM, czyli Model-View-ViewModel, to wzorzec architektoniczny dla aplikacji z interfejsem u&#380;ytkownika. Jego g&#322;&oacute;wna idea jest prosta: <strong>ekran nie powinien zawiera&#263; ca&#322;ej logiki biznesowej</strong>. Zamiast tego View prezentuje stan, ViewModel przygotowuje dane dla ekranu, a Model zajmuje si&#281; domen&#261; i dost&#281;pem do danych.</p><p>W praktyce u&#380;ytkownik naciska przycisk w View, ale sama akcja jest obs&#322;ugiwana przez komend&#281; ViewModelu. ViewModel wywo&#322;uje serwis, aktualizuje sw&oacute;j stan, a binding przekazuje zmian&#281; do interfejsu. Dzi&#281;ki temu kod odpowiedzialny za dzia&#322;anie aplikacji nie jest przyklejony do konkretnego przycisku, strony ani frameworka UI.</p><table>
<thead>
<tr>
<th>Element</th>
<th>Odpowiedzialno&#347;&#263;</th>
<th>Czego powinien unika&#263;</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Model</strong></td>
<td>Dane, regu&#322;y domenowe, encje i operacje biznesowe</td>
<td>Odwo&#322;a&#324; do kontrolek, stron i mechanizm&oacute;w UI</td>
</tr>
<tr>
<td><strong>View</strong></td>
<td>Uk&#322;ad, wygl&#261;d, animacje i prezentacja stanu</td>
<td>Bezpo&#347;redniego wywo&#322;ywania bazy danych oraz logiki biznesowej</td>
</tr>
<tr>
<td><strong>ViewModel</strong></td>
<td>Stan ekranu, komendy, walidacja prezentacyjna i komunikacja z serwisami</td>
<td>Zale&#380;no&#347;ci od konkretnych kontrolek i bezpo&#347;redniej manipulacji widokiem</td>
</tr>
</tbody>
</table><p>Najwa&#380;niejsza granica przebiega mi&#281;dzy View i ViewModelem. <strong>ViewModel nie powinien wiedzie&#263;, czy dzia&#322;a z XAML-em, oknem desktopowym czy ekranem mobilnym</strong>. Je&#380;eli w jego kodzie pojawia si&#281; `Button`, `ContentPage` albo odwo&#322;anie do konkretnej kontrolki, zwykle oznacza to, &#380;e cz&#281;&#347;&#263; odpowiedzialno&#347;ci trafi&#322;a w z&#322;e miejsce.</p><h3 id="model-nie-zawsze-oznacza-encje-z-bazy">Model nie zawsze oznacza encj&#281; z bazy</h3><p>Model mo&#380;e by&#263; encj&#261; domenow&#261;, rekordem zwr&oacute;conym przez API albo obiektem opisuj&#261;cym warto&#347;&#263;, na przyk&#322;ad adres lub kwot&#281;. Nie musi jednak odwzorowywa&#263; jeden do jednego tabeli w bazie danych. Cz&#281;sto lepiej, gdy ViewModel korzysta z osobnych modeli prezentacyjnych, zamiast wystawia&#263; bezpo&#347;rednio obiekty infrastruktury.</p><h3 id="viewmodel-to-stan-konkretnego-ekranu">ViewModel to stan konkretnego ekranu</h3><p>Dobry ViewModel odpowiada na pytanie, <strong>co ekran ma pokaza&#263; i co u&#380;ytkownik mo&#380;e teraz zrobi&#263;</strong>. Mo&#380;e przechowywa&#263; list&#281; produkt&oacute;w, informacj&#281; o &#322;adowaniu, komunikat b&#322;&#281;du oraz komend&#281; od&#347;wie&#380;enia. Nie powinien natomiast stawa&#263; si&#281; magazynem ca&#322;ej aplikacji ani zast&#281;powa&#263; warstwy domenowej.</p><h2 id="jak-przeplyw-danych-dziala-w-aplikacji-net">Jak przep&#322;yw danych dzia&#322;a w aplikacji .NET</h2><p>W aplikacjach WPF, WinUI i .NET MAUI po&#322;&#261;czenie mi&#281;dzy View a ViewModelem zapewnia data binding. Kontrolka obserwuje w&#322;a&#347;ciwo&#347;&#263; ViewModelu, a zmiana warto&#347;ci powoduje aktualizacj&#281; interfejsu. W drug&#261; stron&#281; dzia&#322;a to podobnie, na przyk&#322;ad wpisanie tekstu do pola mo&#380;e zmieni&#263; w&#322;a&#347;ciwo&#347;&#263; ViewModelu.</p><p>&#379;eby interfejs wiedzia&#322; o zmianie, ViewModel musi zg&#322;osi&#263; powiadomienie. Najcz&#281;&#347;ciej s&#322;u&#380;y do tego <code><a href="https://kursdotnet.pl/mvvm-pattern-w-net-kiedy-naprawde-pomaga">INotifyPropertyChanged</a></code> albo gotowe klasy z pakietu CommunityToolkit.Mvvm. W przypadku kolekcji u&#380;ywa si&#281; zwykle <code>ObservableCollection<t></t></code>, kt&oacute;ra informuje widok o dodaniu i usuni&#281;ciu element&oacute;w.</p><pre><code>public partial class ProductsViewModel : ObservableObject
{
    [ObservableProperty]
    private bool isBusy;

    [ObservableProperty]
    private string? errorMessage;

    public ObservableCollection<product> Products { get; } = new();

    private readonly IProductService service;

    public ProductsViewModel(IProductService service)
    {
        this.service = service;
    }

    [RelayCommand]
    private async Task LoadAsync()
    {
        if (IsBusy)
            return;

        try
        {
            IsBusy = true;
            ErrorMessage = null;

            var products = await service.GetAsync();

            Products.Clear();

            foreach (var product in products)
                Products.Add(product);
        }
        catch (Exception)
        {
            ErrorMessage = "Nie uda&#322;o si&#281; pobra&#263; produkt&oacute;w.";
        }
        finally
        {
            IsBusy = false;
        }
    }
}</product></code></pre><p>Atrybuty z przyk&#322;adu generuj&#261; w&#322;a&#347;ciwo&#347;ci i komend&#281; podczas kompilacji. To ogranicza ilo&#347;&#263; powtarzalnego kodu, ale nie zmienia zasad wzorca. Nadal trzeba pilnowa&#263;, aby <strong>operacje I/O by&#322;y asynchroniczne</strong>, a interfejs nie by&#322; blokowany podczas komunikacji z API lub baz&#261; danych.</p><p>W widoku komenda i w&#322;a&#347;ciwo&#347;ci mog&#261; wygl&#261;da&#263; tak:</p><pre><code><verticalstacklayout padding="24">
    <activityindicator isrunning="{Binding IsBusy}" isvisible="{Binding IsBusy}"></activityindicator>

    <button text="Od&#347;wie&#380;" command="{Binding LoadCommand}"></button>

    <label text="{Binding ErrorMessage}" textcolor="Red"></label>

    <collectionview itemssource="{Binding Products}">
        <collectionview.itemtemplate>
            <datatemplate>
                <horizontalstacklayout spacing="12">
                    <label text="{Binding Name}"></label>
                    <label text="{Binding Price}"></label>
                </horizontalstacklayout>
            </datatemplate>
        </collectionview.itemtemplate>
    </collectionview>
</verticalstacklayout></code></pre><p>Istotny jest tak&#380;e <code>BindingContext</code>, czyli obiekt, z kt&oacute;rego widok pobiera warto&#347;ci. Je&#380;eli binding nie dzia&#322;a, pierwsze miejsce do sprawdzenia to w&#322;a&#347;nie kontekst widoku, nazwa w&#322;a&#347;ciwo&#347;ci oraz informacja o zmianach. W praktyce wiele problem&oacute;w z MVVM nie wynika z samego wzorca, tylko z liter&oacute;wki w bindingu albo z ustawienia kontekstu na niew&#322;a&#347;ciwym obiekcie.</p><h2 id="jak-zbudowac-ekran-w-stylu-mvvm">Jak zbudowa&#263; ekran w stylu MVVM</h2><p>Najbezpieczniej zaczyna&#263; od stanu ekranu, a nie od kontrolek. Najpierw zapisuj&#281;, jakie dane maj&#261; si&#281; pojawi&#263;, jakie operacje mo&#380;e wykona&#263; u&#380;ytkownik i jakie sytuacje trzeba pokaza&#263;. Dopiero p&oacute;&#378;niej tworz&#281; XAML, kt&oacute;ry ten stan prezentuje.</p><ol>
<li>
<strong>Zdefiniuj model</strong>, na przyk&#322;ad `Product`, `Order` albo `Customer`.</li>
<li>
<strong>Wydziel serwis</strong> odpowiedzialny za API, baz&#281; danych lub pliki.</li>
<li>
<strong>Utw&oacute;rz ViewModel</strong> z w&#322;a&#347;ciwo&#347;ciami, komendami oraz stanami &#322;adowania i b&#322;&#281;du.</li>
<li>
<strong>Pod&#322;&#261;cz zale&#380;no&#347;ci przez konstruktor</strong>, najlepiej korzystaj&#261;c z wbudowanego kontenera DI.</li>
<li>
<strong>Zbuduj View</strong> wy&#322;&#261;cznie wok&oacute;&#322; binding&oacute;w i komend.</li>
<li>
<strong>Dodaj testy ViewModelu</strong>, zanim ekran rozro&#347;nie si&#281; o kolejne scenariusze.</li>
</ol><p>Za&#322;&oacute;&#380;my, &#380;e ekran ma wy&#347;wietli&#263; list&#281; zam&oacute;wie&#324;. Model opisuje pojedyncze zam&oacute;wienie, serwis pobiera kolekcj&#281;, a ViewModel dodaje informacje potrzebne tylko dla UI, takie jak `IsRefreshing`, `IsEmpty` czy `ErrorMessage`. <strong>To w&#322;a&#347;nie ViewModel t&#322;umaczy dane domenowe na j&#281;zyk ekranu</strong>.</p><p>Nie polecam umieszczania wywo&#322;ania HTTP bezpo&#347;rednio w metodzie komendy, je&#347;li aplikacja ma wi&#281;cej ni&#380; jeden ekran. Lepiej wstrzykn&#261;&#263; interfejs serwisu, poniewa&#380; wtedy ViewModel mo&#380;na testowa&#263; z atrap&#261; danych, bez prawdziwego API i bez zale&#380;no&#347;ci od sieci.</p><pre><code>public interface IOrderService
{
    Task<ireadonlylist getordersasync cancellationtoken public sealed record order int id string customername decimal total></ireadonlylist></code></pre><p>W .NET MAUI rejestracja zale&#380;no&#347;ci mo&#380;e znajdowa&#263; si&#281; w konfiguracji aplikacji. Dzi&#281;ki temu ViewModel nie tworzy sam serwisu przez <code>new</code> i nie musi zna&#263; sposobu jego konfiguracji. To drobna decyzja, kt&oacute;ra mocno u&#322;atwia p&oacute;&#378;niejsze testowanie oraz wymian&#281; implementacji.</p><h2 id="kiedy-mvvm-ma-sens-a-kiedy-jest-przesada">Kiedy MVVM ma sens, a kiedy jest przesad&#261;</h2><p>MVVM szczeg&oacute;lnie dobrze pasuje do aplikacji, w kt&oacute;rych ekran ma w&#322;asny stan, reaguje na wiele zdarze&#324; i pobiera dane z kilku &#378;r&oacute;de&#322;. Dotyczy to paneli administracyjnych, aplikacji biznesowych, aplikacji mobilnych oraz rozbudowanych program&oacute;w desktopowych.</p><table>
<thead>
<tr>
<th>Sytuacja</th>
<th>Rozs&#261;dne podej&#347;cie</th>
<th>Dlaczego</th>
</tr>
</thead>
<tbody>
<tr>
<td>Jedna prosta strona z dwoma kontrolkami</td>
<td>Prosty code-behind mo&#380;e wystarczy&#263;</td>
<td>Pe&#322;na warstwa ViewModelu mo&#380;e zwi&#281;kszy&#263; liczb&#281; plik&oacute;w bez realnej korzy&#347;ci</td>
</tr>
<tr>
<td>Formularz z walidacj&#261; i zapisem</td>
<td>ViewModel z komendami i stanem walidacji</td>
<td>Logik&#281; mo&#380;na testowa&#263; bez uruchamiania interfejsu</td>
</tr>
<tr>
<td>Lista danych &#322;adowana z API</td>
<td>ViewModel plus serwis aplikacyjny</td>
<td>&#321;atwiej obs&#322;u&#380;y&#263; &#322;adowanie, b&#322;&#281;dy, ponowienie i pust&#261; list&#281;</td>
</tr>
<tr>
<td>Du&#380;a aplikacja mobilna lub desktopowa</td>
<td>MVVM z DI i wyspecjalizowanymi ViewModelami</td>
<td>Zmniejsza zale&#380;no&#347;ci mi&#281;dzy ekranami i u&#322;atwia rozw&oacute;j zespo&#322;owy</td>
</tr>
</tbody>
</table><p>Nie ka&#380;dy fragment kodu musi by&#263; na si&#322;&#281; &bdquo;czystym MVVM&rdquo;. Animacja, fokus kontrolki czy nietypowe zachowanie wizualne mog&#261; pozosta&#263; w code-behind. Microsoft r&oacute;wnie&#380; wskazuje, &#380;e View mo&#380;e zawiera&#263; niewielk&#261; ilo&#347;&#263; logiki czysto wizualnej. <strong>Problemem nie jest obecno&#347;&#263; code-behind, tylko umieszczanie w nim logiki biznesowej</strong>.</p><p>Trzeba te&#380; odr&oacute;&#380;ni&#263; MVVM od MVC. MVC cz&#281;sto organizuje aplikacj&#281; wok&oacute;&#322; &#380;&#261;dania i odpowiedzi HTTP, natomiast MVVM koncentruje si&#281; na synchronizacji stanu interfejsu z ViewModelem. Te wzorce mog&#261; wsp&oacute;&#322;istnie&#263;, ale rozwi&#261;zuj&#261; r&oacute;&#380;ne problemy.</p><h2 id="najczestsze-bledy-w-implementacji">Najcz&#281;stsze b&#322;&#281;dy w implementacji</h2><h3 id="jeden-viewmodel-dla-calej-aplikacji">Jeden ViewModel dla ca&#322;ej aplikacji</h3><p>Na pocz&#261;tku kusz&#261;ce jest stworzenie klasy `MainViewModel`, kt&oacute;ra obs&#322;uguje wszystkie ekrany. Po kilku tygodniach trafiaj&#261; do niej u&#380;ytkownicy, zam&oacute;wienia, ustawienia i nawigacja. Efekt to klasa trudna do testowania i jeszcze trudniejsza do zmiany. Lepiej, aby <strong>ka&#380;dy wi&#281;kszy ekran mia&#322; w&#322;asny ViewModel</strong>, a wsp&oacute;lne operacje trafia&#322;y do serwis&oacute;w.</p><h3 id="logika-biznesowa-w-view">Logika biznesowa w View</h3><p>Warunek zapisany w code-behind mo&#380;e wygl&#261;da&#263; niewinnie, ale szybko zaczyna by&#263; kopiowany w kilku miejscach. Je&#380;eli decyzja wp&#322;ywa na wynik biznesowy, przenie&#347; j&#261; do Modelu lub serwisu. View powinien przede wszystkim opisywa&#263;, jak co&#347; wygl&#261;da i na co jest zbindowane.</p><h3 id="viewmodel-sterujacy-kontrolkami">ViewModel steruj&#261;cy kontrolkami</h3><p>Odwo&#322;ania do <code>Entry</code>, <code>Label</code> czy <code>Navigation</code> wi&#261;&#380;&#261; ViewModel z konkretnym frameworkiem. Zamiast tego wystaw w&#322;a&#347;ciwo&#347;&#263; `IsBusy`, komend&#281; `SaveCommand` albo komunikat `ErrorMessage`. <strong>Stan powinien przep&#322;ywa&#263; do widoku</strong>, a nie by&#263; r&#281;cznie ustawiany na kontrolkach z poziomu ViewModelu.</p><h3 id="brak-stanow-posrednich">Brak stan&oacute;w po&#347;rednich</h3><p>Ekran, kt&oacute;ry zna tylko stan &bdquo;dane s&#261;&rdquo; albo &bdquo;danych nie ma&rdquo;, b&#281;dzie &#378;le zachowywa&#322; si&#281; podczas wolnego po&#322;&#261;czenia. Zwykle potrzebujesz co najmniej stan&oacute;w `Loading`, `Loaded`, `Empty` i `Error`. Nie zawsze musz&#261; by&#263; osobnym enumem, ale u&#380;ytkownik powinien dosta&#263; jasn&#261; informacj&#281;, co aktualnie dzieje si&#281; z aplikacj&#261;.</p><h3 id="synchroniczne-operacje-na-watku-ui">Synchroniczne operacje na w&#261;tku UI</h3><p>Wywo&#322;anie sieci, odczyt du&#380;ego pliku lub ci&#281;&#380;kie zapytanie wykonane synchronicznie zamrozi interfejs. W ViewModelach u&#380;ywaj `Task`, `await` i anulowania operacji tam, gdzie ma to sens. Sama obecno&#347;&#263; metody oznaczonej jako `async` nie wystarczy, je&#347;li w &#347;rodku nadal wykonujesz kosztown&#261; prac&#281; blokuj&#261;c&#261;.</p><p class="read-more"><strong>Przeczytaj r&oacute;wnie&#380;: <a href="https://kursdotnet.pl/petla-do-while-w-c-skladnia-przyklady-i-pulapki">P&#281;tla do-while w C# - sk&#322;adnia, przyk&#322;ady i pu&#322;apki</a></strong></p><h3 id="nadmierna-liczba-abstrakcji">Nadmierna liczba abstrakcji</h3><p>MVVM nie wymaga tworzenia interfejsu dla ka&#380;dej w&#322;a&#347;ciwo&#347;ci ani osobnej fabryki dla ka&#380;dej komendy. Abstrakcje powinny rozwi&#261;zywa&#263; konkretny problem, na przyk&#322;ad u&#322;atwia&#263; testowanie serwisu albo wymian&#281; &#378;r&oacute;d&#322;a danych. <strong>Prosty ViewModel jest lepszy ni&#380; architektura pe&#322;na pustych warstw</strong>.</p><h2 id="jak-testowac-viewmodel-bez-uruchamiania-interfejsu">Jak testowa&#263; ViewModel bez uruchamiania interfejsu</h2><p>Jedn&#261; z najwi&#281;kszych zalet tego wzorca jest mo&#380;liwo&#347;&#263; testowania zachowania ekranu jako zwyk&#322;ego kodu C#. Test nie musi otwiera&#263; aplikacji, klika&#263; przycisku ani czeka&#263; na renderowanie XAML. Wystarczy poda&#263; ViewModelowi atrap&#281; serwisu, wywo&#322;a&#263; komend&#281; i sprawdzi&#263; wynik.</p><p>Przyk&#322;adowy test powinien odpowiedzie&#263; na konkretne pytania. Czy po za&#322;adowaniu danych kolekcja zawiera oczekiwane elementy? Czy podczas pobierania `IsBusy` ma warto&#347;&#263; `true`? Czy b&#322;&#261;d z serwisu zmienia komunikat dla u&#380;ytkownika? Czy ponowne uruchomienie komendy nie rozpoczyna kilku identycznych operacji?</p><pre><code>[Fact]
public async Task LoadCommand_adds_products_to_collection()
{
    var service = new FakeProductService(
        new Product("Klawiatura", 299m));

    var viewModel = new ProductsViewModel(service);

    await viewModel.LoadCommand.ExecuteAsync(null);

    Assert.Single(viewModel.Products);
    Assert.Equal("Klawiatura", viewModel.Products[0].Name);
    Assert.False(viewModel.IsBusy);
}</code></pre><p>Nie trzeba testowa&#263; frameworka binding&oacute;w w ka&#380;dym te&#347;cie ViewModelu. Wystarczy zweryfikowa&#263;, czy w&#322;a&#347;ciwo&#347;ci i komendy zmieniaj&#261; si&#281; zgodnie z oczekiwaniami. Bindingi oraz konfiguracj&#281; widoku mo&#380;na sprawdza&#263; osobno, na przyk&#322;ad podczas test&oacute;w integracyjnych lub r&#281;cznej weryfikacji konkretnego ekranu.</p><p>Dobrym sygna&#322;em jako&#347;ci jest sytuacja, w kt&oacute;rej ViewModel mo&#380;na utworzy&#263; w te&#347;cie, przekazuj&#261;c mu tylko kilka zale&#380;no&#347;ci. Je&#347;li potrzebuje ca&#322;ej aplikacji, kontenera DI i dost&#281;pu do kontrolek, prawdopodobnie ma zbyt szeroki zakres odpowiedzialno&#347;ci.</p><h2 id="jak-rozpoznac-dobra-granice-mvvm-w-projekcie">Jak rozpozna&#263; dobr&#261; granic&#281; MVVM w projekcie</h2><p>Najprostszy test polega na wyobra&#380;eniu sobie wymiany interfejsu. Je&#347;li ten sam stan i regu&#322;y mo&#380;na wykorzysta&#263; w innym widoku bez przepisywania logiki, granica mi&#281;dzy warstwami jest prawdopodobnie sensowna. Je&#347;li zmiana koloru przycisku wymaga edycji serwisu pobieraj&#261;cego dane, zale&#380;no&#347;ci s&#261; ju&#380; zbyt ciasne.</p><ul>
<li>View opisuje wygl&#261;d i zachowania czysto wizualne.</li>
<li>ViewModel opisuje stan ekranu oraz akcje dost&#281;pne dla u&#380;ytkownika.</li>
<li>Serwisy obs&#322;uguj&#261; komunikacj&#281; z API, baz&#261; danych i plikami.</li>
<li>Model nie zna kontrolek ani cyklu &#380;ycia widoku.</li>
<li>Ka&#380;dy ekran ma jasno okre&#347;lony zakres odpowiedzialno&#347;ci.</li>
<li>Operacje asynchroniczne nie blokuj&#261; interfejsu.</li>
<li>Najwa&#380;niejsze scenariusze mo&#380;na sprawdzi&#263; w testach jednostkowych.</li>
</ul><p>W mojej ocenie MVVM dzia&#322;a najlepiej wtedy, gdy traktuje si&#281; go jako <strong>narz&#281;dzie do kontroli zale&#380;no&#347;ci</strong>, a nie obowi&#261;zkowy zestaw klas. Je&#347;li ekran jest ma&#322;y, prosty code-behind mo&#380;e by&#263; rozs&#261;dny. Gdy pojawiaj&#261; si&#281; dane z API, walidacja, kilka stan&oacute;w i rozbudowane akcje, dobrze zaprojektowany ViewModel szybko zaczyna si&#281; zwraca&#263;.</p><p>Najwa&#380;niejsze jest zachowanie zdrowego podzia&#322;u. View ma prezentowa&#263;, Model ma reprezentowa&#263; domen&#281;, a ViewModel ma po&#347;redniczy&#263; mi&#281;dzy nimi w spos&oacute;b zrozumia&#322;y i testowalny. Taka struktura nie usuwa ca&#322;ej z&#322;o&#380;ono&#347;ci aplikacji, ale sprawia, &#380;e wiadomo, gdzie szuka&#263; problemu i gdzie wprowadzi&#263; kolejn&#261; zmian&#281;.</p>]]></content:encoded>
      <author>Bruno Krawczyk</author>
      <category>Architektura i wzorce</category>
      <media:thumbnail url="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/thumbnail/8e5eb3f5afb505ab811add4611b15d5f/mvvm-w-net-jak-oddzielic-ui-od-logiki-aplikacji.webp"/>
      <pubDate>Sat, 12 Sep 2026 17:16:00 +0200</pubDate>
    </item>
    <item>
      <title>C# collections - jak dobrać kolekcję do operacji?</title>
      <link>https://kursdotnet.pl/c-collections-jak-dobrac-kolekcje-do-operacji</link>
      <description>C# collections w praktyce: poznaj List, Dictionary, HashSet, Queue i Stack oraz wybierz kolekcję do operacji. Sprawdź najważniejsze zasady.</description>
      <content:encoded><![CDATA[<?xml encoding="utf-8" ?><p>W &#347;wiecie .NET okre&#347;lenie c# collections obejmuje wszystkie sposoby przechowywania i obs&#322;ugi grup obiekt&oacute;w. Najwi&#281;kszy problem zwykle nie polega na znajomo&#347;ci nazw klas, lecz na dobraniu kolekcji do sposobu pracy z danymi: indeksu, klucza, kolejno&#347;ci, unikalno&#347;ci albo wsp&oacute;&#322;bie&#380;no&#347;ci. Poka&#380;&#281; najwa&#380;niejsze typy, ich ograniczenia, z&#322;o&#380;ono&#347;&#263; operacji i praktyczne regu&#322;y wyboru.</p><div class="short-summary">
  <h2 id="dobra-kolekcja-wynika-z-operacji-ktore-wykonujesz-najczesciej">Dobra kolekcja wynika z operacji, kt&oacute;re wykonujesz najcz&#281;&#347;ciej</h2>
  <ul>
    <li>
<strong>List<t></t></strong> sprawdza si&#281; jako domy&#347;lna, dynamiczna lista z dost&#281;pem przez indeks.</li>
    <li>
<strong>Dictionary<tkey tvalue></tkey></strong> wybierz wtedy, gdy dane wyszukujesz po kluczu.</li>
    <li>
<strong>HashSet<t></t></strong> s&#322;u&#380;y do unikalnych element&oacute;w i szybkiego sprawdzania obecno&#347;ci.</li>
    <li>
<strong>Queue<t></t></strong> obs&#322;uguje kolejk&#281; FIFO, a <strong>Stack<t></t></strong> stos LIFO.</li>
    <li>
<strong>ConcurrentDictionary<tkey tvalue></tkey></strong> i inne kolekcje wsp&oacute;&#322;bie&#380;ne s&#261; przeznaczone do dost&#281;pu z wielu w&#261;tk&oacute;w.</li>
    <li>W publicznych API cz&#281;sto lepiej zwraca&#263; <strong>IReadOnlyList<t></t></strong> lub <strong>IEnumerable<t></t></strong> ni&#380; konkretn&#261; implementacj&#281;.</li>
  </ul>
</div><h2 id="najpierw-wybierz-sposob-dostepu-do-danych">Najpierw wybierz spos&oacute;b dost&#281;pu do danych</h2><p>Kolekcja nie jest tylko pojemnikiem na obiekty. Ka&#380;dy typ narzuca okre&#347;lony model pracy. Zanim napisz&#281; <code>new List<order>()</order></code>, zastanawiam si&#281;, czy b&#281;d&#281; potrzebowa&#322; element&oacute;w w konkretnej kolejno&#347;ci, wyszukiwania po identyfikatorze, usuwania z pocz&#261;tku czy mo&#380;e wy&#322;&#261;cznie informacji, czy dana warto&#347;&#263; ju&#380; wyst&#261;pi&#322;a.</p><p>Najprostszy podzia&#322; wygl&#261;da tak. <strong>Sekwencja</strong> przechowuje elementy w kolejno&#347;ci i zwykle daje dost&#281;p przez indeks. <strong>S&#322;ownik</strong> wi&#261;&#380;e klucz z warto&#347;ci&#261;. <strong>Zbi&oacute;r</strong> pilnuje unikalno&#347;ci, a kolejka i stos definiuj&#261; kolejno&#347;&#263; obs&#322;ugi element&oacute;w.</p><table>
  <tbody>
    <tr>
      <th>Potrzeba</th>
      <th>Najcz&#281;stszy wyb&oacute;r</th>
      <th>Dlaczego</th>
    </tr>
    <tr>
      <td>Dost&#281;p przez pozycj&#281;</td>
      <td><code>List<t></t></code></td>
      <td>Szybki indeks i dynamiczny rozmiar</td>
    </tr>
    <tr>
      <td>Sta&#322;a liczba element&oacute;w</td>
      <td><code>T[]</code></td>
      <td>Prosta, szybka struktura o sta&#322;ym rozmiarze</td>
    </tr>
    <tr>
      <td>Wyszukiwanie po kluczu</td>
      <td><code>Dictionary<tkey tvalue></tkey></code></td>
      <td>Zwykle sta&#322;oczasowy dost&#281;p do warto&#347;ci</td>
    </tr>
    <tr>
      <td>Unikalne elementy</td>
      <td><code>HashSet<t></t></code></td>
      <td>Nie dopuszcza duplikat&oacute;w i szybko sprawdza obecno&#347;&#263;</td>
    </tr>
    <tr>
      <td>Obs&#322;uga FIFO</td>
      <td><code>Queue<t></t></code></td>
      <td>Pierwszy dodany element jest obs&#322;ugiwany jako pierwszy</td>
    </tr>
    <tr>
      <td>Obs&#322;uga LIFO</td>
      <td><code>Stack<t></t></code></td>
      <td>Ostatni dodany element jest pobierany jako pierwszy</td>
    </tr>
  </tbody>
</table><p>W praktyce najcz&#281;&#347;ciej zaczynam od <strong>List<t></t></strong>, ale nie traktuj&#281; jej jako rozwi&#261;zania uniwersalnego. Je&#380;eli g&#322;&oacute;wna operacja brzmi &bdquo;znajd&#378; obiekt o identyfikatorze&rdquo;, lista szybko stanie si&#281; niepotrzebnym wyszukiwaniem liniowym.</p><p><img src="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/post_image/0629a9454c4cc62a6e3f13d2566e580a/c-collections-list-dictionary-hashset-queue-stack-comparison-diagram.webp" class="image article-image" loading="lazy" alt="Wykres por&oacute;wnuje wydajno&#347;&#263; kolekcji C# (Array, List, LinkedList, Dictionary, Stack, Queue) dla operacji dost&#281;pu, wyszukiwania, wstawiania i usuwania."></p><h2 id="list-tablica-czy-linkedlist">List<t>, tablica czy LinkedList<t></t></t></h2><h3 id="list-jako-domyslna-lista-robocza">List<t> jako domy&#347;lna lista robocza</t></h3><p><code>List<t></t></code> przechowuje elementy w wewn&#281;trznej tablicy i automatycznie zwi&#281;ksza jej pojemno&#347;&#263;. Odczyt przez indeks, na przyk&#322;ad <code>products[10]</code>, ma z&#322;o&#380;ono&#347;&#263; <strong>O(1)</strong>. Dodawanie na ko&#324;cu jest &#347;rednio bardzo szybkie, ale przy powi&#281;kszaniu wewn&#281;trznej tablicy mo&#380;e wymaga&#263; realokacji i skopiowania element&oacute;w.</p><pre><code>var tags = new List<string> { "azure", "dotnet" };

tags.Add("csharp");
tags.Remove("azure");

<a href="https://kursdotnet.pl/linq-i-foreach-w-c-zasady-pulapki-i-dobre-praktyki">foreach</a> (var tag in tags)
{
    Console.WriteLine(tag);
}</string></code></pre><p>Je&#347;li znam przybli&#380;on&#261; liczb&#281; element&oacute;w, ustawiam pojemno&#347;&#263; pocz&#261;tkow&#261;. To drobna decyzja, kt&oacute;ra przy du&#380;ych kolekcjach ogranicza liczb&#281; realokacji.</p><pre><code>var events = new List<auditevent>(capacity: 10_000);</auditevent></code></pre><p>Najwi&#281;kszym ograniczeniem listy jest wstawianie i usuwanie w &#347;rodku. Wszystkie kolejne elementy musz&#261; zosta&#263; przesuni&#281;te, wi&#281;c operacja kosztuje zwykle <strong>O(n)</strong>. Lista pasuje zatem do odczytu po indeksie i dopisywania na ko&#324;cu, ale nie do cz&#281;stego usuwania z pocz&#261;tku.</p><h3 id="kiedy-lepsza-bedzie-tablica">Kiedy lepsza b&#281;dzie tablica</h3><p>Tablica <code>T[]</code> ma sta&#322;y rozmiar po utworzeniu. Wybieram j&#261;, gdy liczba pozycji jest znana albo gdy przekazuj&#281; dane do API, kt&oacute;re pracuje w&#322;a&#347;nie na tablicach. Daje szybki dost&#281;p indeksowany i ma niewielki narzut, ale nie oferuje wygodnej zmiany rozmiaru.</p><pre><code>int[] points = [10, 20, 30, 40];

Console.WriteLine(points[2]);</code></pre><p>Od C# 12 mo&#380;na u&#380;ywa&#263; <strong>wyra&#380;e&#324; kolekcji</strong>, czyli kr&oacute;tkiej sk&#322;adni z nawiasami kwadratowymi. Ten sam zapis mo&#380;e utworzy&#263; tablic&#281;, list&#281; lub inn&#261; obs&#322;ugiwan&#261; kolekcj&#281; zale&#380;nie od typu docelowego.</p><pre><code>int[] numbers = [1, 2, 3];
List<int> mutableNumbers = [1, 2, 3];

int[] extended = [..numbers, 4, 5];</int></code></pre><h3 id="dlaczego-linkedlist-rzadko-jest-pierwszym-wyborem">Dlaczego LinkedList<t> rzadko jest pierwszym wyborem</t></h3><p><code>LinkedList<t></t></code> dobrze radzi sobie z dodawaniem i usuwaniem elementu, gdy masz ju&#380; konkretny w&#281;ze&#322;. Nie zapewnia jednak szybkiego dost&#281;pu przez indeks, a znalezienie elementu po warto&#347;ci nadal wymaga przej&#347;cia po kolekcji.</p><p>W aplikacjach biznesowych cz&#281;&#347;ciej wybieram <strong>List<t></t></strong>, poniewa&#380; lepiej wykorzystuje pami&#281;&#263; podr&#281;czn&#261; procesora i jest prostsza w u&#380;yciu. Lista wi&#261;zana ma sens dopiero wtedy, gdy profil operacji rzeczywi&#347;cie uzasadnia jej narzut i nie potrzebujesz dost&#281;pu indeksowanego.</p><h2 id="dictionary-i-hashset-rozwiazuja-dwa-rozne-problemy">Dictionary i HashSet rozwi&#261;zuj&#261; dwa r&oacute;&#380;ne problemy</h2><h3 id="dictionary-do-wyszukiwania-po-kluczu">Dictionary<tkey tvalue> do wyszukiwania po kluczu</tkey></h3><p>S&#322;ownik przechowuje pary klucz-warto&#347;&#263;. Je&#380;eli mam kolekcj&#281; u&#380;ytkownik&oacute;w i cz&#281;sto pobieram u&#380;ytkownika po identyfikatorze, s&#322;ownik jest naturalnym wyborem.</p><pre><code>var usersById = new Dictionary<int user>
{
    [101] = new User("Anna"),
    [102] = new User("Piotr")
};

if (usersById.TryGetValue(101, out var user))
{
    Console.WriteLine(user.Name);
}</int></code></pre><p>&#346;redni koszt dodania i wyszukania elementu to <strong>O(1)</strong>, cho&#263; zale&#380;y od jako&#347;ci funkcji haszuj&#261;cej i rozk&#322;adu kluczy. Do bezpiecznego odczytu preferuj&#281; <code>TryGetValue</code>, poniewa&#380; nie rzuca wyj&#261;tku, gdy klucza nie ma.</p><p>Trzeba te&#380; pami&#281;ta&#263;, &#380;e klucz musi mie&#263; poprawnie zdefiniowan&#261; r&oacute;wno&#347;&#263; i funkcj&#281; <code>GetHashCode</code>. Dla w&#322;asnych typ&oacute;w b&#322;&#281;dna implementacja por&oacute;wnywania mo&#380;e powodowa&#263; trudne do zauwa&#380;enia problemy z odnajdywaniem element&oacute;w.</p><h3 id="hashset-do-unikalnosci-i-szybkiego-contains">HashSet<t> do unikalno&#347;ci i szybkiego Contains</t></h3><p><code>HashSet<t></t></code> nie przechowuje par klucz-warto&#347;&#263;. Jego zadaniem jest reprezentowanie zbioru unikalnych element&oacute;w oraz szybkie sprawdzanie, czy element nale&#380;y do zbioru.</p><pre><code>var enabledFeatures = new HashSet<string>(
    StringComparer.OrdinalIgnoreCase)
{
    "reports",
    "export"
};

if (enabledFeatures.Contains("EXPORT"))
{
    Console.WriteLine("Eksport jest w&#322;&#261;czony");
}</string></code></pre><p>To dobry wyb&oacute;r przy filtrowaniu duplikat&oacute;w, sprawdzaniu uprawnie&#324; albo por&oacute;wnywaniu dw&oacute;ch grup warto&#347;ci. <strong>HashSet nie zachowuje kolejno&#347;ci biznesowej</strong>, wi&#281;c nie u&#380;ywam go tam, gdzie kolejno&#347;&#263; wy&#347;wietlania ma znaczenie.</p><p>Je&#380;eli elementy maj&#261; by&#263; uporz&#261;dkowane, rozwa&#380;am <code><a href="https://kursdotnet.pl/drzewo-binarne-w-c-jak-dziala-i-kiedy-uzyc-bst">SortedSet</a><t></t></code>. Operacje s&#261; wtedy zwykle wolniejsze, oko&#322;o <strong>O(log n)</strong>, ale zbi&oacute;r utrzymuje porz&#261;dek zgodny z komparatorem.</p><h3 id="klucz-moze-wymagac-wlasnego-porownywania">Klucz mo&#380;e wymaga&#263; w&#322;asnego por&oacute;wnywania</h3><p>Dla napis&oacute;w &#347;wiadomie wybieram spos&oacute;b por&oacute;wnywania. Domy&#347;lne por&oacute;wnanie mo&#380;e by&#263; wra&#380;liwe na wielko&#347;&#263; liter, a w przypadku identyfikator&oacute;w technicznych cz&#281;sto chc&#281; zachowania niewra&#380;liwego na wielko&#347;&#263; liter.</p><pre><code>var settings = new Dictionary<string string>(
    StringComparer.OrdinalIgnoreCase);

settings["Theme"] = "dark";

Console.WriteLine(settings["theme"]);</string></code></pre><p>To nie jest kosmetyka. <strong>Comparer wp&#322;ywa na to, czy dwa klucze s&#261; uznane za takie same</strong>, dlatego powinien wynika&#263; z regu&#322; domeny, a nie z przypadkowego ustawienia.</p><h2 id="queue-stack-i-kolekcje-do-przetwarzania-zadan">Queue, Stack i kolekcje do przetwarzania zada&#324;</h2><p><code>Queue<t></t></code> stosuj&#281; wtedy, gdy elementy maj&#261; by&#263; obs&#322;ugiwane w kolejno&#347;ci nap&#322;ywu. To <a href="https://kursdotnet.pl/programowanie-obiektowe-w-c-bez-zbednej-teorii">klasy</a>czny model FIFO, przydatny przy kolejce polece&#324;, zadaniach do wykonania albo przetwarzaniu komunikat&oacute;w.</p><pre><code>var jobs = new Queue<string>();

jobs.Enqueue("send-email");
jobs.Enqueue("generate-report");

var nextJob = jobs.Dequeue();</string></code></pre><p>Operacje <code>Enqueue</code> i <code>Dequeue</code> s&#261; zwykle wykonywane w czasie <strong>O(1)</strong>. Je&#380;eli tylko sprawdzam pierwszy element, u&#380;ywam <code>Peek</code>, a nie <code>Dequeue</code>, poniewa&#380; ta druga metoda usuwa element.</p><p><code>Stack<t></t></code> dzia&#322;a odwrotnie. Ostatni dodany element jest pobierany jako pierwszy, czyli obowi&#261;zuje zasada LIFO. Przydaje si&#281; przy historii operacji, cofaniu zmian, analizie wyra&#380;e&#324; i przechodzeniu po strukturach.</p><pre><code>var pages = new Stack<string>();

pages.Push("/home");
pages.Push("/orders");

var previousPage = pages.Pop();</string></code></pre><p>Cz&#281;sty b&#322;&#261;d polega na u&#380;ywaniu <code>List<t></t></code> jako kolejki przez usuwanie elementu o indeksie zero. Taka operacja przesuwa ca&#322;&#261; reszt&#281; listy i mo&#380;e kosztowa&#263; <strong>O(n)</strong>. Do FIFO lepiej u&#380;y&#263; w&#322;a&#347;ciwej abstrakcji.</p><h3 id="gdy-wiele-watkow-korzysta-z-tej-samej-kolekcji">Gdy wiele w&#261;tk&oacute;w korzysta z tej samej kolekcji</h3><p><code>List<t></t></code>, <code>Dictionary<tkey tvalue></tkey></code> i <code>Queue<t></t></code> nie staj&#261; si&#281; bezpieczne tylko dlatego, &#380;e s&#261; u&#380;ywane w aplikacji asynchronicznej. <strong>Asynchroniczno&#347;&#263; i wsp&oacute;&#322;bie&#380;no&#347;&#263; to r&oacute;&#380;ne problemy</strong>. Je&#380;eli wiele w&#261;tk&oacute;w jednocze&#347;nie modyfikuje kolekcj&#281;, potrzebujesz synchronizacji albo typu przeznaczonego do takiego scenariusza.</p><table>
  <tbody>
    <tr>
      <th>Scenariusz</th>
      <th>Typ</th>
      <th>Praktyczna rola</th>
    </tr>
    <tr>
      <td>Wsp&oacute;lny s&#322;ownik wielu w&#261;tk&oacute;w</td>
      <td><code>ConcurrentDictionary<tkey tvalue></tkey></code></td>
      <td>Bezpieczne dodawanie i odczyt kluczy</td>
    </tr>
    <tr>
      <td>Wsp&oacute;lna kolejka producent&oacute;w i konsument&oacute;w</td>
      <td><code>ConcurrentQueue<t></t></code></td>
      <td>Bezpieczna kolejka FIFO</td>
    </tr>
    <tr>
      <td>Wsp&oacute;lny stos</td>
      <td><code>ConcurrentStack<t></t></code></td>
      <td>Bezpieczna obs&#322;uga LIFO</td>
    </tr>
    <tr>
      <td>Ograniczona kolejka z blokowaniem</td>
      <td><code>BlockingCollection<t></t></code></td>
      <td>Kontrola producent&oacute;w, konsument&oacute;w i pojemno&#347;ci</td>
    </tr>
  </tbody>
</table><p>Nie dodaj&#281; blokady na &#347;lepo. Zewn&#281;trzny <code>lock</code> mo&#380;e by&#263; wystarczaj&#261;cy przy prostym, lokalnym przypadku, ale przy wielu operacjach i du&#380;ej liczbie w&#261;tk&oacute;w lepiej zacz&#261;&#263; od <strong>System.Collections.Concurrent</strong>. Synchronizacja ma koszt, wi&#281;c warto dopasowa&#263; j&#261; do rzeczywistego obci&#261;&#380;enia.</p><h2 id="wydajnosc-to-nie-tylko-liczba-elementow">Wydajno&#347;&#263; to nie tylko liczba element&oacute;w</h2><p>Dwie kolekcje mog&#261; przechowywa&#263; te same dane, ale zupe&#322;nie inaczej reagowa&#263; na operacje. Dlatego podczas wyboru patrz&#281; na to, co kod robi najcz&#281;&#347;ciej, a nie tylko na liczb&#281; rekord&oacute;w.</p><table>
  <tbody>
    <tr>
      <th>Operacja</th>
      <th>List<t></t></th>
      <th>Dictionary<tkey tvalue></tkey></th>
      <th>HashSet<t></t></th>
      <th>Queue/Stack</th>
    </tr>
    <tr>
      <td>Odczyt po indeksie</td>
      <td>O(1)</td>
      <td>Nie dotyczy</td>
      <td>Nie dotyczy</td>
      <td>Nie jest g&#322;&oacute;wnym zastosowaniem</td>
    </tr>
    <tr>
      <td>Wyszukanie elementu</td>
      <td>O(n)</td>
      <td>&#346;rednio O(1) po kluczu</td>
      <td>&#346;rednio O(1)</td>
      <td>O(n), je&#347;li szukasz wewn&#261;trz</td>
    </tr>
    <tr>
      <td>Dodanie na ko&#324;cu</td>
      <td>&#346;rednio O(1)</td>
      <td>&#346;rednio O(1)</td>
      <td>&#346;rednio O(1)</td>
      <td>O(1)</td>
    </tr>
    <tr>
      <td>Usuni&#281;cie ze &#347;rodka</td>
      <td>O(n)</td>
      <td>&#346;rednio O(1) po kluczu</td>
      <td>&#346;rednio O(1)</td>
      <td>Nie jest g&#322;&oacute;wnym zastosowaniem</td>
    </tr>
  </tbody>
</table><p>Notacja <strong>O(n)</strong> opisuje wzrost kosztu wraz z liczb&#261; element&oacute;w. Nie oznacza automatycznie, &#380;e operacja jest wolna dla ma&#322;ej kolekcji. Je&#347;li lista ma 20 element&oacute;w, liniowe wyszukiwanie mo&#380;e by&#263; prostsze i wystarczaj&#261;co szybkie. Problemy pojawiaj&#261; si&#281; wtedy, gdy kod wykonuje tak&#261; operacj&#281; tysi&#261;ce razy albo dane rosn&#261; bez kontroli.</p><p>Nie optymalizuj&#281; kolekcji wy&#322;&#261;cznie na podstawie tabeli z&#322;o&#380;ono&#347;ci. Licz&#261; si&#281; r&oacute;wnie&#380; alokacje pami&#281;ci, lokalno&#347;&#263; danych, koszt sortowania, cz&#281;stotliwo&#347;&#263; modyfikacji i czytelno&#347;&#263; kodu. <strong>Profilowanie powinno rozstrzyga&#263; spory</strong>, gdy r&oacute;&#380;nica ma znaczenie dla dzia&#322;ania aplikacji.</p><h2 id="jak-projektowac-api-i-unikac-typowych-bledow">Jak projektowa&#263; API i unika&#263; typowych b&#322;&#281;d&oacute;w</h2><h3 id="nie-wystawiaj-modyfikowalnej-kolekcji-bez-potrzeby">Nie wystawiaj modyfikowalnej kolekcji bez potrzeby</h3><p>Je&#380;eli metoda tylko zwraca dane, nie zawsze chc&#281; oddawa&#263; klientowi <code>List<t></t></code>. Odbiorca mo&#380;e wtedy zmodyfikowa&#263; kolekcj&#281; albo zacz&#261;&#263; polega&#263; na jej konkretnej implementacji.</p><pre><code>public IReadOnlyList<product> GetFeaturedProducts()
{
    return _products;
}</product></code></pre><p><code>IReadOnlyList<t></t></code> komunikuje, &#380;e odbiorca mo&#380;e czyta&#263; elementy i u&#380;ywa&#263; indeksu, ale nie powinien zmienia&#263; kolekcji przez ten interfejs. Gdy potrzebuj&#281; wy&#322;&#261;cznie sekwencyjnego odczytu, wystarczy <code>IEnumerable<t></t></code>. To ma znaczenie tak&#380;e dla op&oacute;&#378;nionego wykonania LINQ, dlatego trzeba wiedzie&#263;, czy wynik jest ju&#380; zmaterializowany.</p><h3 id="nie-uzywaj-starego-systemcollections-bez-powodu">Nie u&#380;ywaj starego System.Collections bez powodu</h3><p>Typy takie jak <code>ArrayList</code> i <code>Hashtable</code> przechowuj&#261; elementy jako <code>object</code>. Wymagaj&#261; rzutowania i pozwalaj&#261; wprowadzi&#263; do kolekcji warto&#347;ci r&oacute;&#380;nych typ&oacute;w, co przesuwa b&#322;&#281;dy z kompilacji na uruchomienie programu.</p><p>W nowym kodzie wybieram kolekcje generyczne, takie jak <strong>List<t> i Dictionary<tkey tvalue></tkey></t></strong>. S&#261; bezpieczniejsze typowo, czytelniejsze i zwykle wydajniejsze, szczeg&oacute;lnie przy typach warto&#347;ci, kt&oacute;re nie musz&#261; by&#263; opakowywane jako <code>object</code>.</p><h3 id="uwazaj-na-duplikaty-rownosc-i-kolejnosc">Uwa&#380;aj na duplikaty, r&oacute;wno&#347;&#263; i kolejno&#347;&#263;</h3><p>Dodanie tego samego obiektu do listy jest dozwolone. W s&#322;owniku drugi wpis z tym samym kluczem mo&#380;e zako&#324;czy&#263; si&#281; wyj&#261;tkiem przy u&#380;yciu <code>Add</code> albo zast&#261;pieniem warto&#347;ci przy u&#380;yciu indeksatora. Zbi&oacute;r z kolei odrzuci duplikat zgodnie z regu&#322;ami r&oacute;wno&#347;ci.</p><p>Nie opieram logiki biznesowej na kolejno&#347;ci element&oacute;w w s&#322;owniku. Je&#380;eli kolejno&#347;&#263; jest cz&#281;&#347;ci&#261; wymagania, u&#380;ywam listy, sortowanej kolekcji albo jawnie sortuj&#281; wynik. <strong>Struktura danych powinna odzwierciedla&#263; regu&#322;&#281; biznesow&#261;</strong>, a nie przypadkowe zachowanie aktualnej implementacji.</p><p class="read-more"><strong>Przeczytaj r&oacute;wnie&#380;: <a href="https://kursdotnet.pl/jak-odwrocic-string-w-c-i-nie-zepsuc-unicode">Jak odwr&oacute;ci&#263; string w C# i nie zepsu&#263; Unicode?</a></strong></p><h3 id="rozwaz-niezmiennosc-przy-wspoldzielonym-stanie">Rozwa&#380; niezmienno&#347;&#263; przy wsp&oacute;&#322;dzielonym stanie</h3><p>Kolekcje z przestrzeni <code>System.Collections.Immutable</code> tworz&#261; nowe wersje danych zamiast modyfikowa&#263; istniej&#261;c&#261; kolekcj&#281;. To wygodne, gdy wiele cz&#281;&#347;ci programu ma bezpiecznie czyta&#263; ten sam stan albo gdy chc&#281; unikn&#261;&#263; przypadkowych zmian.</p><pre><code>var roles = ImmutableHashSet.Create("reader", "editor");
var updatedRoles = roles.Add("admin");

Console.WriteLine(roles.Contains("admin"));        // false
Console.WriteLine(updatedRoles.Contains("admin")); // true</code></pre><p>Nie u&#380;ywam ich automatycznie wsz&#281;dzie. Niezmienno&#347;&#263; upraszcza wsp&oacute;&#322;dzielenie danych, ale kolejne wersje mog&#261; mie&#263; wi&#281;kszy koszt ni&#380; zwyk&#322;a modyfikacja listy. To &#347;wiadomy kompromis mi&#281;dzy <strong>bezpiecze&#324;stwem stanu a narzutem operacji</strong>.</p><h2 id="najprostsza-regula-ktora-zwykle-prowadzi-do-dobrej-decyzji">Najprostsza regu&#322;a, kt&oacute;ra zwykle prowadzi do dobrej decyzji</h2><p>Je&#380;eli potrzebujesz zwyk&#322;ej, zmiennej sekwencji, zacznij od <code>List<t></t></code>. Gdy odczyt odbywa si&#281; po kluczu, wybierz <code>Dictionary<tkey tvalue></tkey></code>, a gdy najwa&#380;niejsza jest unikalno&#347;&#263;, u&#380;yj <code>HashSet<t></t></code>. Do kolejno&#347;ci FIFO i LIFO s&#322;u&#380;&#261; odpowiednio <code>Queue<t></t></code> oraz <code>Stack<t></t></code>.</p><p>Najwi&#281;cej problem&oacute;w pojawia si&#281; wtedy, gdy kolekcj&#281; dobiera si&#281; wed&#322;ug przyzwyczajenia. Ja zaczynam od pytania o najcz&#281;stsz&#261; operacj&#281;, sprawdzam wymagania dotycz&#261;ce kolejno&#347;ci i duplikat&oacute;w, a dopiero potem rozwa&#380;am wsp&oacute;&#322;bie&#380;no&#347;&#263;, niezmienno&#347;&#263; i optymalizacj&#281; pami&#281;ci. Taka kolejno&#347;&#263; decyzji prowadzi zwykle do kodu, kt&oacute;ry jest jednocze&#347;nie <strong>czytelny, przewidywalny i wystarczaj&#261;co szybki</strong>.</p>]]></content:encoded>
      <author>Radosław Krajewski</author>
      <category>C# i .NET</category>
      <media:thumbnail url="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/thumbnail/06a423d0aef3a6123f99f1f27956380a/c-collections-jak-dobrac-kolekcje-do-operacji.webp"/>
      <pubDate>Sat, 12 Sep 2026 12:58:00 +0200</pubDate>
    </item>
    <item>
      <title>Podzapytania SQL bez pułapek - IN, EXISTS, JOIN i CTE</title>
      <link>https://kursdotnet.pl/podzapytania-sql-bez-pulapek-in-exists-join-i-cte</link>
      <description>Podzapytania SQL w praktyce: IN, EXISTS, JOIN i CTE. Sprawdź przykłady, pułapki z NULL i zasady wyboru właściwej konstrukcji.</description>
      <content:encoded><![CDATA[<?xml encoding="utf-8" ?><p>Gdy jedno zapytanie SQL potrzebuje wyniku innego zapytania, nie trzeba od razu budowa&#263; skomplikowanych procedur ani przenosi&#263; danych do aplikacji. Zagnie&#380;d&#380;ony <strong>SELECT</strong> pozwala filtrowa&#263; rekordy, oblicza&#263; warto&#347;ci i tworzy&#263; tymczasowe zestawy danych w ramach jednego polecenia. W praktyce zapis typu select from select oznacza w&#322;a&#347;nie prac&#281; z podzapytaniami, dlatego poka&#380;&#281; jego najwa&#380;niejsze warianty, przyk&#322;ady oraz pu&#322;apki zwi&#261;zane z wydajno&#347;ci&#261;.</p><div class="short-summary">
  <h2 id="zagniezdzone-zapytania-upraszczaja-filtrowanie-i-analize-danych">Zagnie&#380;d&#380;one zapytania upraszczaj&#261; filtrowanie i analiz&#281; danych</h2>
  <ul>
    <li>
<strong>Podzapytanie</strong> to instrukcja SELECT umieszczona wewn&#261;trz innego zapytania.</li>
    <li>Mo&#380;esz u&#380;ywa&#263; go w klauzulach <strong>WHERE, FROM, SELECT</strong> oraz <strong>HAVING</strong>.</li>
    <li>Operator <strong>IN</strong> sprawdza wiele warto&#347;ci, a <strong>EXISTS</strong> jedynie obecno&#347;&#263; pasuj&#261;cego rekordu.</li>
    <li>Podzapytanie skalarne musi zwr&oacute;ci&#263; <strong>jedn&#261; warto&#347;&#263;</strong>, inaczej baza zg&#322;osi b&#322;&#261;d.</li>
    <li>Przy wi&#281;kszych zbiorach danych cz&#281;sto lepszym wyborem b&#281;dzie <strong>JOIN lub CTE</strong>.</li>
  </ul>
</div><p><img src="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/post_image/3f621054fcf8ba29da1dbd7214ec7760/diagram-sql-subquery-select-where-from-visualization.webp" class="image article-image" loading="lazy" alt="Diagram SQL: zapytanie zewn&#281;trzne SELECT pobiera dane z bazy, a podzapytanie SELECT wybiera kraje m&#281;&#380;czyzn z tabeli Tourist1."></p><h2 id="podzapytanie-sql-w-prostym-przykladzie">Podzapytanie SQL w prostym przyk&#322;adzie</h2><p>Podzapytanie, nazywane te&#380; subquery albo nested query, jest zwyk&#322;ym poleceniem <strong>SELECT umieszczonym w innym zapytaniu</strong>. Zapytanie wewn&#281;trzne przygotowuje wynik, a zapytanie zewn&#281;trzne wykorzystuje go do dalszego filtrowania lub prezentacji danych.</p><p>Za&#322;&oacute;&#380;my, &#380;e mamy tabel&#281; <code>Products</code> z kolumnami <code>ProductName</code> i <code>Price</code>. Chcemy pobra&#263; produkty dro&#380;sze ni&#380; &#347;rednia cena wszystkich produkt&oacute;w:</p><pre><code>SELECT ProductName, Price
FROM Products
WHERE Price &gt; (
    SELECT AVG(Price)
    FROM Products
);</code></pre><p>Wewn&#281;trzny SELECT oblicza &#347;redni&#261;, na przyk&#322;ad <strong>250 z&#322;</strong>. Zewn&#281;trzne zapytanie por&oacute;wnuje z ni&#261; cen&#281; ka&#380;dego produktu. To dobry przyk&#322;ad podzapytania skalarnego, poniewa&#380; zwraca ono dok&#322;adnie jedn&#261; warto&#347;&#263;.</p><p>Ja traktuj&#281; takie zapytanie jako dwa logiczne kroki. Najpierw baza ustala warto&#347;&#263; referencyjn&#261;, a dopiero p&oacute;&#378;niej wybiera rekordy spe&#322;niaj&#261;ce warunek. Dzi&#281;ki temu kod jest czytelniejszy ni&#380; r&#281;czne wpisywanie aktualnej &#347;redniej, kt&oacute;ra przecie&#380; mo&#380;e zmieni&#263; si&#281; po dodaniu nowych danych.</p><h2 id="podzapytanie-w-where-pozwala-filtrowac-dane-z-innej-tabeli">Podzapytanie w WHERE pozwala filtrowa&#263; dane z innej tabeli</h2><p>Najcz&#281;stsze zastosowanie zagnie&#380;d&#380;onego SELECT-a polega na filtrowaniu jednej tabeli na podstawie wynik&oacute;w z drugiej. Przyk&#322;adem mo&#380;e by&#263; pobranie klient&oacute;w, kt&oacute;rzy z&#322;o&#380;yli przynajmniej jedno zam&oacute;wienie.</p><pre><code>SELECT CustomerId, CustomerName
FROM Customers
WHERE CustomerId IN (
    SELECT CustomerId
    FROM Orders
);</code></pre><p>Operator <strong>IN</strong> por&oacute;wnuje warto&#347;&#263; z kolumny <code>CustomerId</code> z ca&#322;ym zestawem zwr&oacute;conym przez podzapytanie. Wewn&#281;trzny SELECT mo&#380;e zwr&oacute;ci&#263; wiele wierszy, ale powinien zwraca&#263; <strong>jedn&#261; kolumn&#281;</strong>, kt&oacute;rej typ pasuje do warto&#347;ci por&oacute;wnywanej w zapytaniu zewn&#281;trznym.</p><p>Je&#380;eli interesuje nas przeciwna sytuacja, czyli klienci bez zam&oacute;wie&#324;, mo&#380;emy u&#380;y&#263; <code>NOT IN</code>:</p><pre><code>SELECT CustomerId, CustomerName
FROM Customers
WHERE CustomerId NOT IN (
    SELECT CustomerId
    FROM Orders
);</code></pre><p>Tu pojawia si&#281; wa&#380;na pu&#322;apka. Je&#380;eli podzapytanie zwr&oacute;ci cho&#263; jedn&#261; warto&#347;&#263; <strong>NULL</strong>, dzia&#322;anie <code>NOT IN</code> mo&#380;e da&#263; zaskakuj&#261;cy rezultat i nie zwr&oacute;ci&#263; &#380;adnego rekordu. W praktyce bezpieczniej u&#380;y&#263; <code><a href="https://kursdotnet.pl/if-not-exists-i-not-exists-w-sql-roznice-i-przyklady">NOT EXISTS</a></code> albo odfiltrowa&#263; warto&#347;ci puste:</p><pre><code>SELECT CustomerId, CustomerName
FROM Customers c
WHERE NOT EXISTS (
    SELECT 1
    FROM Orders o
    WHERE o.CustomerId = c.CustomerId
);</code></pre><p>W tym przypadku nie interesuje nas konkretna warto&#347;&#263; z zam&oacute;wienia. Sprawdzamy wy&#322;&#261;cznie, czy powi&#261;zany rekord istnieje. Dlatego <strong>EXISTS cz&#281;sto lepiej oddaje intencj&#281;</strong> i nie ma problemu z NULL-em w taki spos&oacute;b jak <code>NOT IN</code>.</p><h2 id="in-exists-i-podzapytanie-skalarne-rozwiazuja-rozne-problemy">IN, EXISTS i podzapytanie skalarne rozwi&#261;zuj&#261; r&oacute;&#380;ne problemy</h2><p>Te konstrukcje bywaj&#261; wrzucane do jednego worka, ale ich znaczenie jest inne. Dob&oacute;r operatora wp&#322;ywa na czytelno&#347;&#263; zapytania, a czasem tak&#380;e na plan wykonania.</p><table>
  <tbody>
    <tr>
      <th>Konstrukcja</th>
      <th>Kiedy jej u&#380;y&#263;</th>
      <th>Wynik podzapytania</th>
    </tr>
    <tr>
      <td><code>IN</code></td>
      <td>Gdy por&oacute;wnujesz warto&#347;&#263; z list&#261; wynik&oacute;w</td>
      <td>Jedna kolumna, wiele wierszy</td>
    </tr>
    <tr>
      <td><code>EXISTS</code></td>
      <td>Gdy sprawdzasz, czy istnieje powi&#261;zany rekord</td>
      <td>Informacja logiczna TRUE/FALSE</td>
    </tr>
    <tr>
      <td>Podzapytanie skalarne</td>
      <td>Gdy potrzebujesz jednej warto&#347;ci, np. &#347;redniej lub maksimum</td>
      <td>Jedna warto&#347;&#263;</td>
    </tr>
    <tr>
      <td><code>NOT EXISTS</code></td>
      <td>Gdy szukasz rekord&oacute;w bez odpowiednika w drugiej tabeli</td>
      <td>Informacja logiczna TRUE/FALSE</td>
    </tr>
  </tbody>
</table><p>Przyk&#322;ad z <code>EXISTS</code> mo&#380;e wygl&#261;da&#263; tak:</p><pre><code>SELECT p.ProductId, p.ProductName
FROM Products p
WHERE EXISTS (
    SELECT 1
    FROM OrderItems oi
    WHERE oi.ProductId = p.ProductId
);</code></pre><p>Wewn&#281;trzne zapytanie jest powi&#261;zane z bie&#380;&#261;cym rekordem produktu przez <code>p.ProductId</code>. To <strong>podzapytanie skorelowane</strong>, czyli takie, kt&oacute;re korzysta z warto&#347;ci dost&#281;pnej w zapytaniu zewn&#281;trznym. Baza analizuje zale&#380;no&#347;&#263; dla kolejnych produkt&oacute;w, cho&#263; optymalizator mo&#380;e wykona&#263; j&#261; znacznie sprytniej ni&#380; dos&#322;ownie wiersz po wierszu.</p><h2 id="select-w-from-tworzy-tymczasowy-zestaw-danych">SELECT w FROM tworzy tymczasowy zestaw danych</h2><p>Podzapytanie mo&#380;e wyst&#261;pi&#263; nie tylko w <code>WHERE</code>, lecz tak&#380;e w klauzuli <code>FROM</code>. Wtedy dzia&#322;a jak tymczasowa tabela, cz&#281;sto nazywana <strong>derived table</strong>. Taki wariant przydaje si&#281;, gdy chcesz najpierw pogrupowa&#263; dane, a dopiero potem filtrowa&#263; wynik agregacji.</p><p>Za&#322;&oacute;&#380;my, &#380;e chcemy znale&#378;&#263; klient&oacute;w, kt&oacute;rych &#322;&#261;czna warto&#347;&#263; zam&oacute;wie&#324; przekracza 10 000 z&#322;:</p><pre><code>SELECT CustomerId, TotalAmount
FROM (
    SELECT CustomerId, SUM(OrderAmount) AS TotalAmount
    FROM Orders
    GROUP BY CustomerId
) AS CustomerTotals
WHERE TotalAmount &gt; 10000;</code></pre><p>Wewn&#281;trzne zapytanie tworzy zestaw zawieraj&#261;cy jednego klienta w ka&#380;dym wierszu. Zewn&#281;trzny SELECT filtruje ju&#380; gotowe sumy. Alias <strong>CustomerTotals jest wymagany</strong> w wielu silnikach baz danych, dlatego nie warto go pomija&#263; nawet wtedy, gdy konkretna wersja systemu dopuszcza taki zapis.</p><p>Podobny efekt mo&#380;na uzyska&#263; za pomoc&#261; CTE, czyli konstrukcji <code>WITH</code>:</p><pre><code>WITH CustomerTotals AS (
    SELECT CustomerId, SUM(OrderAmount) AS TotalAmount
    FROM Orders
    GROUP BY CustomerId
)
SELECT CustomerId, TotalAmount
FROM CustomerTotals
WHERE TotalAmount &gt; 10000;</code></pre><p>Moim zdaniem CTE wygrywa z d&#322;ugim SELECT-em w <code>FROM</code>, gdy zapytanie ma kilka etap&oacute;w albo b&#281;dzie rozwijane przez innych programist&oacute;w. Samo podzapytanie jest kr&oacute;tsze, ale przy rozbudowanej logice szybko staje si&#281; trudne do czytania.</p><h2 id="podzapytanie-w-select-oblicza-wartosc-dla-kazdego-rekordu">Podzapytanie w SELECT oblicza warto&#347;&#263; dla ka&#380;dego rekordu</h2><p>Mo&#380;esz tak&#380;e umie&#347;ci&#263; SELECT bezpo&#347;rednio na li&#347;cie wybieranych kolumn. Wtedy podzapytanie dostarcza dodatkow&#261; warto&#347;&#263; do ka&#380;dego wiersza wyniku.</p><pre><code>SELECT
    c.CustomerId,
    c.CustomerName,
    (
        SELECT COUNT(*)
        FROM Orders o
        WHERE o.CustomerId = c.CustomerId
    ) AS OrderCount
FROM Customers c;</code></pre><p>Wynik poka&#380;e klient&oacute;w oraz liczb&#281; ich zam&oacute;wie&#324;. To wygodny zapis, szczeg&oacute;lnie gdy potrzebujesz jednej prostej warto&#347;ci, ale przy du&#380;ej liczbie klient&oacute;w mo&#380;e by&#263; mniej korzystny ni&#380; <a href="https://kursdotnet.pl/join-on-w-sql-jak-laczyc-tabele-bez-bledow">agregacja</a> po&#322;&#261;czona z <code>JOIN</code>.</p><p>Alternatywa wygl&#261;da nast&#281;puj&#261;co:</p><pre><code>SELECT
    c.CustomerId,
    c.CustomerName,
    COUNT(o.OrderId) AS OrderCount
FROM Customers c
LEFT JOIN Orders o
    ON o.CustomerId = c.CustomerId
GROUP BY c.CustomerId, c.CustomerName;</code></pre><p>Podzapytanie lepiej pokazuje pojedyncz&#261; zale&#380;no&#347;&#263;, natomiast <strong>JOIN z GROUP BY</strong> cz&#281;sto daje optymalizatorowi wi&#281;ksze pole manewru. Nie traktuj&#281; jednak JOIN-a jako automatycznie lepszego rozwi&#261;zania. Je&#380;eli liczy si&#281; prostota i tabela jest niewielka, podzapytanie w <code>SELECT</code> mo&#380;e by&#263; najbardziej zrozumia&#322;ym wyborem.</p><h2 id="najczestsze-bledy-przy-zagniezdzonych-select-ach">Najcz&#281;stsze b&#322;&#281;dy przy zagnie&#380;d&#380;onych SELECT-ach</h2><h3 id="podzapytanie-zwraca-zbyt-wiele-wierszy">Podzapytanie zwraca zbyt wiele wierszy</h3><p>Ten kod mo&#380;e zako&#324;czy&#263; si&#281; b&#322;&#281;dem:</p><pre><code>SELECT ProductName
FROM Products
WHERE Price = (
    SELECT Price
    FROM Products
    WHERE CategoryId = 3
);</code></pre><p>Je&#380;eli kategoria zawiera kilka produkt&oacute;w, wewn&#281;trzny SELECT zwr&oacute;ci wiele cen, a operator <code>=</code> oczekuje jednej warto&#347;ci. Rozwi&#261;zaniem mo&#380;e by&#263; <code>IN</code>, funkcja agreguj&#261;ca albo dodatkowy warunek ograniczaj&#261;cy wynik do jednego rekordu.</p><h3 id="podzapytanie-zwraca-zbyt-wiele-kolumn">Podzapytanie zwraca zbyt wiele kolumn</h3><p>Operator <code>IN</code> potrzebuje jednej kolumny:</p><pre><code>WHERE CustomerId IN (
    SELECT CustomerId, CustomerName
    FROM Customers
);</code></pre><p>Ten zapis jest niepoprawny, poniewa&#380; wewn&#281;trzne zapytanie zwraca dwie kolumny. Je&#380;eli chcesz por&oacute;wnywa&#263; kilka p&oacute;l jednocze&#347;nie, sk&#322;adnia zale&#380;y od silnika bazy i zwykle wymaga por&oacute;wnania krotek albo u&#380;ycia <code>EXISTS</code>.</p><h3 id="brak-aliasow-powoduje-niejasnosc">Brak alias&oacute;w powoduje niejasno&#347;&#263;</h3><p>Przy kilku tabelach &#322;atwo pomyli&#263; kolumn&#281; z zapytania zewn&#281;trznego i wewn&#281;trznego. Jawne aliasy, takie jak <code>c</code>, <code>o</code> i <code>p</code>, ograniczaj&#261; ryzyko b&#322;&#281;du oraz pomagaj&#261; czyta&#263; zapytanie bez zgadywania, z kt&oacute;rej tabeli pochodzi dana warto&#347;&#263;.</p><p class="read-more"><strong>Przeczytaj r&oacute;wnie&#380;: <a href="https://kursdotnet.pl/t-sql-merge-w-praktyce-synchronizacja-danych-bez-pulapek">T-SQL MERGE w praktyce - synchronizacja danych bez pu&#322;apek</a></strong></p><h3 id="null-zmienia-logike-warunku">NULL zmienia logik&#281; warunku</h3><p>SQL stosuje logik&#281; tr&oacute;jwarto&#347;ciow&#261;, wi&#281;c por&oacute;wnanie z <code>NULL</code> nie daje klasycznego TRUE ani FALSE. Przy konstrukcjach <code>NOT IN</code> jest to szczeg&oacute;lnie zdradliwe. Je&#380;eli nie masz pe&#322;nej kontroli nad danymi, zwykle wybieram <strong>NOT EXISTS</strong> i jawne powi&#261;zanie tabel.</p><h2 id="podzapytanie-czy-join-i-cte">Podzapytanie czy JOIN i CTE</h2><p>Nie ma jednej konstrukcji najlepszej w ka&#380;dej sytuacji. Najrozs&#261;dniej zacz&#261;&#263; od pytania, czy potrzebujesz listy warto&#347;ci, sprawdzenia istnienia rekordu, pojedynczego obliczenia czy wieloetapowego przetwarzania danych.</p><table>
  <tbody>
    <tr>
      <th>Potrzeba</th>
      <th>Najcz&#281;&#347;ciej pasuj&#261;ce rozwi&#261;zanie</th>
      <th>Dlaczego</th>
    </tr>
    <tr>
      <td>Por&oacute;wnanie z list&#261; identyfikator&oacute;w</td>
      <td><code>IN</code></td>
      <td>Czytelnie opisuje filtrowanie po zbiorze</td>
    </tr>
    <tr>
      <td>Sprawdzenie powi&#261;zanego rekordu</td>
      <td><code>EXISTS</code></td>
      <td>Nie pobiera danych, kt&oacute;rych nie potrzebujesz</td>
    </tr>
    <tr>
      <td>Pobranie kolumn z dw&oacute;ch tabel</td>
      <td><code>JOIN</code></td>
      <td>Naturalnie &#322;&#261;czy dane w jeden wynik</td>
    </tr>
    <tr>
      <td>Kilka etap&oacute;w oblicze&#324;</td>
      <td><code>CTE</code></td>
      <td>Porz&#261;dkuje z&#322;o&#380;on&#261; logik&#281;</td>
    </tr>
    <tr>
      <td>Jedna warto&#347;&#263; pomocnicza</td>
      <td>Podzapytanie skalarne</td>
      <td>Kr&oacute;tki zapis bez dodatkowego &#322;&#261;czenia tabel</td>
    </tr>
  </tbody>
</table><p>Wydajno&#347;&#263; zale&#380;y od silnika bazy, indeks&oacute;w, liczby rekord&oacute;w i planu wykonania. Dla tabel licz&#261;cych miliony wierszy nie zgaduj&#281;, czy podzapytanie b&#281;dzie szybkie. Sprawdzam plan wykonania i mierz&#281; czas na danych zbli&#380;onych do produkcyjnych, bo sama forma zapisu nie daje wiarygodnej odpowiedzi.</p><p>Warto te&#380; pami&#281;ta&#263;, &#380;e SQL Server, <a href="https://kursdotnet.pl/coalesce-w-postgresql-jak-bezpiecznie-obslugiwac-null">PostgreSQL</a>, MySQL i Oracle maj&#261; podobne podstawy, ale r&oacute;&#380;ni&#261; si&#281; detalami optymalizacji oraz obs&#322;ugi specyficznych konstrukcji. Przeno&#347;ny przyk&#322;ad zwykle zadzia&#322;a wsz&#281;dzie, lecz przy kodzie produkcyjnym trzeba sprawdzi&#263; dokumentacj&#281; u&#380;ywanego systemu.</p><h2 id="jak-pisac-zagniezdzone-zapytania-ktore-da-sie-utrzymac">Jak pisa&#263; zagnie&#380;d&#380;one zapytania, kt&oacute;re da si&#281; utrzyma&#263;</h2><p>Najpierw okre&#347;lam, jaki wynik ma dostarczy&#263; zapytanie wewn&#281;trzne. Je&#380;eli jest to jedna liczba, u&#380;ywam agregacji i pilnuj&#281;, aby wynik by&#322; skalarny. Je&#380;eli potrzebuj&#281; informacji o istnieniu, wybieram <code>EXISTS</code>, a je&#380;eli chc&#281; pobra&#263; kolumny z kilku &#378;r&oacute;de&#322;, rozwa&#380;am <code>JOIN</code>.</p><ul>
  <li>Stosuj <strong>aliasy tabel</strong>, szczeg&oacute;lnie w podzapytaniach skorelowanych.</li>
  <li>Unikaj <strong>SELECT *</strong>, gdy podzapytanie ma dzia&#322;a&#263; jako &#378;r&oacute;d&#322;o danych dla dalszej logiki.</li>
  <li>Sprawd&#378;, czy podzapytanie zwraca jedn&#261; kolumn&#281; lub jedn&#261; warto&#347;&#263;, zale&#380;nie od u&#380;ytego operatora.</li>
  <li>Przetestuj zachowanie dla <strong>pustego wyniku</strong> i warto&#347;ci NULL.</li>
  <li>Przy du&#380;ych tabelach sprawd&#378; indeksy na kolumnach u&#380;ywanych w warunkach powi&#261;zania.</li>
  <li>Gdy zapytanie robi si&#281; wielopoziomowe, przenie&#347; kolejne etapy do <strong>CTE</strong> albo rozwa&#380; widok.</li>
</ul><p>Najwi&#281;ksz&#261; r&oacute;&#380;nic&#281; robi nie samo u&#380;ycie zagnie&#380;d&#380;onego SELECT-a, lecz dopasowanie konstrukcji do celu. Dobrze napisane podzapytanie potrafi skr&oacute;ci&#263; kod i jasno pokaza&#263; logik&#281; biznesow&#261;, ale &#378;le u&#380;yte mo&#380;e ukry&#263; kosztowne operacje za pozornie niewinn&#261; sk&#322;adni&#261;.</p><h2 id="od-jednego-select-a-do-swiadomego-wyboru-konstrukcji">Od jednego SELECT-a do &#347;wiadomego wyboru konstrukcji</h2><p>Zagnie&#380;d&#380;one zapytania s&#261; naturalnym sposobem pracy z zale&#380;no&#347;ciami mi&#281;dzy danymi. Pozwalaj&#261; filtrowa&#263; rekordy na podstawie innych tabel, oblicza&#263; warto&#347;ci pomocnicze i budowa&#263; tymczasowe zestawy bez kopiowania danych do aplikacji.</p><p>Najprostsza regu&#322;a wygl&#261;da tak: <strong>IN s&#322;u&#380;y do por&oacute;wnania ze zbiorem, EXISTS do sprawdzenia obecno&#347;ci, a podzapytanie skalarne do pobrania jednej warto&#347;ci</strong>. Gdy logika obejmuje wiele etap&oacute;w, lepiej si&#281;gn&#261;&#263; po CTE lub JOIN i sprawdzi&#263; plan wykonania. Taki spos&oacute;b my&#347;lenia jest wa&#380;niejszy ni&#380; zapami&#281;tanie pojedynczego szablonu sk&#322;adni.</p>]]></content:encoded>
      <author>Radosław Krajewski</author>
      <category>Bazy danych i SQL</category>
      <media:thumbnail url="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/thumbnail/b985d8d21bb31338f71919932967b875/podzapytania-sql-bez-pulapek-in-exists-join-i-cte.webp"/>
      <pubDate>Sat, 12 Sep 2026 08:14:00 +0200</pubDate>
    </item>
    <item>
      <title>Visual Basic w .NET - kiedy wybrać go zamiast C#?</title>
      <link>https://kursdotnet.pl/visual-basic-w-net-kiedy-wybrac-go-zamiast-c</link>
      <description>Visual Basic w .NET: sprawdź, kiedy wybrać go zamiast C#, jak zacząć pracę i uniknąć błędów w aplikacjach Windows.</description>
      <content:encoded><![CDATA[<?xml encoding="utf-8" ?><p>Gdy trzeba szybko utrzyma&#263; starsz&#261; aplikacj&#281; Windows albo zrozumie&#263; kod napisany wiele lat temu, znajomo&#347;&#263; Visual Basic nadal potrafi zaoszcz&#281;dzi&#263; sporo czasu. Wyja&#347;niam, czym jest ten j&#281;zyk w ekosystemie .NET, gdzie ma sens w 2026 roku, czym r&oacute;&#380;ni si&#281; od C# oraz jak rozpocz&#261;&#263; prac&#281; bez wpadania w typowe pu&#322;apki.</p><div class="short-summary">
  <h2 id="najwazniejsze-decyzje-przed-wyborem-jezyka">Najwa&#380;niejsze decyzje przed wyborem j&#281;zyka</h2>
  <ul>
    <li>
<strong>Visual Basic</strong> jest pe&#322;noprawnym j&#281;zykiem obiektowym dla platformy .NET, a nie wy&#322;&#261;cznie narz&#281;dziem do prostych makr.</li>
    <li>
<strong>WinForms i WPF</strong> to jego najbardziej naturalne zastosowania w nowych i utrzymywanych aplikacjach desktopowych.</li>
    <li>
<strong>C#</strong> daje dzi&#347; szerszy dost&#281;p do dokumentacji, bibliotek, przyk&#322;ad&oacute;w i nowoczesnych technologii .NET.</li>
    <li>
<strong>Option Strict On</strong> ogranicza b&#322;&#281;dy wynikaj&#261;ce z niejawnych konwersji i p&oacute;&#378;nego wi&#261;zania typ&oacute;w.</li>
    <li>
<strong>.NET 10</strong> jest aktualn&#261; wersj&#261; LTS, wspieran&#261; do listopada 2028 roku.</li>
  </ul>
</div><h2 id="czym-jest-visual-basic-i-jak-dziala-w-net">Czym jest Visual Basic i jak dzia&#322;a w .NET</h2><p>Visual Basic to wysokopoziomowy, obiektowy j&#281;zyk programowania rozwijany przez Microsoft. Jego sk&#322;adnia u&#380;ywa wielu s&#322;&oacute;w przypominaj&#261;cych j&#281;zyk naturalny, dlatego pocz&#261;tkuj&#261;cy cz&#281;sto szybciej rozumiej&#261; pierwsze programy ni&#380; w j&#281;zykach opartych na wi&#281;kszej liczbie symboli.</p><p>W praktyce kod nie dzia&#322;a niezale&#380;nie od platformy. Kompilator przekszta&#322;ca go do kodu po&#347;redniego, a aplikacja korzysta ze &#347;rodowiska uruchomieniowego .NET oraz bibliotek takich jak <strong>System.IO</strong>, <strong>System.Net.Http</strong> czy <strong>System.Collections.Generic</strong>. Dzi&#281;ki temu programista mo&#380;e u&#380;ywa&#263; tych samych mechanizm&oacute;w platformy co osoba pisz&#261;ca w C#.</p><p>To wa&#380;ne rozr&oacute;&#380;nienie. J&#281;zyk okre&#347;la sk&#322;adni&#281;, natomiast .NET dostarcza &#347;rodowisko, biblioteki, narz&#281;dzia budowania i model wdra&#380;ania. Dlatego kod w obu j&#281;zykach mo&#380;e korzysta&#263; z tych samych klas, pakiet&oacute;w NuGet, baz danych i us&#322;ug Azure.</p><h3 id="najwazniejsze-cechy-skladni">Najwa&#380;niejsze cechy sk&#322;adni</h3><p>W kodzie cz&#281;sto spotkamy pe&#322;ne s&#322;owa kluczowe, takie jak <code>Function</code>, <code>Sub</code>, <code>Then</code> czy <code>End If</code>. Dla mnie to jedna z najwi&#281;kszych zalet tego j&#281;zyka podczas utrzymywania starszych system&oacute;w. Intencj&#281; kodu da si&#281; zwykle odczyta&#263; bez znajomo&#347;ci wielu skr&oacute;t&oacute;w sk&#322;adniowych.</p><pre><code>Option Strict On
Option Explicit On

Module Program
    Sub Main()
        Dim price As Decimal = 129.99D
        Dim quantity As Integer = 2
        Dim total As Decimal = price * quantity

        Console.WriteLine($"Razem: {total:C}")
    End Sub
End Module</code></pre><p><strong>Option Explicit On</strong> wymusza deklarowanie zmiennych, a <strong>Option Strict On</strong> ogranicza ryzykowne konwersje i p&oacute;&#378;ne wi&#261;zanie. W ma&#322;ym przyk&#322;adzie nie robi to wielkiej r&oacute;&#380;nicy, ale w aplikacji biznesowej potrafi uchroni&#263; przed b&#322;&#281;dem ujawniaj&#261;cym si&#281; dopiero podczas dzia&#322;ania programu.</p><h2 id="nie-myl-jezyka-net-z-vba">Nie myl j&#281;zyka .NET z VBA</h2><p>Nazwa bywa &#378;r&oacute;d&#322;em nieporozumie&#324;, poniewa&#380; wiele os&oacute;b kojarzy j&#261; przede wszystkim z makrami w Excelu. <strong>VBA</strong> s&#322;u&#380;y do automatyzowania aplikacji pakietu Office, natomiast odmiana dzia&#322;aj&#261;ca z .NET tworzy samodzielne aplikacje, biblioteki i programy korzystaj&#261;ce z ca&#322;ego &#347;rodowiska uruchomieniowego.</p><p>Oba j&#281;zyki maj&#261; podobne korzenie i cz&#281;&#347;&#263; s&#322;&oacute;w kluczowych, ale nie s&#261; zamienne. Projekt .NET mo&#380;e odwo&#322;ywa&#263; si&#281; do bibliotek, korzysta&#263; z typ&oacute;w generycznych, asynchroniczno&#347;ci i pakiet&oacute;w NuGet. Makro VBA dzia&#322;a wewn&#261;trz programu gospodarza, na przyk&#322;ad Excela, i ma zupe&#322;nie inny model wdra&#380;ania.</p><p>Je&#380;eli celem jest automatyzacja arkusza, VBA mo&#380;e by&#263; wystarczaj&#261;ce. Gdy tworz&#281; aplikacj&#281; z interfejsem, testami, baz&#261; danych, logowaniem i osobnym procesem wdra&#380;ania, wybieram projekt .NET napisany w Visual Basic albo C#.</p><h2 id="gdzie-ten-jezyk-nadal-sprawdza-sie-najlepiej">Gdzie ten j&#281;zyk nadal sprawdza si&#281; najlepiej</h2><p>Najmocniejsz&#261; stron&#261; tego j&#281;zyka pozostaj&#261; aplikacje desktopowe dla Windows. W szczeg&oacute;lno&#347;ci dotyczy to system&oacute;w wewn&#281;trznych firm, program&oacute;w magazynowych, narz&#281;dzi produkcyjnych i aplikacji administracyjnych, kt&oacute;re przez lata rozbudowywano bez przepisywania ca&#322;ego rozwi&#261;zania.</p><h3 id="windows-forms">Windows Forms</h3><p><strong>Windows Forms</strong>, cz&#281;sto skracane do WinForms, pozwala budowa&#263; interfejsy z&#322;o&#380;one z formularzy i kontrolek. Projektant w Visual Studio u&#322;atwia rozmieszczanie przycisk&oacute;w, p&oacute;l tekstowych czy tabel, co nadal ma du&#380;&#261; warto&#347;&#263; przy prostych aplikacjach dla konkretnego dzia&#322;u firmy.</p><p>Nie traktowa&#322;bym jednak projektanta jako zamiennika architektury. Nawet ma&#322;y program powinien oddziela&#263; obs&#322;ug&#281; interfejsu od logiki biznesowej, dost&#281;pu do danych i komunikacji z us&#322;ugami. W przeciwnym razie szybki prototyp zamienia si&#281; w trudny do testowania plik z setkami procedur obs&#322;ugi zdarze&#324;.</p><h3 id="wpf-i-biblioteki-wspoldzielone">WPF i biblioteki wsp&oacute;&#322;dzielone</h3><p><strong>WPF</strong> daje wi&#281;ksze mo&#380;liwo&#347;ci budowania interfejs&oacute;w, wi&#261;zania danych i rozdzielenia widoku od logiki. Nadaje si&#281; lepiej do rozbudowanych aplikacji desktopowych, cho&#263; wymaga opanowania XAML oraz wzorc&oacute;w takich jak MVVM.</p><p>J&#281;zyk mo&#380;na te&#380; wykorzysta&#263; do tworzenia bibliotek klas, narz&#281;dzi konsolowych i us&#322;ug pomocniczych. Wsp&oacute;&#322;dzielenie biblioteki z kodem C# jest mo&#380;liwe, bo oba j&#281;zyki kompiluj&#261; si&#281; do tego samego &#347;rodowiska .NET. Trzeba tylko uwa&#380;a&#263; na elementy sk&#322;adni, kt&oacute;re nie maj&#261; bezpo&#347;redniego odpowiednika po drugiej stronie.</p><h3 id="kiedy-wybrac-c">Kiedy wybra&#263; C#</h3><p>Do nowego projektu webowego, API, aplikacji chmurowej, Blazora, .NET MAUI albo rozwi&#261;zania intensywnie korzystaj&#261;cego z najnowszych funkcji platformy wybra&#322;bym <strong>C#</strong>. Nie dlatego, &#380;e drugi j&#281;zyk przestaje dzia&#322;a&#263;, lecz dlatego, &#380;e C# ma wi&#281;kszy ekosystem przyk&#322;ad&oacute;w, bibliotek, szkole&#324; i gotowych integracji.</p><p>Microsoft nadal utrzymuje kompatybilno&#347;&#263; oraz narz&#281;dzia dla Visual Basic, ale strategia rozwoju nie zak&#322;ada kopiowania ka&#380;dej nowej funkcji C#. Dla istniej&#261;cego systemu to zwykle dobra wiadomo&#347;&#263;, bo stabilno&#347;&#263; bywa wa&#380;niejsza ni&#380; coroczne zmiany sk&#322;adni. Dla nowej aplikacji oznacza jednak mniejszy wyb&oacute;r i wi&#281;ksz&#261; szans&#281;, &#380;e przyk&#322;ad z dokumentacji b&#281;dzie dost&#281;pny wy&#322;&#261;cznie w C#.</p><h2 id="visual-basic-kontra-c-w-codziennej-pracy">Visual Basic kontra C# w codziennej pracy</h2><p>Oba j&#281;zyki korzystaj&#261; z .NET, wi&#281;c r&oacute;&#380;nice dotycz&#261; przede wszystkim sposobu zapisu, narz&#281;dzi i dost&#281;pno&#347;ci materia&#322;&oacute;w. Poni&#380;sze zestawienie pomaga szybko oceni&#263;, kt&oacute;ry wyb&oacute;r b&#281;dzie rozs&#261;dniejszy dla konkretnego projektu.</p><table>
  <tbody>
    <tr>
      <th>Kryterium</th>
      <th>Visual Basic</th>
      <th>C#</th>
    </tr>
    <tr>
      <td>Czytelno&#347;&#263; dla pocz&#261;tkuj&#261;cych</td>
      <td>&#321;agodny start dzi&#281;ki bardziej opisowej sk&#322;adni</td>
      <td>Wymaga szybszego oswojenia z nawiasami i operatorami</td>
    </tr>
    <tr>
      <td>Windows Forms i WPF</td>
      <td>Bardzo dobry wyb&oacute;r, szczeg&oacute;lnie przy istniej&#261;cych systemach</td>
      <td>Bardzo dobry wyb&oacute;r i cz&#281;&#347;ciej spotykany w nowych projektach</td>
    </tr>
    <tr>
      <td>Web, chmura i nowe us&#322;ugi .NET</td>
      <td>Mo&#380;liwy, ale z mniejsz&#261; liczb&#261; przyk&#322;ad&oacute;w i szablon&oacute;w</td>
      <td>Najszersze wsparcie ekosystemu</td>
    </tr>
    <tr>
      <td>Rynek pracy</td>
      <td>Najcz&#281;&#347;ciej utrzymanie i rozw&oacute;j system&oacute;w firmowych</td>
      <td>Du&#380;o ofert dla nowych aplikacji i us&#322;ug</td>
    </tr>
    <tr>
      <td>Wsp&oacute;&#322;praca z kodem .NET</td>
      <td>Pe&#322;ny dost&#281;p do bibliotek platformy</td>
      <td>Pe&#322;ny dost&#281;p do bibliotek platformy</td>
    </tr>
  </tbody>
</table><p>Ten sam fragment logiki mo&#380;e wygl&#261;da&#263; inaczej, cho&#263; wykonuje identyczn&#261; operacj&#281;. W Visual Basic zapis u&#380;ywa s&#322;owa <code>Function</code>, a w C# kr&oacute;tszej lambdy.</p><pre><code>' Visual Basic
Dim numbers = {2, 4, 6, 8}
Dim evenSum = numbers.
    Where(Function(number) number Mod 2 = 0).
    Sum()

' C#
var numbers = new[] { 2, 4, 6, 8 };
var evenSum = numbers
    .Where(number =&gt; number % 2 == 0)
    .Sum();</code></pre><p>W codziennej pracy r&oacute;&#380;nica nie sprowadza si&#281; do d&#322;ugo&#347;ci kodu. <strong>Dokumentacja i spo&#322;eczno&#347;&#263;</strong> maj&#261; ogromny wp&#322;yw na tempo rozwi&#261;zywania problem&oacute;w, dlatego przy nowych technologiach C# cz&#281;sto pozwala szybciej znale&#378;&#263; aktualny przyk&#322;ad.</p><h2 id="jak-zaczac-prace-z-tym-jezykiem-w-2026-roku">Jak zacz&#261;&#263; prac&#281; z tym j&#281;zykiem w 2026 roku</h2><p>Na pocz&#261;tek wybra&#322;bym <strong>Visual Studio</strong> z obci&#261;&#380;eniem zwi&#261;zanym z tworzeniem aplikacji .NET. Dla prostego &#263;wiczenia wystarczy projekt konsolowy, ale przy pracy z formularzami trzeba zaznaczy&#263; komponenty Windows Forms lub WPF.</p><ol>
  <li>Utw&oacute;rz projekt konsolowy, biblioteki klas albo aplikacji Windows Forms.</li>
  <li>Ustaw docelow&#261; wersj&#281; .NET zgodn&#261; ze &#347;rodowiskiem wdro&#380;eniowym.</li>
  <li>Dodaj na pocz&#261;tku pliku <code>Option Strict On</code> i <code>Option Explicit On</code>.</li>
  <li>Podziel kod na warstw&#281; interfejsu, logiki i dost&#281;pu do danych.</li>
  <li>Dodaj testy dla oblicze&#324; i regu&#322; biznesowych, zanim rozbudujesz interfejs.</li>
</ol><p>W 2026 roku nowe rozwi&#261;zanie warto planowa&#263; na <strong>.NET 10 LTS</strong>, je&#347;li nie istnieje ograniczenie infrastruktury lub biblioteki. .NET 8 i .NET 9 ko&#324;cz&#261; wsparcie 10 listopada 2026 roku, wi&#281;c rozpocz&#281;cie projektu na jednej z tych wersji wymaga &#347;wiadomego planu aktualizacji.</p><p class="read-more"><strong>Przeczytaj r&oacute;wnie&#380;: <a href="https://kursdotnet.pl/jak-definiowac-elementy-w-c-klasy-metody-i-define">Jak definiowa&#263; elementy w C#? Klasy, metody i #define</a></strong></p><h3 id="pulapki-ktore-widze-najczesciej">Pu&#322;apki, kt&oacute;re widz&#281; najcz&#281;&#347;ciej</h3><ul>
  <li>
<strong>Niejawne konwersje</strong> mi&#281;dzy tekstem, liczb&#261; i dat&#261;, kt&oacute;re dzia&#322;aj&#261; dla jednych danych, a psuj&#261; si&#281; dla innych.</li>
  <li>
<strong>Nadu&#380;ywanie modu&#322;&oacute;w</strong> i zmiennych globalnych zamiast klas z jasno okre&#347;lon&#261; odpowiedzialno&#347;ci&#261;.</li>
  <li>
<strong>&#321;&#261;czenie zapyta&#324; SQL</strong> przez konkatenacj&#281; tekstu, co zwi&#281;ksza ryzyko b&#322;&#281;d&oacute;w i atak&oacute;w SQL injection.</li>
  <li>
<strong>Blokowanie w&#261;tku interfejsu</strong> podczas d&#322;ugiej operacji, przez co aplikacja wygl&#261;da na zawieszon&#261;.</li>
  <li>
<strong>Traktowanie migracji jako prostego przepisywania sk&#322;adni</strong>, mimo &#380;e stary projekt mo&#380;e zale&#380;e&#263; od nieaktualnych bibliotek i ustawie&#324;.</li>
</ul><p>Najwi&#281;ksz&#261; popraw&#281; jako&#347;ci zwykle daje nie zmiana j&#281;zyka, ale konsekwentne typowanie, testy i podzia&#322; odpowiedzialno&#347;ci. Przepisanie aplikacji z Visual Basic do C# bez uporz&#261;dkowania architektury cz&#281;sto tylko przenosi stare problemy do nowego projektu.</p><h2 id="czy-warto-uczyc-sie-go-od-zera">Czy warto uczy&#263; si&#281; go od zera</h2><p>Je&#380;eli uczysz si&#281; programowania z my&#347;l&#261; o <strong>nowych aplikacjach .NET</strong>, zacz&#261;&#322;bym od C#. &#321;atwiej b&#281;dzie p&oacute;&#378;niej korzysta&#263; z dokumentacji, przyk&#322;ad&oacute;w dotycz&#261;cych Azure, ASP.NET Core, kontener&oacute;w i narz&#281;dzi AI, kt&oacute;re najcz&#281;&#347;ciej pokazuj&#261; w&#322;a&#347;nie ten j&#281;zyk.</p><p>Inaczej wygl&#261;da sytuacja, gdy masz konkretny cel. Do utrzymania systemu WinForms, automatyzacji proces&oacute;w w firmie albo pracy z istniej&#261;cym kodem nauka Visual Basic mo&#380;e by&#263; bardzo praktyczna. Nie musisz opanowa&#263; ca&#322;ej historii j&#281;zyka. Najpierw poznaj typy, klasy, interfejsy, <a href="https://kursdotnet.pl/generyki-w-c-i-net-jak-pisac-bezpieczniejszy-kod">kolekcje</a>, LINQ, obs&#322;ug&#281; wyj&#261;tk&oacute;w i operacje asynchroniczne.</p><p>Dobrym &#263;wiczeniem jest ma&#322;a aplikacja do ewidencji zada&#324;. Powinna zapisywa&#263; dane, filtrowa&#263; je przez LINQ i wykonywa&#263; operacje plikowe bez blokowania interfejsu. Taki projekt uczy wi&#281;cej ni&#380; seria oderwanych przyk&#322;ad&oacute;w, bo pokazuje, jak sk&#322;adnia &#322;&#261;czy si&#281; z bibliotekami .NET.</p><h2 id="najrozsadniejsza-strategia-dla-istniejacego-projektu">Najrozs&#261;dniejsza strategia dla istniej&#261;cego projektu</h2><p>Nie podejmowa&#322;bym decyzji o migracji tylko dlatego, &#380;e C# jest obecnie popularniejszy. Najpierw sprawdzi&#322;bym wiek projektu, u&#380;ywane biblioteki, liczb&#281; u&#380;ytkownik&oacute;w, dost&#281;pno&#347;&#263; programist&oacute;w i koszt test&oacute;w regresji. Je&#347;li aplikacja dzia&#322;a stabilnie, a zesp&oacute;&#322; dobrze zna obecny kod, <strong>modernizacja bez zmiany j&#281;zyka</strong> mo&#380;e by&#263; ta&#324;sza i bezpieczniejsza.</p><p>Zmian&#281; na C# rozwa&#380;y&#322;bym przy du&#380;ej przebudowie, wej&#347;ciu w ASP.NET Core lub Azure, problemach z rekrutacj&#261; oraz potrzebie korzystania z bibliotek publikowanych g&#322;&oacute;wnie dla C#. Najlepsza &#347;cie&#380;ka cz&#281;sto jest mieszana. Mo&#380;na zostawi&#263; sprawdzon&#261; warstw&#281; desktopow&#261;, a nowe biblioteki lub us&#322;ugi pisa&#263; w C# i wsp&oacute;&#322;dzieli&#263; kontrakty przez .NET.</p><p>Dla mnie ten j&#281;zyk nie jest dzi&#347; uniwersalnym wyborem do ka&#380;dego rodzaju oprogramowania, ale nadal jest u&#380;ytecznym narz&#281;dziem w konkretnym miejscu. Gdy liczy si&#281; utrzymanie aplikacji Windows i czytelno&#347;&#263; kodu, nie skre&#347;la&#322;bym go tylko z powodu wieku. Przy nowym produkcie webowym lub chmurowym postawi&#322;bym jednak na C#, bo ograniczy to tarcie na ka&#380;dym kolejnym etapie rozwoju.</p>]]></content:encoded>
      <author>Bruno Krawczyk</author>
      <category>C# i .NET</category>
      <media:thumbnail url="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/thumbnail/3433f28cdb5470b394d9d5d49d85ac9a/visual-basic-w-net-kiedy-wybrac-go-zamiast-c.webp"/>
      <pubDate>Fri, 11 Sep 2026 15:22:00 +0200</pubDate>
    </item>
    <item>
      <title>CQRS w .NET - kiedy rozdzielenie komend i zapytań ma sens?</title>
      <link>https://kursdotnet.pl/cqrs-w-net-kiedy-rozdzielenie-komend-i-zapytan-ma-sens</link>
      <description>CQRS w .NET: poznaj warianty, korzyści i koszty oraz sprawdź, kiedy rozdzielenie komend i zapytań ma sens.</description>
      <content:encoded><![CDATA[<?xml encoding="utf-8" ?><p>Gdy aplikacja ma coraz wi&#281;cej regu&#322; biznesowych, ten sam model danych zaczyna jednocze&#347;nie obs&#322;ugiwa&#263; skomplikowane zapisy i dziesi&#261;tki prostych odczyt&oacute;w. W&#322;a&#347;nie wtedy rozdzielenie komend od zapyta&#324; mo&#380;e uporz&#261;dkowa&#263; kod, poprawi&#263; wydajno&#347;&#263; i u&#322;atwi&#263; rozw&oacute;j, ale tylko pod warunkiem, &#380;e problem rzeczywi&#347;cie tego wymaga. Poka&#380;&#281;, jak dzia&#322;a CQRS w aplikacjach .NET, czym r&oacute;&#380;ni&#261; si&#281; jego warianty, kiedy ma sens oraz jakie koszty pojawiaj&#261; si&#281; po drodze.</p><div class="short-summary">
<h2 id="najwazniejsze-decyzje-dotyczace-cqrs-w-jednym-miejscu">Najwa&#380;niejsze decyzje dotycz&#261;ce CQRS w jednym miejscu</h2>
<ul>
<li>
<strong>Komendy</strong> zmieniaj&#261; stan systemu, a zapytania tylko odczytuj&#261; dane.</li>
<li>CQRS nie wymaga od razu <strong>dw&oacute;ch baz danych</strong> ani architektury mikroserwisowej.</li>
<li>Najwi&#281;ksz&#261; korzy&#347;&#263; daje przy <strong>z&#322;o&#380;onych regu&#322;ach biznesowych</strong> i du&#380;ej r&oacute;&#380;nicy mi&#281;dzy odczytem a zapisem.</li>
<li>Oddzielne modele odczytu mog&#261; zwi&#281;kszy&#263; wydajno&#347;&#263;, ale cz&#281;sto wprowadzaj&#261; <strong>op&oacute;&#378;nion&#261; sp&oacute;jno&#347;&#263;</strong>.</li>
<li>W prostym CRUD rozdzielenie odpowiedzialno&#347;ci zwykle oznacza <strong>wi&#281;cej kodu bez proporcjonalnej korzy&#347;ci</strong>.</li>
</ul>
</div><h2 id="na-czym-polega-rozdzielenie-komend-i-zapytan">Na czym polega rozdzielenie komend i zapyta&#324;</h2><p>Skr&oacute;t CQRS oznacza Command Query Responsibility Segregation, czyli rozdzielenie odpowiedzialno&#347;ci za operacje zmieniaj&#261;ce dane i operacje, kt&oacute;re je odczytuj&#261;. W polskich materia&#322;ach spotkasz te&#380; angielskie okre&#347;lenie <strong>cqrs pattern</strong>, ale sama nazwa jest mniej istotna ni&#380; praktyczna zasada: <strong>odczyt i zapis nie musz&#261; korzysta&#263; z tego samego modelu</strong>.</p><p>Komenda opisuje zamiar wykonania operacji, na przyk&#322;ad &bdquo;z&#322;&oacute;&#380; zam&oacute;wienie&rdquo;, &bdquo;anuluj p&#322;atno&#347;&#263;&rdquo; albo &bdquo;zmie&#324; adres dostawy&rdquo;. Handler komendy sprawdza regu&#322;y biznesowe, modyfikuje agregat i zapisuje wynik. Zapytanie ma znacznie prostsz&#261; rol&#281;. Pobiera dane potrzebne konkretnemu ekranowi, raportowi lub endpointowi i nie powinno zmienia&#263; stanu aplikacji.</p><p>W tradycyjnym CRUD jeden model encji cz&#281;sto obs&#322;uguje oba przypadki. Encja <code>Order</code> mo&#380;e by&#263; &#322;adowana przez formularz edycji, widok listy, raport administracyjny i proces p&#322;atno&#347;ci. Po pewnym czasie zaczyna zawiera&#263; pola oraz zale&#380;no&#347;ci potrzebne g&#322;&oacute;wnie do odczytu, a logika zapisu miesza si&#281; z projekcjami danych.</p><p>W CQRS model zapisu mo&#380;e pilnowa&#263; regu&#322; domenowych, a model odczytu mo&#380;e by&#263; p&#322;askim DTO przygotowanym dok&#322;adnie dla klienta. Nie chodzi wi&#281;c wy&#322;&#261;cznie o fizyczne bazy danych. Najwa&#380;niejszy jest <strong>podzia&#322; odpowiedzialno&#347;ci w kodzie</strong>.</p><p class="read-more"><strong>Przeczytaj r&oacute;wnie&#380;: <a href="https://kursdotnet.pl/var-w-javascripcie-zakres-hoisting-i-pulapki-starego-kodu">var w JavaScripcie - zakres, hoisting i pu&#322;apki starego kodu</a></strong></p><h3 id="command-nie-jest-zwykla-metoda-aktualizacji">Command nie jest zwyk&#322;&#261; metod&#261; aktualizacji</h3><p>Dobrze zaprojektowana komenda opisuje dzia&#322;anie biznesowe, a nie techniczny szczeg&oacute;&#322; SQL. <code>ChangeOrderStatusCommand</code> m&oacute;wi, co ma si&#281; wydarzy&#263;, natomiast handler decyduje, jak sprawdzi&#263; uprawnienia, przej&#347;cia status&oacute;w i warunki transakcji.</p><pre><code>public sealed record ConfirmOrderCommand(Guid OrderId);

public sealed class ConfirmOrderHandler
{
    public async Task Handle(
        ConfirmOrderCommand command,
        CancellationToken cancellationToken)
    {
        var order = await repository.Get(command.OrderId, cancellationToken);

        order.Confirm();
        await repository.Save(order, cancellationToken);
    }
}</code></pre><p>W praktyce cz&#281;sto u&#380;ywa si&#281; bibliotek po&#347;rednicz&#261;cych, takich jak MediatR, ale nie s&#261; one definicj&#261; CQRS. Mo&#380;na zbudowa&#263; ten styl bez mediatora, korzystaj&#261;c ze zwyk&#322;ych klas aplikacyjnych. <strong>Biblioteka porz&#261;dkuje przep&#322;yw</strong>, lecz nie zast&#261;pi dobrego modelu domeny ani sensownego podzia&#322;u odpowiedzialno&#347;ci.</p><h2 id="jak-wyglada-przeplyw-cqrs-w-aplikacji-net">Jak wygl&#261;da przep&#322;yw CQRS w aplikacji .NET</h2><p>Typowy przep&#322;yw zaczyna si&#281; w kontrolerze ASP.NET Core lub endpointcie Minimal API. Warstwa wej&#347;ciowa tworzy komend&#281; albo zapytanie i przekazuje je do odpowiedniego handlera. Handler komendy korzysta z modelu domenowego i transakcji, a handler zapytania pobiera gotowy model widoku.</p><p>Najprostszy uk&#322;ad mo&#380;na przedstawi&#263; tak:</p><ul>
<li>
<strong>Endpoint</strong> przyjmuje &#380;&#261;danie HTTP i wykonuje podstawow&#261; walidacj&#281;.</li>
<li>
<strong>Command handler</strong> uruchamia regu&#322;y biznesowe i zapisuje zmian&#281;.</li>
<li>
<strong>Query handler</strong> pobiera dane w formie dopasowanej do klienta.</li>
<li>
<strong>Model zapisu</strong> chroni sp&oacute;jno&#347;&#263; oraz regu&#322;y domenowe.</li>
<li>
<strong>Model odczytu</strong> zapewnia szybkie i wygodne projekcje.</li>
</ul><p>Przyk&#322;adowe zapytanie nie musi &#322;adowa&#263; ca&#322;ej encji ani uruchamia&#263; mechanizmu &#347;ledzenia zmian <a href="https://kursdotnet.pl/architektura-cebulowa-w-net-jak-poukladac-warstwy">Entity Framework Core</a>. Dla listy zam&oacute;wie&#324; cz&#281;sto lepiej wykona&#263; projekcj&#281; do DTO albo u&#380;y&#263; Dappera i pobra&#263; dok&#322;adnie te kolumny, kt&oacute;re s&#261; potrzebne.</p><pre><code>public sealed record GetCustomerOrdersQuery(Guid CustomerId);

public sealed record OrderListItem(
    Guid Id,
    string Number,
    string Status,
    decimal Total);

public sealed class GetCustomerOrdersHandler
{
    public Task<ireadonlylist>&gt; Handle(
        GetCustomerOrdersQuery query,
        CancellationToken cancellationToken)
    {
        return db.Orders
            .Where(x =&gt; x.CustomerId == query.CustomerId)
            .Select(x =&gt; new OrderListItem(
                x.Id,
                x.Number,
                x.Status,
                x.Total))
            .ToListAsync(cancellationToken);
    }
}</ireadonlylist></code></pre><p>Takie zapytanie jest <strong>odseparowane od modelu domenowego</strong>. Nie musi wiedzie&#263;, jak zam&oacute;wienie zmienia status ani jakie warunki obowi&#261;zuj&#261; przy jego zatwierdzaniu. Dzi&#281;ki temu kod odczytu jest prostszy, a model domenowy nie zostaje obci&#261;&#380;ony potrzebami ka&#380;dego ekranu w systemie.</p><p>Nie nale&#380;y jednak uto&#380;samia&#263; CQRS z endpointami HTTP POST i GET. Sam fakt, &#380;e aplikacja u&#380;ywa r&oacute;&#380;nych czasownik&oacute;w HTTP, nie oznacza jeszcze rozdzielenia modeli. CQRS dotyczy przede wszystkim <strong>granicy mi&#281;dzy operacj&#261; zmieniaj&#261;c&#261; stan a operacj&#261; odczytuj&#261;c&#261; stan</strong>.</p><h2 id="trzy-warianty-rozdzielenia-ktore-warto-odroznic">Trzy warianty rozdzielenia, kt&oacute;re warto odr&oacute;&#380;ni&#263;</h2><p>Najcz&#281;stszy b&#322;&#261;d polega na za&#322;o&#380;eniu, &#380;e CQRS zawsze oznacza dwie aplikacje, dwie bazy danych i komunikacj&#281; przez broker. To jeden z mo&#380;liwych wariant&oacute;w, ale nie punkt wyj&#347;cia. W wielu projektach rozs&#261;dniej zacz&#261;&#263; od rozdzielenia kodu przy zachowaniu wsp&oacute;lnego magazynu danych.</p><table>
<tbody>
<tr>
<th>Wariant</th>
<th>Jak dzia&#322;a</th>
<th>Kiedy ma sens</th>
</tr>
<tr>
<td>Logiczne CQRS</td>
<td>Komendy i zapytania maj&#261; osobne klasy, ale korzystaj&#261; z jednej bazy.</td>
<td>Gdy chcesz uporz&#261;dkowa&#263; kod bez kosztownej zmiany infrastruktury.</td>
</tr>
<tr>
<td>Rozdzielone modele</td>
<td>Model zapisu i odczytu maj&#261; r&oacute;&#380;ne struktury, lecz mog&#261; dzia&#322;a&#263; na tym samym silniku bazy.</td>
<td>Gdy widoki wymagaj&#261; wielu projekcji, a zapis ma z&#322;o&#380;one regu&#322;y.</td>
</tr>
<tr>
<td>Pe&#322;ne CQRS</td>
<td>Odczyt i zapis u&#380;ywaj&#261; oddzielnych magazyn&oacute;w, cz&#281;sto synchronizowanych zdarzeniami.</td>
<td>Gdy potrzebujesz niezale&#380;nego skalowania, r&oacute;&#380;nych technologii lub du&#380;ej wydajno&#347;ci odczytu.</td>
</tr>
</tbody>
</table><p>W pierwszym wariancie mo&#380;esz u&#380;y&#263; Entity Framework Core po stronie zapisu oraz osobnych zapyta&#324; SQL po stronie odczytu. To cz&#281;sto najlepszy kompromis dla monolitu. Zesp&oacute;&#322; zyskuje czytelny podzia&#322;, ale nie musi od razu rozwi&#261;zywa&#263; problem&oacute;w replikacji, kolejek i odbudowy projekcji.</p><p>Oddzielne bazy danych przydaj&#261; si&#281; wtedy, gdy charakter ruchu jest naprawd&#281; r&oacute;&#380;ny. Przyk&#322;adowo system mo&#380;e przyjmowa&#263; niewiele polece&#324; zmieniaj&#261;cych dane, ale obs&#322;ugiwa&#263; tysi&#261;ce odczyt&oacute;w katalogu lub panelu analitycznego. Wtedy model odczytu mo&#380;na zoptymalizowa&#263; pod wyszukiwanie, <a href="https://kursdotnet.pl/pamiec-podreczna-in-memory-w-net-od-imemorycache-do-redis">cache</a> albo konkretny typ raportu.</p><p>Trzeba zaakceptowa&#263; konsekwencj&#281; takiej architektury. Je&#380;eli zapis publikuje zdarzenie, a osobny proces aktualizuje projekcj&#281;, u&#380;ytkownik mo&#380;e przez kr&oacute;tki czas zobaczy&#263; <strong>stary stan danych</strong>. To tak zwana eventual consistency, czyli sp&oacute;jno&#347;&#263; osi&#261;gana z op&oacute;&#378;nieniem.</p><h2 id="kiedy-cqrs-daje-realna-przewage">Kiedy CQRS daje realn&#261; przewag&#281;</h2><p>Najlepiej sprawdza si&#281; tam, gdzie zapis i odczyt maj&#261; wyra&#378;nie inne potrzeby. Dobrym przyk&#322;adem jest system zam&oacute;wie&#324;. Z&#322;o&#380;enie zam&oacute;wienia mo&#380;e wymaga&#263; sprawdzenia ceny, dost&#281;pno&#347;ci, rabat&oacute;w, limit&oacute;w klienta i statusu p&#322;atno&#347;ci, podczas gdy ekran historii zam&oacute;wie&#324; potrzebuje tylko kilku gotowych p&oacute;l.</p><p>Podobnie wygl&#261;da sytuacja w systemie finansowym, platformie logistycznej albo aplikacji z rozbudowanym procesem akceptacji. <strong>Im wi&#281;cej regu&#322; i wyj&#261;tk&oacute;w po stronie zapisu</strong>, tym wi&#281;ksz&#261; warto&#347;&#263; daje model, kt&oacute;ry nie miesza ich z kodem raportowym.</p><p>CQRS mo&#380;e by&#263; dobrym wyborem, gdy spe&#322;nione s&#261; przynajmniej niekt&oacute;re z poni&#380;szych warunk&oacute;w:</p><ul>
<li>odczyt&oacute;w jest znacznie wi&#281;cej ni&#380; operacji zmieniaj&#261;cych dane,</li>
<li>r&oacute;&#380;ne ekrany potrzebuj&#261; zupe&#322;nie innych kszta&#322;t&oacute;w danych,</li>
<li>model domenowy zawiera wa&#380;ne regu&#322;y i nie powinien by&#263; u&#380;ywany jako DTO,</li>
<li>konieczne jest niezale&#380;ne skalowanie zapisu i odczytu,</li>
<li>raportowanie obci&#261;&#380;a baz&#281; transakcyjn&#261;,</li>
<li>zdarzenia biznesowe maj&#261; znaczenie dla innych proces&oacute;w.</li>
</ul><p>Moim zdaniem szczeg&oacute;lnie istotny jest ostatni punkt. Je&#380;eli po zmianie statusu zam&oacute;wienia trzeba zaktualizowa&#263; magazyn, wys&#322;a&#263; wiadomo&#347;&#263; i przeliczy&#263; raport, zdarzenie domenowe mo&#380;e uporz&#261;dkowa&#263; te reakcje. Nie oznacza to jednak automatycznie konieczno&#347;ci wdra&#380;ania Event Sourcingu. <strong>CQRS i Event Sourcing to r&oacute;&#380;ne wzorce</strong>. Pierwszy rozdziela odczyt i zapis, drugi przechowuje histori&#281; zmian jako strumie&#324; zdarze&#324;.</p><h2 id="koszty-i-pulapki-o-ktorych-latwo-zapomniec">Koszty i pu&#322;apki, o kt&oacute;rych &#322;atwo zapomnie&#263;</h2><p>Rozdzielenie modeli zwi&#281;ksza liczb&#281; element&oacute;w, kt&oacute;re trzeba utrzymywa&#263;. Pojawiaj&#261; si&#281; osobne komendy, zapytania, <a href="https://kursdotnet.pl/mediator-w-c-bez-chaosu-handlery-pipeline-i-dobre-praktyki">handlery</a>, DTO, mapowania, testy i czasem procesy synchronizuj&#261;ce. Przy prostym formularzu administracyjnym taki narzut mo&#380;e by&#263; wi&#281;kszy ni&#380; problem, kt&oacute;ry pr&oacute;bujesz rozwi&#261;za&#263;.</p><p>Najpowa&#380;niejszym ryzykiem jest pozorna architektura. Zesp&oacute;&#322; tworzy klasy <code>CreateProductCommand</code> i <code>GetProductQuery</code>, ale obie wywo&#322;uj&#261; ten sam serwis CRUD, pracuj&#261; na tej samej encji i nie daj&#261; &#380;adnej niezale&#380;no&#347;ci. Samo nazwanie klas komendami nie przynosi korzy&#347;ci. <strong>Podzia&#322; powinien chroni&#263; r&oacute;&#380;ne odpowiedzialno&#347;ci</strong>, a nie tylko zmienia&#263; nazwy plik&oacute;w.</p><p>Przy osobnych modelach odczytu trzeba dodatkowo obs&#322;u&#380;y&#263;:</p><ul>
<li>
<strong>op&oacute;&#378;nienia</strong> w propagacji zmian,</li>
<li>ponowne przetwarzanie zdarze&#324; bez duplikowania skutk&oacute;w,</li>
<li>kolejno&#347;&#263; zdarze&#324; i konflikty aktualizacji,</li>
<li>odbudow&#281; projekcji po zmianie jej struktury,</li>
<li>monitorowanie kolejek, b&#322;&#281;d&oacute;w i nieprzetworzonych komunikat&oacute;w.</li>
</ul><p>W praktyce szczeg&oacute;lnie cz&#281;sto pomija si&#281; idempotencj&#281;. Handler idempotentny mo&#380;e bezpiecznie obs&#322;u&#380;y&#263; t&#281; sam&#261; wiadomo&#347;&#263; wi&#281;cej ni&#380; raz. Bez tego ponowiona wiadomo&#347;&#263; mo&#380;e dwukrotnie naliczy&#263; op&#322;at&#281;, wys&#322;a&#263; podw&oacute;jne powiadomienie albo utworzy&#263; dwa identyczne rekordy.</p><p>Nie wprowadza&#322;bym pe&#322;nego CQRS do prostego katalogu produkt&oacute;w, panelu s&#322;ownik&oacute;w czy niewielkiej aplikacji z kilkoma tabelami. W takich miejscach klasyczny CRUD z dobrze rozdzielonymi warstwami jest zwykle <strong>ta&#324;szy, prostszy i wystarczaj&#261;co skalowalny</strong>.</p><h2 id="jak-wdrazac-cqrs-bez-przebudowy-calego-systemu">Jak wdra&#380;a&#263; CQRS bez przebudowy ca&#322;ego systemu</h2><p>Najbezpieczniej zacz&#261;&#263; od jednego fragmentu domeny, w kt&oacute;rym problem jest widoczny. Nie przenosi&#322;bym ca&#322;ej aplikacji do CQRS tylko dlatego, &#380;e wzorzec dobrze wygl&#261;da na diagramie. Wybierz proces z trudnymi regu&#322;ami zapisu albo odczytem, kt&oacute;ry wymaga kosztownych zapyta&#324; i wielu wyj&#261;tk&oacute;w.</p><ol>
<li>
<strong>Oddziel operacje zapisu od odczytu</strong> na poziomie klas i interfejs&oacute;w.</li>
<li>Zdefiniuj komendy j&#281;zykiem biznesowym, zamiast kopiowa&#263; nazwy tabel.</li>
<li>Przenie&#347; regu&#322;y zmian do agregatu lub serwisu domenowego.</li>
<li>Utw&oacute;rz osobne DTO dla najwa&#380;niejszych widok&oacute;w.</li>
<li>Zoptymalizuj zapytania niezale&#380;nie od modelu zapisu.</li>
<li>Dopiero p&oacute;&#378;niej oce&#324;, czy potrzebujesz osobnej bazy lub komunikacji asynchronicznej.</li>
</ol><p>Przy takim podej&#347;ciu mo&#380;esz zachowa&#263; jedn&#261; transakcj&#281; i jeden magazyn danych, a mimo to uzyska&#263; wi&#281;kszo&#347;&#263; korzy&#347;ci organizacyjnych. Dopiero pomiary poka&#380;&#261;, czy problemem jest baza transakcyjna, koszt projekcji, czas odpowiedzi, czy mo&#380;e &#378;le zaprojektowana logika biznesowa.</p><p>W systemie .NET zacz&#261;&#322;bym od prostego podzia&#322;u katalog&oacute;w, na przyk&#322;ad <code>Features/Orders/Commands</code> oraz <code>Features/Orders/Queries</code>. Taki uk&#322;ad jest czytelniejszy ni&#380; globalne foldery <code>Services</code> i <code>Repositories</code>, w kt&oacute;rych po kilku miesi&#261;cach trudno znale&#378;&#263; kod odpowiedzialny za konkretny przypadek u&#380;ycia.</p><p>Dobrze jest te&#380; mierzy&#263; osobno czas obs&#322;ugi komend i zapyta&#324;, liczb&#281; b&#322;&#281;d&oacute;w oraz op&oacute;&#378;nienie synchronizacji projekcji. Bez tych danych &#322;atwo uzna&#263;, &#380;e CQRS poprawi&#322; system, cho&#263; w rzeczywisto&#347;ci tylko zwi&#281;kszy&#322; liczb&#281; abstrakcji.</p><h2 id="najrozsadniejszy-punkt-startu-dla-projektu-net">Najrozs&#261;dniejszy punkt startu dla projektu .NET</h2><p>Je&#380;eli aplikacja ma prost&#261; domen&#281; i g&#322;&oacute;wnie wykonuje operacje CRUD, zacznij od czytelnego kodu i dobrych zapyta&#324;. CQRS wprowad&#378; dopiero tam, gdzie <strong>r&oacute;&#380;nica mi&#281;dzy zapisem a odczytem jest praktycznym problemem</strong>, a nie jedynie ciekaw&#261; koncepcj&#261; architektoniczn&#261;.</p><p>W wi&#281;kszo&#347;ci zespo&#322;&oacute;w najlepsz&#261; pierwsz&#261; wersj&#261; b&#281;dzie lekki wariant z jednym magazynem danych, osobnymi command handlerami, query handlerami i DTO. Pozwala on sprawdzi&#263;, czy rozdzielenie faktycznie pomaga, bez dok&#322;adania od razu broker&oacute;w, replikacji i obs&#322;ugi op&oacute;&#378;nionej sp&oacute;jno&#347;ci.</p><p>Najwa&#380;niejsza decyzja nie brzmi wi&#281;c &bdquo;czy u&#380;y&#263; CQRS w ca&#322;ej aplikacji&rdquo;, lecz &bdquo;w kt&oacute;rym fragmencie systemu osobne modele rozwi&#261;&#380;&#261; konkretny problem&rdquo;. Takie podej&#347;cie ogranicza ryzyko, u&#322;atwia pomiary i zostawia drog&#281; do p&oacute;&#378;niejszego skalowania bez kosztownej rewolucji.</p>]]></content:encoded>
      <author>Bruno Krawczyk</author>
      <category>Architektura i wzorce</category>
      <media:thumbnail url="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/thumbnail/8f2742a77b6520f84f409245e07c9692/cqrs-w-net-kiedy-rozdzielenie-komend-i-zapytan-ma-sens.webp"/>
      <pubDate>Fri, 11 Sep 2026 11:08:00 +0200</pubDate>
    </item>
    <item>
      <title>Swagger co to i jak działa w ASP.NET Core?</title>
      <link>https://kursdotnet.pl/swagger-co-to-i-jak-dziala-w-aspnet-core</link>
      <description>Swagger co to? Poznaj OpenAPI, Swagger UI i konfigurację w ASP.NET Core. Sprawdź, jak dokumentować API i gdzie kończą się możliwości narzędzia.</description>
      <content:encoded><![CDATA[<?xml encoding="utf-8" ?><p>Gdy aplikacja webowa ma wi&#281;cej ni&#380; kilka endpoint&oacute;w, samo &bdquo;zapytaj programist&#281;&rdquo; szybko przestaje dzia&#322;a&#263;. Swagger porz&#261;dkuje opis API, pokazuje parametry i odpowiedzi, a w interaktywnym panelu pozwala wys&#322;a&#263; &#380;&#261;danie bez pisania osobnego klienta. Wyja&#347;niam, czym jest to narz&#281;dzie, jak &#322;&#261;czy si&#281; z OpenAPI, jak u&#380;ywa&#263; go w <a href="https://kursdotnet.pl/api-w-net-od-podstaw-od-endpointu-do-wdrozenia">ASP.NET Core</a> oraz gdzie ko&#324;cz&#261; si&#281; jego mo&#380;liwo&#347;ci.</p><div class="short-summary">
<h2 id="swagger-zamienia-opis-api-w-dokumentacje-z-ktorej-mozna-od-razu-korzystac">Swagger zamienia opis API w dokumentacj&#281;, z kt&oacute;rej mo&#380;na od razu korzysta&#263;</h2>
<ul>
<li>
<strong>Swagger</strong> to zestaw narz&#281;dzi do opisywania, dokumentowania i testowania API.</li>
<li>
<strong>OpenAPI</strong> jest standardem, na podstawie kt&oacute;rego powstaje opis endpoint&oacute;w.</li>
<li>
<strong>Swagger UI</strong> pokazuje dokumentacj&#281; w przegl&#261;darce i pozwala wykonywa&#263; &#380;&#261;dania.</li>
<li>W ASP.NET Core dokument OpenAPI mo&#380;na generowa&#263; wbudowanymi mechanizmami, ale interfejs wizualny trzeba doda&#263; osobno.</li>
<li>Swagger nie zast&#281;puje <strong>test&oacute;w automatycznych</strong> ani narz&#281;dzi do test&oacute;w obci&#261;&#380;eniowych i bezpiecze&#324;stwa.</li>
</ul>
</div><p><img src="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/post_image/3c2500d4515fe39524ef77eb78c4f7f7/swagger-ui-openapi-interactive-api-documentation-screen-example.webp" class="image article-image" loading="lazy" alt="Swagger co to? Interfejs API Petstore, kt&oacute;ry pozwala na zarz&#261;dzanie zwierz&#281;tami, dodawanie, aktualizacj&#281;, wyszukiwanie i usuwanie."></p><h2 id="czym-naprawde-jest-swagger-i-czym-rozni-sie-od-openapi">Czym naprawd&#281; jest Swagger i czym r&oacute;&#380;ni si&#281; od OpenAPI</h2><p>Najkr&oacute;cej m&oacute;wi&#261;c, <strong>Swagger pomaga opisa&#263; i udost&#281;pni&#263; API</strong> w formie zrozumia&#322;ej zar&oacute;wno dla cz&#322;owieka, jak i dla narz&#281;dzi programistycznych. Dzi&#281;ki niemu mo&#380;na szybko sprawdzi&#263;, jakie endpointy oferuje aplikacja, jakie przyjmuj&#261; parametry, jakie zwracaj&#261; dane i jak wygl&#261;da obs&#322;uga autoryzacji.</p><p>Tu &#322;atwo o drobne nieporozumienie. <strong>OpenAPI</strong> jest formalnym standardem opisu interfejs&oacute;w HTTP, natomiast Swagger to ekosystem narz&#281;dzi korzystaj&#261;cych z tego standardu. Dawniej nazw&#261; Swagger okre&#347;lano tak&#380;e sam&#261; specyfikacj&#281;, ale obecnie rozdzielenie tych poj&#281;&#263; jest znacznie bardziej precyzyjne.</p><table>
<tbody>
<tr>
<th>Element</th>
<th>Do czego s&#322;u&#380;y</th>
</tr>
<tr>
<td>OpenAPI</td>
<td>Opisuje struktur&#281; API w formacie JSON lub YAML.</td>
</tr>
<tr>
<td>Swagger UI</td>
<td>Wy&#347;wietla interaktywn&#261; dokumentacj&#281; w przegl&#261;darce.</td>
</tr>
<tr>
<td>Swagger Editor</td>
<td>Pozwala tworzy&#263; i edytowa&#263; dokument OpenAPI.</td>
</tr>
<tr>
<td>Swashbuckle</td>
<td>Integruje generowanie dokumentu i Swagger UI z aplikacj&#261; ASP.NET Core.</td>
</tr>
<tr>
<td>NSwag</td>
<td>Generuje dokumentacj&#281; oraz mo&#380;e tworzy&#263; klient&oacute;w API dla r&oacute;&#380;nych j&#281;zyk&oacute;w.</td>
</tr>
</tbody>
</table><p>W praktyce najcz&#281;&#347;ciej spotykam po&#322;&#261;czenie <strong>OpenAPI + Swagger UI</strong>. Pierwszy element opisuje kontrakt, a drugi daje wygodny ekran do jego przegl&#261;dania i wykonywania przyk&#322;adowych wywo&#322;a&#324;.</p><h2 id="jak-swagger-dziala-w-aplikacji-webowej">Jak Swagger dzia&#322;a w aplikacji webowej</h2><p>Mechanizm jest prostszy, ni&#380; sugeruje liczba nazw. Aplikacja udost&#281;pnia endpointy, framework zbiera informacje o ich trasach, parametrach, modelach i odpowiedziach, a nast&#281;pnie tworzy dokument OpenAPI. Swagger UI odczytuje ten dokument i buduje na jego podstawie stron&#281; dokumentacji.</p><h3 id="od-endpointu-do-dokumentacji">Od endpointu do dokumentacji</h3><ol>
<li>Programista tworzy endpoint, na przyk&#322;ad <code>GET /api/products</code>.</li>
<li>Framework rozpoznaje metod&#281; HTTP, tras&#281;, parametry i typ odpowiedzi.</li>
<li>Dodatkowe adnotacje lub konfiguracja uzupe&#322;niaj&#261; opis o kody status&oacute;w, autoryzacj&#281; i przyk&#322;ady.</li>
<li>Generator tworzy dokument OpenAPI w formacie JSON lub YAML.</li>
<li>Swagger UI prezentuje go jako interaktywn&#261; stron&#281;.</li>
</ol><p>Je&#380;eli endpoint zwraca list&#281; produkt&oacute;w, dokument powinien informowa&#263; nie tylko o samej trasie. Dobrze opisane API pokazuje tak&#380;e, czy filtr znajduje si&#281; w parametrze zapytania, jakie pola ma produkt i co oznacza odpowied&#378; <strong>200, 400 albo 404</strong>. To w&#322;a&#347;nie takie szczeg&oacute;&#322;y skracaj&#261; czas integracji mi&#281;dzy frontendem a backendem.</p><h3 id="code-first-i-design-first">Code-first i design-first</h3><p>

W podej&#347;ciu <strong>code-first</strong> najpierw tworz&#281; kontroler lub <a href="https://kursdotnet.pl/minimal-api-w-aspnet-core-kiedy-wybrac-je-zamiast-kontrolerow">Minimal API</a>, a dokumentacja powstaje na podstawie kodu i jego metadanych. To wygodne rozwi&#261;zanie dla wi&#281;kszo&#347;ci projekt&oacute;w .NET, szczeg&oacute;lnie gdy backend i dokumentacja rozwijaj&#261; si&#281; razem.

</p><p>W podej&#347;ciu <strong>design-first</strong> najpierw powstaje kontrakt OpenAPI, a dopiero p&oacute;&#378;niej implementacja. Ten wariant dobrze sprawdza si&#281; przy wi&#281;kszych zespo&#322;ach, pracy r&oacute;wnoleg&#322;ej frontendowc&oacute;w i backendowc&oacute;w oraz przy projektowaniu publicznego API. Wymaga jednak dyscypliny, bo kod musi p&oacute;&#378;niej rzeczywi&#347;cie realizowa&#263; ustalenia z dokumentu.</p><h2 id="co-daje-swagger-ui-w-codziennej-pracy">Co daje Swagger UI w codziennej pracy</h2><p>Najwi&#281;ksz&#261; zalet&#261; Swagger UI jest to, &#380;e dokumentacja nie ko&#324;czy si&#281; na statycznej li&#347;cie metod. Przy ka&#380;dym endpointcie mo&#380;na zobaczy&#263; <strong>parametry, schemat &#380;&#261;dania, mo&#380;liwe odpowiedzi i przyk&#322;adow&#261; sk&#322;adni&#281;</strong>, a po w&#322;&#261;czeniu opcji &bdquo;Try it out&rdquo; tak&#380;e wys&#322;a&#263; prawdziwe &#380;&#261;danie do dzia&#322;aj&#261;cej aplikacji.</p><p>To przyspiesza kilka typowych zada&#324;. Frontendowiec mo&#380;e sprawdzi&#263; format odpowiedzi bez zagl&#261;dania do kodu backendu, tester mo&#380;e szybko odtworzy&#263; prosty przypadek, a nowy cz&#322;onek zespo&#322;u &#322;atwiej poznaje projekt. Sam cz&#281;sto traktuj&#281; Swagger UI jako pierwszy punkt kontroli, gdy chc&#281; sprawdzi&#263;, czy API rzeczywi&#347;cie zachowuje si&#281; tak, jak zak&#322;adali&#347;my.</p><table>
<tbody>
<tr>
<th>Narz&#281;dzie</th>
<th>Najlepsze zastosowanie</th>
<th>Ograniczenie</th>
</tr>
<tr>
<td>Swagger UI</td>
<td>Szybkie poznanie endpoint&oacute;w i r&#281;czne wywo&#322;ania.</td>
<td>Nie zast&#281;puje pe&#322;nych test&oacute;w automatycznych.</td>
</tr>
<tr>
<td>Postman lub podobny klient</td>
<td>Budowanie kolekcji &#380;&#261;da&#324;, zmiennych &#347;rodowiskowych i scenariuszy.</td>
<td>Dokumentacja mo&#380;e si&#281; rozjecha&#263; z rzeczywistym API.</td>
</tr>
<tr>
<td>Testy integracyjne</td>
<td>Automatyczne sprawdzanie zachowania aplikacji przy ka&#380;dej zmianie.</td>
<td>Wymagaj&#261; przygotowania kodu i &#347;rodowiska testowego.</td>
</tr>
<tr>
<td>Surowy dokument OpenAPI</td>
<td>Generowanie klient&oacute;w, <a href="https://kursdotnet.pl/angular-formarray-bez-chaosu-dynamiczne-pola-i-walidacja">walidacja</a> kontraktu i integracja z innymi narz&#281;dziami.</td>
<td>Jest ma&#322;o wygodny do r&#281;cznego przegl&#261;dania.</td>
</tr>
</tbody>
</table><p>Istotne jest rozr&oacute;&#380;nienie mi&#281;dzy <strong>eksploracj&#261; API a testowaniem jako&#347;ci aplikacji</strong>. Swagger UI mo&#380;e pokaza&#263;, &#380;e endpoint odpowiada i zwraca dane, ale nie sprawdzi za Ciebie z&#322;o&#380;onych regu&#322; biznesowych, odporno&#347;ci na du&#380;y ruch ani podatno&#347;ci na ataki.</p><h2 id="jak-uruchomic-swagger-w-aspnet-core">Jak uruchomi&#263; Swagger w ASP.NET Core</h2><p>W nowszych wersjach ASP.NET Core pakiet <code>Microsoft.AspNetCore.OpenApi</code> zapewnia wbudowane generowanie dokumentu OpenAPI. Dokumentacja Microsoft Learn podkre&#347;la przy tym wa&#380;ne rozdzielenie: framework generuje opis API, ale wizualny interfejs, taki jak Swagger UI, trzeba doda&#263; za pomoc&#261; osobnego pakietu.</p><h3 id="wbudowane-generowanie-dokumentu">Wbudowane generowanie dokumentu</h3><p>Minimalna konfiguracja mo&#380;e wygl&#261;da&#263; tak:</p><pre><code>var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenApi();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapGet("/api/products", () =&gt;
{
    return Results.Ok(new[]
    {
        new { Id = 1, Name = "Klawiatura" }
    });
});

app.Run();</code></pre><p>W tym wariancie aplikacja udost&#281;pnia dokument OpenAPI, ale nie otrzymuje automatycznie gotowej strony z przyciskami do wykonywania &#380;&#261;da&#324;. To dobre rozwi&#261;zanie, gdy dokument b&#281;dzie konsumowany przez inne narz&#281;dzia albo gdy chcesz samodzielnie dobra&#263; interfejs, na przyk&#322;ad Swagger UI lub Scalar.</p><p class="read-more"><strong>Przeczytaj r&oacute;wnie&#380;: <a href="https://kursdotnet.pl/var-w-javascripcie-zakres-hoisting-i-pulapki-starego-kodu">var w JavaScripcie - zakres, hoisting i pu&#322;apki starego kodu</a></strong></p><h3 id="swagger-ui-przez-swashbuckle">Swagger UI przez Swashbuckle</h3><p>W wielu istniej&#261;cych projektach nadal spotkasz bibliotek&#281; <strong>Swashbuckle.AspNetCore</strong>. Typowa konfiguracja wygl&#261;da nast&#281;puj&#261;co:</p><pre><code>builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}</code></pre><p>Po uruchomieniu aplikacji interfejs jest zwykle dost&#281;pny pod &#347;cie&#380;k&#261; <code>/swagger</code>. Konkretna konfiguracja zale&#380;y od wersji pakietu, rodzaju API i tego, czy u&#380;ywasz kontroler&oacute;w, czy Minimal APIs. Najwa&#380;niejsze jest to, aby opisywa&#263; odpowiedzi i parametry &#347;wiadomie, zamiast liczy&#263; na automatyczne wykrycie ka&#380;dego szczeg&oacute;&#322;u.</p><p>Je&#347;li API ma by&#263; u&#380;ywane przez zewn&#281;trznych klient&oacute;w, warto generowa&#263; osobny dokument dla r&oacute;&#380;nych odbiorc&oacute;w lub wersji. <strong>Publiczne API i wewn&#281;trzne endpointy</strong> nie zawsze powinny pojawia&#263; si&#281; w tej samej dokumentacji.</p><h2 id="gdzie-swagger-nie-wystarczy-i-jakie-bledy-zdarzaja-sie-najczesciej">Gdzie Swagger nie wystarczy i jakie b&#322;&#281;dy zdarzaj&#261; si&#281; najcz&#281;&#347;ciej</h2><p>Swagger nie naprawia nieczytelnego API. Je&#380;eli endpointy maj&#261; przypadkowe nazwy, niesp&oacute;jne kody status&oacute;w albo zwracaj&#261; za ka&#380;dym razem inny kszta&#322;t danych, interaktywny panel tylko poka&#380;e ten problem w &#322;adniejszej formie.</p><ul>
<li>
<strong>Nieaktualny opis</strong> - dokumentacja nie zmienia si&#281; sama, je&#347;li cz&#281;&#347;&#263; zachowania jest ukryta w kodzie lub r&#281;cznej konfiguracji.</li>
<li>
<strong>Brak opis&oacute;w odpowiedzi</strong> - klient nie wie, czy spodziewa&#263; si&#281; b&#322;&#281;du walidacji, pustej odpowiedzi czy wyj&#261;tku serwera.</li>
<li>
<strong>Wystawienie dokumentacji publicznie</strong> - opis mo&#380;e ujawnia&#263; wewn&#281;trzne endpointy, modele danych i schemat autoryzacji.</li>
<li>
<strong>Mylenie testu r&#281;cznego z automatycznym</strong> - jedno udane klikni&#281;cie nie daje gwarancji, &#380;e API dzia&#322;a poprawnie w ka&#380;dym przypadku.</li>
<li>
<strong>Problemy z autoryzacj&#261; lub CORS</strong> - interfejs mo&#380;e dzia&#322;a&#263;, ale &#380;&#261;danie z przegl&#261;darki zostanie zablokowane albo pozbawione tokenu.</li>
</ul><p>W &#347;rodowisku produkcyjnym najcz&#281;&#347;ciej ograniczam Swagger UI do administrator&oacute;w, sieci wewn&#281;trznej albo konkretnego &#347;rodowiska. Dobr&#261; praktyk&#261; jest tak&#380;e rozdzielenie dokumentacji publicznej od wewn&#281;trznej oraz wy&#322;&#261;czenie przycisku wykonywania &#380;&#261;da&#324; tam, gdzie nie jest potrzebny.</p><p>Nie traktuj&#281; te&#380; Swaggera jako zamiennika test&oacute;w integracyjnych. Dokument OpenAPI mo&#380;e natomiast sta&#263; si&#281; ich podstaw&#261;, poniewa&#380; narz&#281;dzia potrafi&#261; wykorzysta&#263; go do walidacji kontraktu, generowania klient&oacute;w i sprawdzania zgodno&#347;ci implementacji z ustalonym interfejsem.</p><h2 id="kiedy-swagger-naprawde-poprawia-prace-nad-aplikacja">Kiedy Swagger naprawd&#281; poprawia prac&#281; nad aplikacj&#261;</h2><p>Swagger ma najwi&#281;kszy sens wtedy, gdy z API korzysta wi&#281;cej ni&#380; jedna osoba albo gdy backend komunikuje si&#281; z frontendem, aplikacj&#261; mobiln&#261; czy zewn&#281;trznym partnerem. Przy ma&#322;ym prototypie mo&#380;e wydawa&#263; si&#281; dodatkiem, ale koszt uruchomienia jest zwykle niewielki, a korzy&#347;&#263; ro&#347;nie wraz z liczb&#261; endpoint&oacute;w.</p><p>Moja praktyczna rekomendacja jest prosta. W&#322;&#261;cz dokument OpenAPI od pocz&#261;tku projektu, dodaj czytelne opisy parametr&oacute;w i odpowiedzi, a Swagger UI udost&#281;pniaj przede wszystkim w &#347;rodowisku deweloperskim i testowym. W produkcji zostaw tylko taki zakres, kt&oacute;ry rzeczywi&#347;cie musi by&#263; dost&#281;pny, i zabezpiecz go tak samo powa&#380;nie jak pozosta&#322;e elementy aplikacji.</p><p>Je&#380;eli poj&#281;cie &bdquo;swagger co to&rdquo; sprowadzi&#263; do jednego zdania, powiedzia&#322;bym tak: <strong>to wygodny spos&oacute;b na pokazanie, jak dzia&#322;a API, oraz szybkie sprawdzenie jego endpoint&oacute;w</strong>. Najwi&#281;ksz&#261; warto&#347;&#263; daje jednak dopiero wtedy, gdy dokumentacja jest aktualna, kontrakt sp&oacute;jny, a r&#281;czne wywo&#322;ania uzupe&#322;niaj&#261;, zamiast zast&#281;powa&#263;, prawdziwe testy.</p>]]></content:encoded>
      <author>Radosław Krajewski</author>
      <category>Aplikacje webowe</category>
      <media:thumbnail url="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/thumbnail/0c35e04258582293656ea0ce8e0c8b1c/swagger-co-to-i-jak-dziala-w-aspnet-core.webp"/>
      <pubDate>Thu, 10 Sep 2026 18:02:00 +0200</pubDate>
    </item>
    <item>
      <title>Podgląd zdarzeń Windows - jak czytać logi i znaleźć błąd</title>
      <link>https://kursdotnet.pl/podglad-zdarzen-windows-jak-czytac-logi-i-znalezc-blad</link>
      <description>Podgląd zdarzeń Windows pomoże znaleźć przyczynę awarii aplikacji i problemów systemu. Sprawdź filtrowanie, PowerShell i eksport logów.</description>
      <content:encoded><![CDATA[<?xml encoding="utf-8" ?><p>Komputer zaczyna si&#281; zawiesza&#263;, aplikacja .NET zamyka si&#281; bez komunikatu albo system d&#322;ugo uruchamia us&#322;ug&#281;? W takich sytuacjach dziennik zdarze&#324; Windows cz&#281;sto pokazuje, co wydarzy&#322;o si&#281; chwil&#281; wcze&#347;niej i kt&oacute;ry sk&#322;adnik by&#322; za to odpowiedzialny. Wyja&#347;niam, gdzie go znale&#378;&#263;, jak czyta&#263; wpisy, filtrowa&#263; zdarzenia oraz wykorzysta&#263; <a href="https://kursdotnet.pl/wsl-2-na-windows-11-instalacja-i-rozwiazanie-problemow">PowerShell</a> i .NET do sprawniejszej diagnostyki.</p><div class="short-summary">
  <h2 id="najwazniejsze-informacje-o-diagnostyce-zdarzen-w-windows">Najwa&#380;niejsze informacje o diagnostyce zdarze&#324; w Windows</h2>
  <ul>
    <li>
<strong>Podgl&#261;d zdarze&#324;</strong> uruchomisz poleceniem <code>eventvwr.msc</code> albo z menu Start.</li>
    <li>Najcz&#281;&#347;ciej analizuje si&#281; dzienniki <strong>Aplikacja</strong>, <strong>System</strong> i <strong>Zabezpieczenia</strong>.</li>
    <li>Sam poziom &bdquo;B&#322;&#261;d&rdquo; nie oznacza jeszcze awarii. Liczy si&#281; <strong>czas, &#378;r&oacute;d&#322;o, identyfikator i tre&#347;&#263; wpisu</strong>.</li>
    <li>Do szybkiego filtrowania wielu rekord&oacute;w najlepiej sprawdza si&#281; <strong>PowerShell i Get-WinEvent</strong>.</li>
    <li>Wpisy mo&#380;na zapisa&#263; do pliku <strong>.evtx</strong> i przekaza&#263; do dalszej analizy.</li>
  </ul>
</div><h2 id="gdzie-znalezc-dzienniki-zdarzen-w-windows">Gdzie znale&#378;&#263; dzienniki zdarze&#324; w Windows</h2><p>Najprostszym sposobem jest otwarcie menu Start i wpisanie &bdquo;Podgl&#261;d zdarze&#324;&rdquo;. Mo&#380;esz te&#380; nacisn&#261;&#263; <strong>Win + R</strong>, wpisa&#263; <code>eventvwr.msc</code> i zatwierdzi&#263; Enterem. To wbudowana konsola administracyjna, kt&oacute;ra zbiera informacje z systemu, us&#322;ug i aplikacji.</p><p>Po lewej stronie zobaczysz kilka g&#322;&oacute;wnych grup. Na pocz&#261;tek interesuj&#261; Ci&#281; przede wszystkim <strong>Dzienniki systemu Windows</strong> oraz <strong>Dzienniki aplikacji i us&#322;ug</strong>. Druga grupa bywa szczeg&oacute;lnie cenna przy diagnozowaniu konkretnych komponent&oacute;w, na przyk&#322;ad Windows Update, sterownik&oacute;w, Defendera czy us&#322;ug u&#380;ywanych przez aplikacj&#281; desktopow&#261;.</p><table>
  <tbody>
    <tr>
      <th>Dziennik</th>
      <th>Kiedy go sprawdza&#263;</th>
    </tr>
    <tr>
      <td><strong>Aplikacja</strong></td>
      <td>Gdy program zamyka si&#281;, zg&#322;asza wyj&#261;tek albo nie mo&#380;e wykona&#263; operacji.</td>
    </tr>
    <tr>
      <td><strong>System</strong></td>
      <td>Gdy problem dotyczy sterownika, us&#322;ugi, dysku, sieci lub uruchamiania systemu.</td>
    </tr>
    <tr>
      <td><strong>Zabezpieczenia</strong></td>
      <td>Gdy analizujesz logowania, uprawnienia, audyt i dzia&#322;ania zwi&#261;zane z kontami.</td>
    </tr>
    <tr>
      <td><strong>Konfiguracja</strong></td>
      <td>Gdy potrzebujesz informacji o instalacji aktualizacji, sk&#322;adnik&oacute;w lub zmianach systemowych.</td>
    </tr>
    <tr>
      <td><strong>Dzienniki aplikacji i us&#322;ug</strong></td>
      <td>Gdy zwyk&#322;y dziennik aplikacji nie pokazuje wystarczaj&#261;cych szczeg&oacute;&#322;&oacute;w.</td>
    </tr>
  </tbody>
</table><p>Pliki dziennik&oacute;w s&#261; przechowywane mi&#281;dzy innymi w katalogu <code>C:\Windows\System32\winevt\Logs</code>, ale nie polecam otwierania ich ani modyfikowania bezpo&#347;rednio. Do odczytu u&#380;ywaj Podgl&#261;du zdarze&#324;, <a href="https://kursdotnet.pl/microsoft-linux-w-praktyce-wsl-2-aplikacje-i-ograniczenia">PowerShell</a>a albo bibliotek systemowych, poniewa&#380; narz&#281;dzia te poprawnie interpretuj&#261; struktur&#281; plik&oacute;w <strong>.evtx</strong>.</p><h2 id="jak-czytac-pojedyncze-zdarzenie">Jak czyta&#263; pojedyncze zdarzenie</h2><p>Lista wpis&oacute;w zawiera zwykle dat&#281; i godzin&#281;, poziom, &#378;r&oacute;d&#322;o oraz identyfikator zdarzenia. Najcz&#281;&#347;ciej spotkasz poziomy <strong>Informacje</strong>, <strong>Ostrze&#380;enie</strong>, <strong>B&#322;&#261;d</strong> i <strong>Inspekcja zako&#324;czona powodzeniem</strong> lub niepowodzeniem.</p><p>Nie traktuj&#281; czerwonej ikony jako gotowej diagnozy. Windows zapisuje wiele b&#322;&#281;d&oacute;w przej&#347;ciowych, kt&oacute;re nie maj&#261; praktycznego wp&#322;ywu na dzia&#322;anie komputera. Najwi&#281;ksz&#261; warto&#347;&#263; ma korelacja kilku informacji: <strong>co wydarzy&#322;o si&#281; tu&#380; przed problemem</strong>, jaki proces by&#322; &#378;r&oacute;d&#322;em wpisu i czy zdarzenie powtarza si&#281; regularnie.</p><h3 id="najwazniejsze-pola-wpisu">Najwa&#380;niejsze pola wpisu</h3><ul>
  <li>
<strong>&#377;r&oacute;d&#322;o</strong> wskazuje komponent, kt&oacute;ry zapisa&#322; zdarzenie, na przyk&#322;ad Service Control Manager, Application Error albo konkretna us&#322;uga.</li>
  <li>
<strong>Identyfikator zdarzenia</strong> pomaga odr&oacute;&#380;ni&#263; typy problem&oacute;w w ramach jednego &#378;r&oacute;d&#322;a.</li>
  <li>
<strong>Poziom</strong> pokazuje wag&#281; zg&#322;oszon&#261; przez producenta komponentu, ale nie zawsze opisuje rzeczywisty wp&#322;yw na u&#380;ytkownika.</li>
  <li>
<strong>U&#380;ytkownik</strong> i <strong>komputer</strong> wskazuj&#261; kontekst, w kt&oacute;rym zdarzenie zosta&#322;o zarejestrowane.</li>
  <li>Zak&#322;adka <strong>Szczeg&oacute;&#322;y</strong> cz&#281;sto zawiera dane, kt&oacute;rych nie wida&#263; w kr&oacute;tkim opisie, na przyk&#322;ad nazw&#281; modu&#322;u, kod wyj&#261;tku albo identyfikator procesu.</li>
</ul><p>Przy awarii programu zwracam uwag&#281; na wpisy <strong>Application Error</strong> i <strong>.NET Runtime</strong>. Kod wyj&#261;tku, nazwa uszkodzonego modu&#322;u oraz czas zdarzenia cz&#281;sto pozwalaj&#261; odr&oacute;&#380;ni&#263; b&#322;&#261;d aplikacji od problemu z systemem, bibliotek&#261; DLL albo sterownikiem.</p><p>Sam identyfikator zdarzenia nie wystarcza do pewnej diagnozy. Ten sam numer mo&#380;e pojawi&#263; si&#281; w r&oacute;&#380;nych wersjach Windows lub w odmiennych konfiguracjach, dlatego zawsze analizuj go razem ze &#378;r&oacute;d&#322;em, tre&#347;ci&#261; i pe&#322;nymi szczeg&oacute;&#322;ami.</p><h2 id="jak-filtrowac-i-eksportowac-wpisy">Jak filtrowa&#263; i eksportowa&#263; wpisy</h2><p>Przegl&#261;danie tysi&#281;cy rekord&oacute;w jeden po drugim jest strat&#261; czasu. W wybranym dzienniku kliknij <strong>Filtruj bie&#380;&#261;cy dziennik</strong>, a nast&#281;pnie zaw&#281;&#378; zakres po poziomie, &#378;r&oacute;dle, identyfikatorze zdarzenia i przedziale czasu.</p><p>Przy diagnozowaniu awarii najlepiej zacz&#261;&#263; od okresu obejmuj&#261;cego kilka minut przed i po problemie. Zbyt szeroki zakres zwykle zasypuje wynikami, a zbyt w&#261;ski mo&#380;e pomin&#261;&#263; zdarzenie poprzedzaj&#261;ce w&#322;a&#347;ciwy b&#322;&#261;d.</p><h3 id="praktyczne-filtrowanie-w-powershellu">Praktyczne filtrowanie w PowerShellu</h3><p>PowerShell jest wygodniejszy od interfejsu graficznego, gdy trzeba przeszuka&#263; wiele komputer&oacute;w albo powtarza&#263; analiz&#281;. Przyk&#322;adowe polecenie pobiera 50 najnowszych b&#322;&#281;d&oacute;w z dziennika systemowego:</p><pre><code>Get-WinEvent -FilterHashtable @{
    LogName = 'System'
    Level = 2
} -MaxEvents 50</code></pre><p>Mo&#380;esz te&#380; ograniczy&#263; wynik do konkretnego &#378;r&oacute;d&#322;a lub czasu:</p><pre><code>Get-WinEvent -FilterHashtable @{
    LogName      = 'Application'
    ProviderName = '.NET Runtime'
    StartTime    = (Get-Date).AddHours(-2)
} | Select-Object TimeCreated, Id, LevelDisplayName, Message</code></pre><p>Wed&#322;ug dokumentacji Microsoft polecenie <strong>Get-WinEvent</strong> obs&#322;uguje zar&oacute;wno klasyczne dzienniki zdarze&#324;, jak i zdarzenia zwi&#261;zane z ETW. W praktyce jest szybsze i bardziej elastyczne ni&#380; starsze <code>Get-EventLog</code>, szczeg&oacute;lnie przy filtrowaniu po czasie oraz wielu kryteriach.</p><p>Je&#347;li chcesz przekaza&#263; wpisy do dalszej analizy, wybierz w Podgl&#261;dzie zdarze&#324; opcj&#281; <strong>Zapisz wszystkie zdarzenia jako</strong> i u&#380;yj formatu <code>.evtx</code>. Pojedynczy wpis mo&#380;na r&oacute;wnie&#380; skopiowa&#263; jako tekst lub XML. Format XML jest przydatny, gdy analizujesz dodatkowe pola niewidoczne w podstawowym widoku.</p><h2 id="co-sprawdzac-przy-typowych-problemach">Co sprawdza&#263; przy typowych problemach</h2><p>Dzienniki s&#261; najbardziej u&#380;yteczne wtedy, gdy szukasz odpowiedzi na konkretne pytanie. Nie pr&oacute;buj analizowa&#263; ca&#322;ej historii systemu naraz. Najpierw ustal moment wyst&#261;pienia problemu, a potem sprawd&#378; wpisy z kilku minut wok&oacute;&#322; tego czasu.</p><h3 id="aplikacja-zamyka-sie-bez-komunikatu">Aplikacja zamyka si&#281; bez komunikatu</h3><p>W dzienniku <strong>Aplikacja</strong> wyszukaj zdarzenia z czasu zamkni&#281;cia programu. Interesuj&#261; Ci&#281; przede wszystkim nazwa aplikacji, kod wyj&#261;tku, modu&#322; powoduj&#261;cy b&#322;&#261;d i ewentualny wpis <strong>.NET Runtime</strong>. Je&#347;li aplikacja korzysta z w&#322;asnego &#378;r&oacute;d&#322;a zdarze&#324;, sprawd&#378; r&oacute;wnie&#380; odpowiedni kana&#322; w sekcji aplikacji i us&#322;ug.</p><h3 id="system-dlugo-sie-uruchamia">System d&#322;ugo si&#281; uruchamia</h3><p>Sprawd&#378; dziennik <strong>System</strong> oraz wpisy dotycz&#261;ce us&#322;ug. Powtarzaj&#261;ce si&#281; problemy Service Control Manager mog&#261; wskazywa&#263; na us&#322;ug&#281;, kt&oacute;ra startuje z op&oacute;&#378;nieniem, ko&#324;czy prac&#281; albo czeka na zale&#380;no&#347;&#263;. Pojedynczy wpis po aktualizacji nie musi oznacza&#263; trwa&#322;ej usterki.</p><h3 id="komputer-zawiesza-sie-lub-restartuje">Komputer zawiesza si&#281; lub restartuje</h3><p>Szukaj zdarze&#324; zwi&#261;zanych z Kernel-Power, sterownikami, dyskiem i sprz&#281;tem. Wpis Kernel-Power o identyfikatorze 41 informuje zwykle, &#380;e system nie zosta&#322; prawid&#322;owo zamkni&#281;ty, ale sam nie wyja&#347;nia przyczyny. Mo&#380;e by&#263; skutkiem utraty zasilania, twardego resetu, przegrzania albo wcze&#347;niejszego b&#322;&#281;du sterownika.</p><p class="read-more"><strong>Przeczytaj r&oacute;wnie&#380;: <a href="https://kursdotnet.pl/vcruntime140dll-i-msvcp140dll-jak-naprawic-blad">VCRUNTIME140.dll i MSVCP140.dll - jak naprawi&#263; b&#322;&#261;d?</a></strong></p><h3 id="nie-dziala-logowanie-lub-uprawnienia">Nie dzia&#322;a logowanie lub uprawnienia</h3><p>W takich przypadkach przydatny jest dziennik <strong>Zabezpieczenia</strong>. Pami&#281;taj jednak, &#380;e zakres zapisywanych zdarze&#324; zale&#380;y od w&#322;&#261;czonych zasad audytu. Brak wpisu nie zawsze oznacza, &#380;e dzia&#322;anie si&#281; nie wydarzy&#322;o, lecz czasem tylko to, &#380;e system nie mia&#322; skonfigurowanego rejestrowania danego typu aktywno&#347;ci.</p><h2 id="dziennik-zdarzen-w-aplikacjach-net">Dziennik zdarze&#324; w aplikacjach .NET</h2><p>Je&#380;eli tworzysz aplikacj&#281; desktopow&#261; w .NET, mo&#380;esz zapisywa&#263; w&#322;asne informacje w systemie zdarze&#324;. Ma to sens przy b&#322;&#281;dach wyst&#281;puj&#261;cych tylko u cz&#281;&#347;ci u&#380;ytkownik&oacute;w, problemach z konfiguracj&#261; albo awariach, kt&oacute;rych nie da si&#281; odtworzy&#263; lokalnie.</p><p>W prostym scenariuszu u&#380;yjesz klasy <code>EventLog</code>:</p><pre><code>using System.Diagnostics;

if (!EventLog.SourceExists("MojaAplikacja"))
{
    EventLog.CreateEventSource("MojaAplikacja", "Application");
}

using var log = new EventLog("Application")
{
    Source = "MojaAplikacja"
};

log.WriteEntry(
    "Nie uda&#322;o si&#281; wczyta&#263; konfiguracji.",
    EventLogEntryType.Error,
    1001);</code></pre><p>Tworzenie &#378;r&oacute;d&#322;a zdarze&#324; mo&#380;e wymaga&#263; uprawnie&#324; administratora, dlatego nie powinno odbywa&#263; si&#281; przy ka&#380;dym uruchomieniu programu. Lepiej przygotowa&#263; &#378;r&oacute;d&#322;o podczas instalacji aplikacji albo u&#380;y&#263; mechanizmu logowania, kt&oacute;ry zapisuje dane do w&#322;asnego pliku.</p><p>W aplikacjach produkcyjnych nie zapisuj hase&#322;, token&oacute;w, pe&#322;nych danych osobowych ani ca&#322;ych obiekt&oacute;w &#380;&#261;da&#324;. Dziennik mo&#380;e by&#263; dost&#281;pny dla administrator&oacute;w i narz&#281;dzi monitoruj&#261;cych, wi&#281;c powinien zawiera&#263; <strong>minimum informacji potrzebnych do diagnozy</strong>. Przy wi&#281;kszych projektach warto po&#322;&#261;czy&#263; logowanie lokalne z centralnym systemem obserwowalno&#347;ci.</p><p>Je&#347;li interesuje Ci&#281; tak&#380;e programistyczna obs&#322;uga zdarze&#324;, dobrym uzupe&#322;nieniem jest <a href="https://learn.microsoft.com/pl-pl/windows/win32/events/windows-events" rel="nofollow noopener noreferrer">dokumentacja zdarze&#324; systemu Windows</a>, kt&oacute;ra opisuje model kana&#322;&oacute;w, dostawc&oacute;w i odczytu danych przez API.</p><h2 id="najczestsze-bledy-podczas-analizy-logow">Najcz&#281;stsze b&#322;&#281;dy podczas analizy log&oacute;w</h2><p>Pierwszy b&#322;&#261;d to traktowanie ka&#380;dego ostrze&#380;enia jak awarii. System mo&#380;e zapisa&#263; ostrze&#380;enie, a mimo to dzia&#322;a&#263; poprawnie. Znacznie wa&#380;niejsza jest <strong>powtarzalno&#347;&#263; i zgodno&#347;&#263; czasowa</strong> z obserwowanym problemem.</p><p>Drugi b&#322;&#261;d polega na wyszukiwaniu identyfikatora zdarzenia bez sprawdzania &#378;r&oacute;d&#322;a. Zanim wyci&#261;gniesz wnioski, zanotuj pe&#322;n&#261; nazw&#281; dostawcy, wersj&#281; systemu, godzin&#281; i tre&#347;&#263; wpisu. Dopiero wtedy por&oacute;wnuj zdarzenie z dokumentacj&#261; lub innymi przypadkami.</p><p>Trzeci problem to zbyt ma&#322;y rozmiar dziennika. Gdy log szybko si&#281; przepe&#322;nia, starsze wpisy mog&#261; by&#263; nadpisywane. W&#322;a&#347;ciwo&#347;ci ka&#380;dego dziennika pozwalaj&#261; ustawi&#263; maksymalny rozmiar pliku i spos&oacute;b przechowywania zdarze&#324;, ale zwi&#281;kszanie limitu ma sens tylko wtedy, gdy masz wystarczaj&#261;co du&#380;o miejsca na dysku.</p><p>Nie czy&#347;&#263; dziennik&oacute;w tylko po to, by &bdquo;znikn&#281;&#322;y b&#322;&#281;dy&rdquo;. Usuni&#281;cie wpis&oacute;w kasuje materia&#322; diagnostyczny i mo&#380;e utrudni&#263; analiz&#281; incydentu. Je&#347;li potrzebujesz porz&#261;dku, najpierw wyeksportuj log, a dopiero p&oacute;&#378;niej rozwa&#380; jego wyczyszczenie zgodnie z procedur&#261; administracyjn&#261;.</p><h2 id="jak-zamienic-pojedynczy-wpis-w-uzyteczna-diagnoze">Jak zamieni&#263; pojedynczy wpis w u&#380;yteczn&#261; diagnoz&#281;</h2><p>Najlepszy efekt daje kr&oacute;tka, powtarzalna procedura. Zanotuj dok&#322;adn&#261; godzin&#281; problemu, sprawd&#378; kilka minut wcze&#347;niejszych zdarze&#324;, por&oacute;wnaj wpisy z dziennika aplikacji i systemu, a na ko&#324;cu wyeksportuj dane, zanim zaczniesz zmienia&#263; konfiguracj&#281;.</p><p>W praktyce nie szukam jednego &bdquo;magicznego&rdquo; identyfikatora. Szukam <strong>&#322;a&#324;cucha zdarze&#324;</strong>, kt&oacute;ry pokazuje, co uruchomi&#322;o problem, jaki komponent zareagowa&#322; i czy b&#322;&#261;d wyst&#261;pi&#322; ponownie. Taki spos&oacute;b pracy jest wolniejszy ni&#380; przypadkowe usuwanie ostrze&#380;e&#324;, ale daje znacznie wi&#281;ksz&#261; szans&#281; na trwa&#322;e rozwi&#261;zanie.</p><p>Podgl&#261;d zdarze&#324; nie zast&#261;pi monitoringu, zrzut&oacute;w pami&#281;ci ani test&oacute;w sprz&#281;tu, ale pozostaje jednym z pierwszych miejsc, kt&oacute;re warto sprawdzi&#263;. Gdy po&#322;&#261;czysz go z filtrowaniem PowerShell, w&#322;asnym logowaniem w aplikacji .NET i rozs&#261;dn&#261; retencj&#261; danych, otrzymasz praktyczne narz&#281;dzie do diagnozowania Windows bez zgadywania.</p>]]></content:encoded>
      <author>Przemysław Kwiatkowski</author>
      <category>Windows i aplikacje desktopowe</category>
      <media:thumbnail url="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/thumbnail/d96f88951418531d472ef1bb76de5c26/podglad-zdarzen-windows-jak-czytac-logi-i-znalezc-blad.webp"/>
      <pubDate>Wed, 09 Sep 2026 15:01:00 +0200</pubDate>
    </item>
    <item>
      <title>XOR w C# - operacje bitowe, flagi i najczęstsze błędy</title>
      <link>https://kursdotnet.pl/xor-w-c-operacje-bitowe-flagi-i-najczestsze-bledy</link>
      <description>XOR w C# bez tajemnic: poznaj operacje bitowe i logiczne, przełączanie flag oraz najczęstsze błędy. Sprawdź praktyczny przewodnik.</description>
      <content:encoded><![CDATA[<?xml encoding="utf-8" ?><p>Gdy trzeba sprawdzi&#263;, czy dwa bity r&oacute;&#380;ni&#261; si&#281; od siebie, prze&#322;&#261;czy&#263; pojedyncz&#261; flag&#281; albo por&oacute;wna&#263; dwa warunki logiczne, operator XOR w C# okazuje si&#281; bardzo wygodnym narz&#281;dziem. Poka&#380;&#281;, jak dzia&#322;a zapis <strong>^</strong>, czym r&oacute;&#380;ni si&#281; operacja bitowa od logicznej, gdzie przydaje si&#281; w .NET i jakie b&#322;&#281;dy najcz&#281;&#347;ciej prowadz&#261; do nieczytelnego lub niepoprawnego kodu.</p><div class="short-summary">
<h2 id="najwazniejsze-informacje-o-xor-w-c-w-kilku-punktach">Najwa&#380;niejsze informacje o XOR w C# w kilku punktach</h2>
<ul>
<li>
<strong>Operator ^</strong> zwraca 1 dla bit&oacute;w, kt&oacute;re r&oacute;&#380;ni&#261; si&#281; od siebie.</li>
<li>Dla warto&#347;ci ca&#322;kowitych wykonuje <strong>operacj&#281; bitow&#261;</strong>, a dla <strong>bool</strong> operacj&#281; logiczn&#261; exclusive OR.</li>
<li>
<strong>true ^ false</strong> daje true, ale true ^ true daje false.</li>
<li>Operator <strong>^=</strong> pozwala szybko prze&#322;&#261;cza&#263; <a href="https://kursdotnet.pl/enum-w-c-jak-uzywac-go-bezpiecznie">flagi bitowe</a>.</li>
<li>Przy typach <strong>byte, short i ushort</strong> trzeba pami&#281;ta&#263; o promocji do typu int.</li>
</ul>
</div><p><img src="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/post_image/16889a40943b9c9609bdf16778d6f16c/c-bitwise-xor-binary-operation-truth-table-flags-mask.webp" class="image article-image" loading="lazy" alt="Operacja bitwise XOR w C# na myValueA z mask&#261; 0x0000FFFF. Wynik myValueY ma niezmienion&#261; cz&#281;&#347;&#263; i odwr&oacute;con&#261;."></p><h2 id="jak-dziala-operator-xor-w-c">Jak dzia&#322;a operator XOR w C#</h2><p>XOR, czyli alternatywa wykluczaj&#261;ca, por&oacute;wnuje dwa wej&#347;cia bit po bicie. Wynik jest ustawiony wtedy, gdy <strong>dok&#322;adnie jedno wej&#347;cie ma warto&#347;&#263; 1</strong>. Gdy oba bity s&#261; takie same, wynik wynosi 0.</p><table>
<tbody>
<tr>
<th>Pierwszy bit</th>
<th>Drugi bit</th>
<th>Wynik XOR</th>
</tr>
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td>0</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>0</td>
</tr>
</tbody>
</table><p>W C# zapis wygl&#261;da prosto. Dla liczb ca&#322;kowitych operator <strong>^</strong> dzia&#322;a na reprezentacji binarnej obu warto&#347;ci.</p><pre><code>int wynik = 5 ^ 3;

// 5 = 0101
// 3 = 0011
//     ----
//     0110 = 6</code></pre><p>W tym przyk&#322;adzie r&oacute;&#380;ni&#261; si&#281; dwa najm&#322;odsze bity, dlatego wynik to <strong>6</strong>. Nie traktuj&#281; XOR jako zamiennika zwyk&#322;ego dodawania czy odejmowania. To narz&#281;dzie do pracy z bitami, maskami, flagami i niskopoziomowym formatem danych.</p><h2 id="operacja-bitowa-i-logiczna-to-nie-to-samo">Operacja bitowa i logiczna to nie to samo</h2><p>Ten sam symbol ma w C# dwa zastosowania. Dla typ&oacute;w ca&#322;kowitych wykonuje <strong>XOR bitowy</strong>, natomiast dla warto&#347;ci <strong>bool</strong> por&oacute;wnuje dwa warunki logiczne.</p><pre><code>bool pierwszy = true;
bool drugi = false;

Console.WriteLine(pierwszy ^ drugi); // True
Console.WriteLine(true ^ true);      // False
Console.WriteLine(false ^ false);    // False</code></pre><p>Logiczne XOR jest przydatne wtedy, gdy poprawny ma by&#263; <strong>dok&#322;adnie jeden warunek</strong>. Przyk&#322;adowo u&#380;ytkownik mo&#380;e wybra&#263; logowanie has&#322;em albo kodem jednorazowym, ale nie oba mechanizmy jednocze&#347;nie.</p><pre><code>bool hasloWybrane = true;
bool kodJednorazowyWybrany = false;

bool poprawnyWybor = hasloWybrane ^ kodJednorazowyWybrany;</code></pre><p>Wa&#380;na r&oacute;&#380;nica wzgl&#281;dem <strong>&amp;&amp;</strong> i <strong>||</strong> polega na tym, &#380;e operator <strong>^ zawsze oblicza oba operandy</strong>. Nie stosuje kr&oacute;tkiego obwodu. Je&#380;eli drugi operand wywo&#322;uje metod&#281; albo wykonuje kosztown&#261; operacj&#281;, zostanie wykonany tak&#380;e wtedy, gdy wynik wydaje si&#281; ju&#380; oczywisty.</p><h2 id="najpraktyczniejsze-zastosowanie-to-maski-i-flagi">Najpraktyczniejsze zastosowanie to maski i flagi</h2><p>W aplikacjach .NET wiele ustawie&#324; mo&#380;na zapisa&#263; w jednej liczbie. Ka&#380;dy bit reprezentuje osobn&#261; flag&#281;, na przyk&#322;ad uprawnienie do odczytu, zapisu albo usuwania danych. XOR pozwala <strong>prze&#322;&#261;czy&#263; wybran&#261; flag&#281;</strong> bez zmiany pozosta&#322;ych.</p><pre><code>[Flags]
enum Uprawnienia
{
    Brak = 0,
    Odczyt = 1,
    Zapis = 2,
    Usuwanie = 4
}

Uprawnienia uprawnienia = Uprawnienia.Odczyt | Uprawnienia.Zapis;

// Wy&#322;&#261;cza Zapis, je&#347;li by&#322; w&#322;&#261;czony.
// W&#322;&#261;cza Zapis, je&#347;li by&#322; wy&#322;&#261;czony.
uprawnienia ^= Uprawnienia.Zapis;</code></pre><p>To zachowanie jest wygodne w interfejsach, konfiguracji i edytorach ustawie&#324;. Trzeba jednak pami&#281;ta&#263;, &#380;e XOR nie oznacza &bdquo;usu&#324; flag&#281;&rdquo;. On j&#261; <strong>odwraca</strong>. Je&#347;li kod mo&#380;e zosta&#263; wykonany dwa razy, druga operacja przywr&oacute;ci poprzedni stan.</p><p>Do innych cel&oacute;w u&#380;ywam innych operator&oacute;w. <strong>|</strong> s&#322;u&#380;y do ustawienia flagi, <strong>&amp;</strong> do sprawdzenia jej obecno&#347;ci, a po&#322;&#261;czenie <strong>&amp; ~</strong> do bezpiecznego wyczyszczenia.</p><pre><code>uprawnienia |= Uprawnienia.Usuwanie;

bool mozeUsuwac =
    (uprawnienia &amp; Uprawnienia.Usuwanie) != 0;

uprawnienia &amp;= ~Uprawnienia.Usuwanie;</code></pre><p>W praktyce taki podzia&#322; jest czytelniejszy ni&#380; u&#380;ywanie XOR wsz&#281;dzie, gdzie pracujemy z flagami. Operator powinien jasno komunikowa&#263; intencj&#281; kodu: ustaw, sprawd&#378;, wyczy&#347;&#263; albo prze&#322;&#261;cz.</p><h2 id="typy-liczbowe-promocja-i-operator">Typy liczbowe, promocja i operator ^=</h2><p>Operator XOR dzia&#322;a z ca&#322;kowitymi typami liczbowymi, mi&#281;dzy innymi z <strong>int, uint, long, ulong, byte i short</strong>. Przy mniejszych typach C# stosuje promocj&#281; numeryczn&#261;. Dlatego wynik operacji na dw&oacute;ch warto&#347;ciach typu byte jest zwykle typu <strong>int</strong>.</p><pre><code>byte a = 0b_1010;
byte b = 0b_0110;

int wynik = a ^ b;

// Je&#347;li potrzebujesz ponownie byte:
byte wynikByte = (byte)(a ^ b);</code></pre><p>Rzutowanie ma sens tylko wtedy, gdy masz pewno&#347;&#263;, &#380;e wynik mie&#347;ci si&#281; w zakresie typu docelowego. W przeciwnym razie konwersja mo&#380;e uci&#261;&#263; starsze bity i ukry&#263; b&#322;&#261;d.</p><p>Dost&#281;pna jest tak&#380;e wersja przypisania z operacj&#261; XOR, czyli <strong>^=</strong>. Wyra&#380;enie `wartosc ^= maska` oznacza to samo co `wartosc = wartosc ^ maska`, z t&#261; r&oacute;&#380;nic&#261;, &#380;e lewa strona jest obliczana tylko raz.</p><pre><code>int wartosc = 0b_0101;
wartosc ^= 0b_0011;

// wartosc ma teraz warto&#347;&#263; 0b_0110</code></pre><p>W przypadku typ&oacute;w enum z atrybutem <strong>[Flags]</strong> taki zapis cz&#281;sto jest najbardziej naturalny. Mimo to dobrze nazwa&#263; mask&#281; i u&#380;ywa&#263; warto&#347;ci opisowych zamiast wpisywa&#263; w kodzie surowe liczby binarne.</p><h2 id="precedencja-operatorow-i-bledy-ktore-latwo-przeoczyc">Precedencja operator&oacute;w i b&#322;&#281;dy, kt&oacute;re &#322;atwo przeoczy&#263;</h2><p>Operator XOR ma ni&#380;szy priorytet ni&#380; operator <strong>&amp;</strong>, ale wy&#380;szy ni&#380; operator <strong>|</strong>. Oznacza to, &#380;e wyra&#380;enie z kilkoma operacjami bitowymi mo&#380;e zosta&#263; odczytane inaczej, ni&#380; sugeruje intuicja. W kodzie produkcyjnym zwykle dodaj&#281; nawiasy nawet wtedy, gdy znam kolejno&#347;&#263; wykonywania.</p><pre><code>int wynik1 = a | b ^ c;
int wynik2 = a | (b ^ c);</code></pre><p>W tym przypadku oba zapisy s&#261; r&oacute;wnowa&#380;ne, ale drugi wyra&#378;niej pokazuje zamiar. Przy sprawdzaniu flag r&oacute;wnie&#380; lepiej nie polega&#263; na pami&#281;ci dotycz&#261;cej precedencji.</p><pre><code>bool aktywna = (wartosc &amp; maska) != 0;</code></pre><p>Cz&#281;sty b&#322;&#261;d polega na u&#380;yciu XOR tam, gdzie potrzebne jest zwyk&#322;e OR. Wyra&#380;enie <strong>a ^ b</strong> wy&#322;&#261;czy bit ustawiony w obu operandach, natomiast <strong>a | b</strong> zachowa go jako ustawiony. To r&oacute;&#380;nica, kt&oacute;ra potrafi da&#263; trudny do zauwa&#380;enia b&#322;&#261;d w systemie uprawnie&#324;.</p><p>Druga pu&#322;apka dotyczy pomylenia symbolu XOR z operatorem indeksowania od ko&#324;ca. W wyra&#380;eniu `tablica[^1]` znak <strong>^</strong> nie wykonuje operacji bitowej, tylko oznacza pierwszy element liczony od ko&#324;ca. Znaczenie operatora wynika wi&#281;c z kontekstu i typu wyra&#380;enia.</p><h2 id="gdzie-xor-ma-sens-a-gdzie-lepiej-go-unikac">Gdzie XOR ma sens, a gdzie lepiej go unika&#263;</h2><p>XOR jest dobrym wyborem w kodzie pracuj&#261;cym z <strong>maskami bitowymi, protoko&#322;ami, formatami binarnymi i flagami</strong>. Przydaje si&#281; te&#380; do prostych transformacji, por&oacute;wnywania wzorc&oacute;w bitowych oraz oblicze&#324;, w kt&oacute;rych interesuje nas r&oacute;&#380;nica mi&#281;dzy dwoma zestawami bit&oacute;w.</p><p>Nie u&#380;ywa&#322;bym go jednak do zast&#281;powania czytelnych warunk&oacute;w biznesowych. Zapis `czyAdmin ^ czyZablokowany` mo&#380;e by&#263; poprawny technicznie, ale dla wielu os&oacute;b mniej jasny ni&#380; jawne sprawdzenie regu&#322;y. Gdy logika ma znaczenie biznesowe, <strong>czytelno&#347;&#263; zwykle wygrywa z kr&oacute;tszym zapisem</strong>.</p><p>Trzeba te&#380; uwa&#380;a&#263; na argument, &#380;e XOR sam w sobie zapewnia szyfrowanie. Operacja jest odwracalna, poniewa&#380; `a ^ b ^ b` zwraca ponownie `a`, ale u&#380;ycie <a href="https://kursdotnet.pl/jak-definiowac-elementy-w-c-klasy-metody-i-define">sta&#322;e</a>go lub przewidywalnego klucza nie tworzy bezpiecznego mechanizmu ochrony danych. Do poufno&#347;ci stosuj&#281; algorytmy kryptograficzne z biblioteki .NET, a XOR traktuj&#281; jako operacj&#281; techniczn&#261;, nie gotowy system szyfrowania.</p><p>Moja praktyczna zasada jest prosta. Je&#380;eli w kodzie wida&#263; bitowe maski albo struktur&#281; danych opisan&#261; bitami, XOR jest naturalnym narz&#281;dziem. Je&#347;li operacja dotyczy regu&#322; u&#380;ytkownika, p&#322;atno&#347;ci lub uprawnie&#324; opisanych s&#322;owami, najpierw wybieram konstrukcj&#281;, kt&oacute;r&#261; bez problemu odczyta osoba przegl&#261;daj&#261;ca kod.</p><h2 id="jak-zapamietac-dzialanie-xor-bez-liczenia-w-pamieci">Jak zapami&#281;ta&#263; dzia&#322;anie XOR bez liczenia w pami&#281;ci</h2><p>Naj&#322;atwiej my&#347;le&#263; o XOR jako o odpowiedzi na pytanie &bdquo;czy te dwa bity s&#261; r&oacute;&#380;ne?&rdquo;. Dla liczb mo&#380;na zapami&#281;ta&#263; trzy w&#322;asno&#347;ci: <strong>x ^ 0 = x</strong>, <strong>x ^ x = 0</strong> oraz <strong>x ^ y ^ y = x</strong>. Te regu&#322;y wyja&#347;niaj&#261; zar&oacute;wno prze&#322;&#261;czanie flag, jak i odwracalno&#347;&#263; prostych operacji.</p><p>W codziennym C# najwa&#380;niejsze jest nie samo zapami&#281;tanie symbolu, lecz dobranie w&#322;a&#347;ciwego zastosowania. Operator <strong>^</strong> &#347;wietnie radzi sobie z bitami i warunkiem &bdquo;dok&#322;adnie jedno&rdquo;, ale nie zast&#281;puje operator&oacute;w <strong>|</strong>, <strong>&amp;</strong>, <strong>&amp;&amp;</strong> ani bezpiecznych algorytm&oacute;w kryptograficznych.</p>]]></content:encoded>
      <author>Przemysław Kwiatkowski</author>
      <category>C# i .NET</category>
      <media:thumbnail url="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/thumbnail/9a97e11d450a4f1d5da41f82bf18b99b/xor-w-c-operacje-bitowe-flagi-i-najczestsze-bledy.webp"/>
      <pubDate>Wed, 09 Sep 2026 08:40:00 +0200</pubDate>
    </item>
    <item>
      <title>RabbitMQ vs Kafka - który broker pasuje do Twojej aplikacji?</title>
      <link>https://kursdotnet.pl/rabbitmq-vs-kafka-ktory-broker-pasuje-do-twojej-aplikacji</link>
      <description>RabbitMQ vs Kafka: poznaj różnice w routingu, retry, skalowaniu i replayu. Sprawdź, który broker pasuje do Twojej aplikacji.</description>
      <content:encoded><![CDATA[<?xml encoding="utf-8" ?><p>Projekt ro&#347;nie, a prosty mechanizm wysy&#322;ania komunikat&oacute;w zaczyna wp&#322;ywa&#263; na op&oacute;&#378;nienia, retry i spos&oacute;b skalowania ca&#322;ego systemu. Por&oacute;wnanie RabbitMQ vs Kafka pokazuje dwie r&oacute;&#380;ne filozofie pracy z komunikatami, dlatego omawiam tu architektur&#281; obu narz&#281;dzi, najwa&#380;niejsze wzorce, obs&#322;ug&#281; b&#322;&#281;d&oacute;w oraz praktyczne kryteria wyboru dla aplikacji, tak&#380;e tych tworzonych w .NET.</p><div class="short-summary">
<h2 id="najwazniejsza-roznica-wynika-z-tego-czy-obslugujesz-zadania-czy-historie-zdarzen">Najwa&#380;niejsza r&oacute;&#380;nica wynika z tego, czy obs&#322;ugujesz zadania, czy histori&#281; zdarze&#324;</h2>
<ul>
<li>
<strong>RabbitMQ</strong> najlepiej pasuje do kolejek zada&#324;, routingu, retry, dead-letteringu i komunikacji mi&#281;dzy us&#322;ugami.</li>
<li>
<strong>Kafka</strong> sprawdza si&#281; przy du&#380;ym strumieniu zdarze&#324;, partycjach, replayu oraz integracji z analityk&#261;.</li>
<li>W RabbitMQ wiadomo&#347;&#263; jest niezale&#380;nym zadaniem, a w Kafka elementem <strong>trwa&#322;ego logu</strong>.</li>
<li>RabbitMQ kieruje komunikaty przez <strong>exchange i bindingi</strong>, natomiast Kafka opiera si&#281; g&#322;&oacute;wnie na topicach i partycjach.</li>
<li>W obu rozwi&#261;zaniach trzeba projektowa&#263; <strong>idempotentnych konsument&oacute;w</strong>, bo gwarancja exactly-once rzadko rozwi&#261;zuje problem end-to-end.</li>
</ul>
</div><h2 id="rabbitmq-i-kafka-zaczynaja-od-innego-modelu-danych">RabbitMQ i Kafka zaczynaj&#261; od innego modelu danych</h2><p>RabbitMQ jest brokerem komunikat&oacute;w. Producent wysy&#322;a wiadomo&#347;&#263; do <strong>exchange</strong>, a broker decyduje, do kt&oacute;rych kolejek lub strumieni ma ona trafi&#263;. Regu&#322;y routingu definiuj&#261; bindingi, klucze routingu i typ exchange. Konsument pobiera wiadomo&#347;&#263;, wykonuje prac&#281;, a potem potwierdza j&#261; za pomoc&#261; acknowledgement.</p><p>Ten model dobrze pasuje do sytuacji, w kt&oacute;rej wiadomo&#347;&#263; oznacza konkretne zadanie. Przyk&#322;adowo us&#322;uga zam&oacute;wie&#324; publikuje polecenie wygenerowania faktury, a jeden z dost&#281;pnych worker&oacute;w je odbiera. Po poprawnym przetworzeniu komunikat znika z kolejki, a w przypadku b&#322;&#281;du mo&#380;e wr&oacute;ci&#263; do obs&#322;ugi, trafi&#263; do retry queue albo dead-letter queue.</p><p>Kafka dzia&#322;a przede wszystkim jako <strong>rozproszony, trwa&#322;y log</strong>. Producent zapisuje rekord w topicu, kt&oacute;ry jest podzielony na partycje. Ka&#380;dy rekord ma offset, czyli pozycj&#281; w danej partycji, a konsument mo&#380;e &#347;ledzi&#263; swoje miejsce i wr&oacute;ci&#263; do wcze&#347;niejszych danych.</p><p>To zmienia spos&oacute;b my&#347;lenia. Odczyt wiadomo&#347;ci z Kafka nie musi jej usuwa&#263;. Ten sam fakt biznesowy mo&#380;e zosta&#263; odczytany przez kilka grup konsument&oacute;w, a nowa us&#322;uga mo&#380;e przetworzy&#263; histori&#281; od wybranego momentu. W&#322;a&#347;nie dlatego Kafka jest naturalnym wyborem dla <strong>event streamingu</strong>, analityki i budowania kolejnych projekcji danych.</p><table>
<tbody>
<tr>
<th>Kryterium</th>
<th>RabbitMQ</th>
<th>Kafka</th>
</tr>
<tr>
<td>Podstawowy model</td>
<td>Broker komunikat&oacute;w i kolejki</td>
<td>Trwa&#322;y log zdarze&#324;</td>
</tr>
<tr>
<td>Routing</td>
<td>Exchange, bindingi i klucze routingu</td>
<td>Topic, partycja i logika producenta</td>
</tr>
<tr>
<td>R&oacute;wnoleg&#322;o&#347;&#263;</td>
<td>Wielu konsument&oacute;w kolejki konkuruje o wiadomo&#347;ci</td>
<td>Jedna partycja jest obs&#322;ugiwana przez jednego konsumenta w grupie</td>
</tr>
<tr>
<td>Usuwanie danych</td>
<td>Po potwierdzeniu i obs&#322;udze wiadomo&#347;ci</td>
<td>Wed&#322;ug retencji, rozmiaru lub polityki przechowywania</td>
</tr>
<tr>
<td>Powt&oacute;rne przetwarzanie</td>
<td>Wymaga retry, requeue lub osobnej konfiguracji</td>
<td>Naturalne dzi&#281;ki offsetom i retencji</td>
</tr>
</tbody>
</table><p>Granica mi&#281;dzy produktami nie jest ju&#380; tak ostra jak kilka lat temu. RabbitMQ ma trwa&#322;e strumienie, a nowsze mo&#380;liwo&#347;ci Kafka obejmuj&#261; r&oacute;wnie&#380; scenariusze bli&#380;sze kolejkom. Mimo tego <strong>pierwotne za&#322;o&#380;enia architektury nadal wp&#322;ywaj&#261; na wygod&#281; u&#380;ycia</strong>, obs&#322;ug&#281; b&#322;&#281;d&oacute;w i spos&oacute;b modelowania aplikacji.</p><h2 id="rabbitmq-wygrywa-tam-gdzie-komunikat-jest-konkretna-praca">RabbitMQ wygrywa tam, gdzie komunikat jest konkretn&#261; prac&#261;</h2><h3 id="kolejka-zadan-dla-workerow">Kolejka zada&#324; dla worker&oacute;w</h3><p>Najbardziej typowy wzorzec RabbitMQ to work queue. API przyjmuje &#380;&#261;danie szybko, publikuje zadanie, a osobny worker wykonuje je poza g&#322;&oacute;wnym &#380;&#261;daniem HTTP. Tak mo&#380;na obs&#322;u&#380;y&#263; generowanie dokument&oacute;w, wysy&#322;k&#281; wiadomo&#347;ci e-mail, przetwarzanie obraz&oacute;w czy synchronizacj&#281; z systemem zewn&#281;trznym.</p><p>W tym scenariuszu liczy si&#281; to, &#380;e <strong>jedno zadanie trafia do jednego wykonawcy</strong>. RabbitMQ daje tu naturalne acknowledgement, kontrol&#281; prefetchu, mo&#380;liwo&#347;&#263; ponowienia wiadomo&#347;ci i przekierowanie problematycznych komunikat&oacute;w do dead-letter queue. Nie trzeba budowa&#263; tych mechanizm&oacute;w od podstaw w kodzie aplikacji.</p><h3 id="routing-i-selektywna-dystrybucja">Routing i selektywna dystrybucja</h3><p>RabbitMQ jest bardzo wygodny, gdy odbiorcy interesuj&#261; si&#281; r&oacute;&#380;nymi fragmentami tego samego strumienia informacji. Exchange typu topic mo&#380;e kierowa&#263; komunikaty wed&#322;ug wzorc&oacute;w, na przyk&#322;ad <strong>orders.eu.*</strong> albo <strong>payments.failed</strong>. Nowy konsument mo&#380;e do&#322;&#261;czy&#263; przez utworzenie w&#322;asnej kolejki i bindingu bez zmiany producenta.</p><p>To praktyczne w architekturze mikroserwis&oacute;w. Us&#322;uga publikuj&#261;ca zdarzenie &bdquo;zam&oacute;wienie op&#322;acone&rdquo; nie musi zna&#263; us&#322;ug magazynu, powiadomie&#324; i raportowania. Ka&#380;da z nich ma w&#322;asn&#261; kolejk&#281;, w&#322;asne tempo pracy i w&#322;asn&#261; polityk&#281; retry.</p><h3 id="request-response-i-komunikacja-punkt-punkt">Request-response i komunikacja punkt-punkt</h3><p>RabbitMQ dobrze pasuje tak&#380;e do komunikacji request-response mi&#281;dzy us&#322;ugami. Producent wysy&#322;a &#380;&#261;danie z correlation ID, a odbiorca odpowiada na wskazany kana&#322;. W aplikacji .NET taki wzorzec mo&#380;e by&#263; prostszy do utrzymania ni&#380; tworzenie osobnych topic&oacute;w odpowiedzi i r&#281;czne zarz&#261;dzanie korelacj&#261; rekord&oacute;w.</p><p>Nie oznacza to, &#380;e ka&#380;d&#261; komunikacj&#281; synchroniczn&#261; trzeba zamienia&#263; na wiadomo&#347;&#263;. Je&#380;eli odpowied&#378; musi wr&oacute;ci&#263; w kilkudziesi&#281;ciu milisekundach, zwyk&#322;e HTTP lub gRPC cz&#281;sto b&#281;dzie czytelniejsze. RabbitMQ ma sens wtedy, gdy potrzebujesz <strong>buforowania, odporno&#347;ci na chwilow&#261; niedost&#281;pno&#347;&#263; i asynchronicznego wykonania</strong>.</p><p>W praktyce najcz&#281;stszy b&#322;&#261;d polega na traktowaniu RabbitMQ jak bazy danych. Kolejka powinna mie&#263; jasno okre&#347;lony cel, w&#322;a&#347;ciciela i polityk&#281; obs&#322;ugi b&#322;&#281;d&oacute;w. Je&#380;eli komunikaty maj&#261; by&#263; analizowane ponownie po wielu tygodniach, klasyczna kolejka mo&#380;e by&#263; niewygodnym narz&#281;dziem.</p><h2 id="kafka-ma-przewage-gdy-zdarzenia-tworza-historie-systemu">Kafka ma przewag&#281;, gdy zdarzenia tworz&#261; histori&#281; systemu</h2><h3 id="strumien-zdarzen-biznesowych">Strumie&#324; zdarze&#324; biznesowych</h3><p>Kafka pasuje do architektury, w kt&oacute;rej system zapisuje fakty, a r&oacute;&#380;ne us&#322;ugi reaguj&#261; na nie niezale&#380;nie. Przyk&#322;adowy event <strong>OrderPlaced</strong> mo&#380;e zasili&#263; magazyn, p&#322;atno&#347;ci, analityk&#281;, rekomendacje i model uczenia maszynowego. Ka&#380;da grupa konsument&oacute;w czyta ten sam topic wed&#322;ug w&#322;asnego offsetu.</p><p>To odwraca popularny spos&oacute;b projektowania. Nie my&#347;l&#281; wtedy o tym, kt&oacute;ry serwis ma dosta&#263; konkretn&#261; wiadomo&#347;&#263;, lecz o tym, <strong>jakie zdarzenie powinno zosta&#263; zachowane</strong> i kto mo&#380;e by&#263; zainteresowany jego interpretacj&#261;.</p><h3 id="replay-i-odbudowa-projekcji">Replay i odbudowa projekcji</h3><p>Najwi&#281;ksz&#261; praktyczn&#261; zalet&#261; Kafka jest mo&#380;liwo&#347;&#263; ponownego odczytania danych. Je&#380;eli nowa us&#322;uga zaczyna dzia&#322;a&#263; dzisiaj, mo&#380;e przetworzy&#263; <a href="https://kursdotnet.pl/observer-w-c-i-net-implementacja-events-i-rxnet">zdarzenia</a> z wcze&#347;niejszego okresu i zbudowa&#263; w&#322;asny widok. Jest to szczeg&oacute;lnie przydatne przy event sourcingu, audycie oraz tworzeniu nowych modeli raportowych.</p><p>RabbitMQ r&oacute;wnie&#380; oferuje strumienie, ale klasyczna kolejka nie zosta&#322;a zaprojektowana jako d&#322;ugoterminowy dziennik zdarze&#324;. Je&#380;eli historia jest cz&#281;&#347;ci&#261; produktu, trzeba &#347;wiadomie wybra&#263; retencj&#281;, format event&oacute;w, kompatybilno&#347;&#263; schemat&oacute;w i spos&oacute;b migracji. Sam fakt, &#380;e komunikat jest trwa&#322;y, nie oznacza jeszcze, &#380;e b&#281;dzie wygodny do odczytu za rok.</p><h3 id="partycje-i-kontrola-kolejnosci">Partycje i kontrola kolejno&#347;ci</h3><p>Kafka gwarantuje kolejno&#347;&#263; w obr&#281;bie pojedynczej partycji, nie ca&#322;ego topicu. Dlatego klucz partycjonowania ma znaczenie. Je&#380;eli wszystkie zdarzenia dotycz&#261;ce jednego zam&oacute;wienia powinny zachowa&#263; kolejno&#347;&#263;, u&#380;ywam identyfikatora zam&oacute;wienia jako klucza. Dzi&#281;ki temu trafiaj&#261; do tej samej partycji.</p><p>Trzeba jednak pilnowa&#263; r&oacute;wnowagi. Z&#322;y klucz mo&#380;e utworzy&#263; <strong>hot partition</strong>, czyli partycj&#281; przeci&#261;&#380;on&#261; znacznie bardziej ni&#380; pozosta&#322;e. Zwi&#281;kszenie liczby partycji nie naprawi b&#322;&#281;dnego modelu danych, a dodatkowo mo&#380;e skomplikowa&#263; skalowanie i zarz&#261;dzanie offsetami.</p><p>Kafka jest wi&#281;c mocnym wyborem dla telemetrii, &#347;ledzenia aktywno&#347;ci u&#380;ytkownik&oacute;w, integracji danych i du&#380;ych potok&oacute;w zdarze&#324;. Nie wybiera&#322;bym go tylko dlatego, &#380;e brzmi bardziej &bdquo;enterprise&rdquo;. Je&#347;li aplikacja potrzebuje g&#322;&oacute;wnie zada&#324; z retry i dead-letteringiem, jego logowy model mo&#380;e doda&#263; niepotrzebnej z&#322;o&#380;ono&#347;ci.</p><h2 id="najwieksze-roznice-pojawiaja-sie-przy-bledach-skalowaniu-i-operacjach">Najwi&#281;ksze r&oacute;&#380;nice pojawiaj&#261; si&#281; przy b&#322;&#281;dach, skalowaniu i operacjach</h2><h3 id="retry-i-dead-lettering">Retry i dead-lettering</h3><p>RabbitMQ traktuje nieudan&#261; obs&#322;ug&#281; jako problem konkretnej wiadomo&#347;ci. Mo&#380;na j&#261; ponowi&#263; po op&oacute;&#378;nieniu, przekierowa&#263; do innej kolejki albo zatrzyma&#263; po okre&#347;lonej liczbie pr&oacute;b. To dobrze pasuje do zada&#324;, kt&oacute;rych nie wolno zgubi&#263;, ale kt&oacute;rych nie nale&#380;y te&#380; bez ko&#324;ca wykonywa&#263; ponownie.</p><p>W Kafka b&#322;&#261;d konsumenta cz&#281;sto wi&#261;&#380;e si&#281; z offsetem partycji. Je&#380;eli konsument nie mo&#380;e przej&#347;&#263; dalej, trzeba zdecydowa&#263;, czy zatrzyma&#263; partycj&#281;, pomin&#261;&#263; rekord, zapisa&#263; go do osobnego topicu czy obs&#322;u&#380;y&#263; retry w aplikacji. Da si&#281; zbudowa&#263; solidny mechanizm, lecz <strong>wi&#281;cej odpowiedzialno&#347;ci spada na zesp&oacute;&#322;</strong>.</p><h3 id="gwarancje-dostarczenia">Gwarancje dostarczenia</h3><p>Oba systemy mog&#261; dzia&#322;a&#263; w modelu at-least-once, czyli wiadomo&#347;&#263; zostanie dostarczona co najmniej raz, ale w razie awarii mo&#380;e pojawi&#263; si&#281; ponownie. Z tego powodu kod konsumenta powinien by&#263; idempotentny. Operacja idempotentna daje ten sam efekt po jednym i po kilku identycznych wywo&#322;aniach.</p><p>Przy p&#322;atno&#347;ci nie powinienem wi&#281;c bezpo&#347;rednio tworzy&#263; nowej transakcji za ka&#380;dym razem, gdy pojawi si&#281; komunikat. Lepszym rozwi&#261;zaniem jest zapisanie unikalnego identyfikatora operacji i sprawdzenie przed wykonaniem, czy dana operacja nie zosta&#322;a ju&#380; zako&#324;czona. To zwykle daje wi&#281;cej bezpiecze&#324;stwa ni&#380; poleganie na has&#322;ach typu exactly-once.</p><h3 id="skalowanie-konsumentow">Skalowanie konsument&oacute;w</h3><p>W RabbitMQ zwi&#281;kszam liczb&#281; worker&oacute;w, gdy ro&#347;nie liczba zada&#324;. Broker rozdziela wiadomo&#347;ci mi&#281;dzy dost&#281;pne instancje, a mechanizmy acknowledgement i prefetch pomagaj&#261; kontrolowa&#263; obci&#261;&#380;enie. Ograniczeniem mo&#380;e by&#263; liczba kolejek, rozmiar komunikat&oacute;w, spos&oacute;b replikacji i obci&#261;&#380;enie routingu.</p><p>W Kafka skalowanie konsument&oacute;w jest zwi&#261;zane z liczb&#261; partycji. W tradycyjnej grupie konsument&oacute;w jedna partycja jest obs&#322;ugiwana przez jednego aktywnego konsumenta. Je&#347;li topic ma cztery partycje, uruchomienie dwudziestu instancji nie da dwudziestokrotnego wzrostu r&oacute;wnoleg&#322;o&#347;ci.</p><p class="read-more"><strong>Przeczytaj r&oacute;wnie&#380;: <a href="https://kursdotnet.pl/data-fabric-w-praktyce-warstwy-wzorce-i-wdrozenie-w-azure">Data fabric w praktyce - warstwy, wzorce i wdro&#380;enie w Azure</a></strong></p><h3 id="koszt-operacyjny">Koszt operacyjny</h3><p>Nie ma jednej uniwersalnej odpowiedzi na pytanie, kt&oacute;ry system jest ta&#324;szy. Koszt zale&#380;y od wolumenu danych, retencji, replikacji, chmury, monitoringu i kompetencji zespo&#322;u. Kafka cz&#281;sto wymaga bardziej &#347;wiadomego planowania partycji, retencji i przepustowo&#347;ci dysk&oacute;w, natomiast RabbitMQ mo&#380;e skomplikowa&#263; si&#281; przy bardzo du&#380;ej liczbie niezale&#380;nych przep&#322;yw&oacute;w.</p><p>W projektach .NET patrz&#281; te&#380; na biblioteki i spos&oacute;b pracy zespo&#322;u. MassTransit, RawRabbit czy bezpo&#347;redni klient AMQP mog&#261; upro&#347;ci&#263; komunikacj&#281; z RabbitMQ, a dla Kafka popularny jest klient oparty na librdkafka. Najwa&#380;niejsze jest jednak nie to, kt&oacute;ry pakiet ma wygodniejsze API, tylko czy abstrakcja nie ukrywa kluczowych decyzji o retry, offsetach i idempotencji.</p><h2 id="jak-wybrac-rozwiazanie-do-konkretnego-systemu">Jak wybra&#263; rozwi&#261;zanie do konkretnego systemu</h2><table>
<tbody>
<tr>
<th>Sytuacja</th>
<th>Lepszy pierwszy wyb&oacute;r</th>
<th>Dlaczego</th>
</tr>
<tr>
<td>Asynchroniczne zadania dla worker&oacute;w</td>
<td>RabbitMQ</td>
<td>Naturalne potwierdzenia, retry, dead-lettering i konkuruj&#261;cy konsumenci.</td>
</tr>
<tr>
<td>Request-response mi&#281;dzy us&#322;ugami</td>
<td>RabbitMQ</td>
<td>Prosty routing, correlation ID i kontrola dostarczenia pojedynczej wiadomo&#347;ci.</td>
</tr>
<tr>
<td>Wiele odbiorc&oacute;w zainteresowanych r&oacute;&#380;nymi zdarzeniami</td>
<td>RabbitMQ lub Kafka</td>
<td>RabbitMQ daje routing po stronie brokera, Kafka niezale&#380;ne grupy i retencj&#281;.</td>
</tr>
<tr>
<td>Du&#380;y strumie&#324; zdarze&#324; i telemetrii</td>
<td>Kafka</td>
<td>Partycje, batching, wysoka przepustowo&#347;&#263; i trwa&#322;y log.</td>
</tr>
<tr>
<td>Replay danych po wdro&#380;eniu nowej us&#322;ugi</td>
<td>Kafka</td>
<td>Konsument mo&#380;e rozpocz&#261;&#263; od wybranego offsetu.</td>
</tr>
<tr>
<td>Priorytety, op&oacute;&#378;nienia i wygaszanie komunikat&oacute;w</td>
<td>RabbitMQ</td>
<td>Model kolejki lepiej odwzorowuje niezale&#380;ne zadania i ich cykl &#380;ycia.</td>
</tr>
</tbody>
</table><p>Ja zaczynam od czterech pyta&#324;. Czy komunikat jest <strong>zadaniem do wykonania</strong>, czy faktem do zachowania? Czy nowy konsument powinien m&oacute;c odczyta&#263; star&#261; histori&#281;? Czy potrzebuj&#281; routingu po nag&#322;&oacute;wkach i wzorcach? Czy kolejno&#347;&#263; ma obowi&#261;zywa&#263; dla pojedynczego klucza, czy dla ca&#322;ego przep&#322;ywu?</p><p>Je&#380;eli odpowiedzi wskazuj&#261; na zadania, op&oacute;&#378;nienia, retry i komunikacj&#281; punkt-punkt, wybieram RabbitMQ. Je&#380;eli najwa&#380;niejsze s&#261; retencja, replay, wiele niezale&#380;nych grup oraz przetwarzanie strumieniowe, sk&#322;aniam si&#281; ku Kafka.</p><p>Mo&#380;liwe jest tak&#380;e u&#380;ycie obu narz&#281;dzi. Przyk&#322;adowo Kafka mo&#380;e przechowywa&#263; zdarzenia domenowe i zasila&#263; analityk&#281;, a RabbitMQ mo&#380;e obs&#322;ugiwa&#263; kr&oacute;tkotrwa&#322;e zadania operacyjne. Taki uk&#322;ad ma sens tylko wtedy, gdy granica odpowiedzialno&#347;ci jest jasna. Dwa brokery bez konkretnego powodu oznaczaj&#261; podw&oacute;jny monitoring, wi&#281;cej procedur awaryjnych i wi&#281;kszy koszt utrzymania.</p><h2 id="najlepszy-broker-to-ten-ktorego-model-pasuje-do-cyklu-zycia-wiadomosci">Najlepszy broker to ten, kt&oacute;rego model pasuje do cyklu &#380;ycia wiadomo&#347;ci</h2><p>RabbitMQ wybra&#322;bym wtedy, gdy wiadomo&#347;&#263; ma zosta&#263; dostarczona, przetworzona i ewentualnie ponowiona po b&#322;&#281;dzie. Kafka wybra&#322;bym wtedy, gdy zdarzenie ma pozosta&#263; cz&#281;&#347;ci&#261; historii, a wielu odbiorc&oacute;w powinno niezale&#380;nie odczytywa&#263; je w swoim tempie.</p><p>Najwi&#281;cej problem&oacute;w widz&#281; wtedy, gdy zesp&oacute;&#322; wybiera narz&#281;dzie na podstawie samej przepustowo&#347;ci albo popularno&#347;ci. O powodzeniu decyduj&#261; przede wszystkim <strong>semantyka komunikatu, strategia b&#322;&#281;d&oacute;w, klucz partycjonowania, retencja i idempotencja</strong>, a dopiero p&oacute;&#378;niej benchmark wykonany w warunkach zbli&#380;onych do produkcji.</p><p>Dlatego przed wdro&#380;eniem rozpisa&#322;bym jeden rzeczywisty przep&#322;yw, w&#322;&#261;cznie z awari&#261; konsumenta, ponowieniem, duplikatem i odtworzeniem danych. Je&#347;li ten scenariusz naturalnie pasuje do kolejki, RabbitMQ b&#281;dzie zwykle prostsz&#261; drog&#261;. Je&#347;li przypomina dziennik zdarze&#324;, Kafka da zespo&#322;owi wi&#281;cej mo&#380;liwo&#347;ci na dalszy rozw&oacute;j.</p>]]></content:encoded>
      <author>Przemysław Kwiatkowski</author>
      <category>Architektura i wzorce</category>
      <media:thumbnail url="https://frce8xp4ye4n.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/blog-assets/thumbnail/3eeeccb961e697b1fcb4a5d604bda9e0/rabbitmq-vs-kafka-ktory-broker-pasuje-do-twojej-aplikacji.webp"/>
      <pubDate>Tue, 08 Sep 2026 16:16:00 +0200</pubDate>
    </item>
  </channel>
</rss>