<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: nikosst</title>
    <description>The latest articles on DEV Community by nikosst (@__b63657).</description>
    <link>https://dev.to/__b63657</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2710936%2F20075e34-3f33-44d9-a08b-c1703ff98457.jpg</url>
      <title>DEV Community: nikosst</title>
      <link>https://dev.to/__b63657</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/__b63657"/>
    <language>en</language>
    <item>
      <title>Sieve στο .NET: από το πρώτο φίλτρο σε ένα σωστά σχεδιασμένο API</title>
      <dc:creator>nikosst</dc:creator>
      <pubDate>Tue, 10 Nov 2026 11:58:03 +0000</pubDate>
      <link>https://dev.to/__b63657/sieve-sto-net-apo-to-proto-philtro-se-ena-sosta-skhediasmeno-api-5691</link>
      <guid>https://dev.to/__b63657/sieve-sto-net-apo-to-proto-philtro-se-ena-sosta-skhediasmeno-api-5691</guid>
      <description>&lt;p&gt;Υπάρχει μια απαίτηση που εμφανίζεται σχεδόν σε κάθε εφαρμογή:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;«Θέλω να βλέπω μια λίστα με τα δεδομένα μου».&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Μπορεί να είναι προϊόντα σε ένα ηλεκτρονικό κατάστημα, παραγγελίες σε ένα διαχειριστικό περιβάλλον, πελάτες σε ένα CRM ή εργασίες σε ένα σύστημα διαχείρισης έργων.&lt;/p&gt;

&lt;p&gt;Στην αρχή, η υλοποίηση φαίνεται απλή. Δημιουργούμε ένα endpoint, διαβάζουμε μερικές εγγραφές από τη βάση και τις επιστρέφουμε.&lt;/p&gt;

&lt;p&gt;Στη συνέχεια αρχίζουν οι πραγματικές ανάγκες:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;«Να μπορώ να ψάχνω με βάση το όνομα».&lt;/p&gt;

&lt;p&gt;«Να εμφανίζω μόνο συγκεκριμένες κατηγορίες».&lt;/p&gt;

&lt;p&gt;«Να ταξινομώ με βάση την τιμή».&lt;/p&gt;

&lt;p&gt;«Να βλέπω είκοσι αποτελέσματα κάθε φορά».&lt;/p&gt;

&lt;p&gt;«Να μπορώ να τα συνδυάζω όλα αυτά».&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Κάπως έτσι, ένα μικρό endpoint αποκτά πολλές συνθήκες, διαφορετικές διαδρομές εκτέλεσης και επαναλαμβανόμενο κώδικα. Όταν η ίδια απαίτηση εμφανιστεί σε πέντε ή δέκα διαφορετικές λίστες, το πρόβλημα γίνεται και θέμα συντήρησης.&lt;/p&gt;

&lt;p&gt;Το &lt;strong&gt;Sieve&lt;/strong&gt; προσφέρει έναν κοινό τρόπο να περιγράφουμε και να εφαρμόζουμε filtering, sorting και pagination σε ένα .NET API.&lt;/p&gt;

&lt;p&gt;Για να το αξιοποιήσουμε σωστά, όμως, χρειάζεται να καταλάβουμε περισσότερα από το πώς εγκαθίσταται ένα NuGet package. Χρειάζεται να δούμε πώς συνδέεται με το LINQ, πότε εκτελείται το query στη βάση, ποια πεδία επιτρέπουμε στον client να χρησιμοποιήσει και ποιο είναι το κόστος αυτής της ευελιξίας.&lt;/p&gt;

&lt;p&gt;Σε αυτό το άρθρο θα ξεκινήσουμε από τις βασικές έννοιες και θα προχωρήσουμε σταδιακά σε ένα ολοκληρωμένο παράδειγμα με ASP.NET Core, EF Core και SQLite.&lt;/p&gt;

&lt;p&gt;Ο στόχος είναι διπλός: ένας αρχάριος να μπορεί να παρακολουθήσει τη λογική και ένας πιο έμπειρος developer να μπορεί να αξιολογήσει τις σχεδιαστικές αποφάσεις.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Τι είναι το Sieve
&lt;/h2&gt;

&lt;p&gt;Το Sieve είναι μια βιβλιοθήκη για .NET που μας επιτρέπει να εφαρμόζουμε:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Filtering:&lt;/strong&gt; ποια δεδομένα θέλουμε.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sorting:&lt;/strong&gt; με ποια σειρά τα θέλουμε.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pagination:&lt;/strong&gt; ποιο τμήμα των αποτελεσμάτων θέλουμε.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Η περιγραφή αυτών των επιλογών μπορεί να έρχεται μέσα από τις παραμέτρους ενός HTTP request.&lt;/p&gt;

&lt;p&gt;Για παράδειγμα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="na"&gt;GET /api/products?filters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="nc"&gt;Accessories&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;sorts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;priceCents&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;pageSize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το request διαβάζεται ως:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;«Επέστρεψε την πρώτη σελίδα, με έως είκοσι προϊόντα της κατηγορίας Accessories, ταξινομημένα κατά αύξουσα τιμή».&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Το Sieve μετατρέπει αυτή την περιγραφή σε LINQ εκφράσεις πάνω σε ένα &lt;code&gt;IQueryable&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Όταν το &lt;code&gt;IQueryable&lt;/code&gt; προέρχεται από EF Core, ο database provider μεταφράζει τις υποστηριζόμενες εκφράσεις σε SQL.&lt;/p&gt;

&lt;p&gt;Μπορούμε να σκεφτούμε το Sieve ως έναν μηχανισμό σύνθεσης queries με μια μικρή, συγκεκριμένη γλώσσα.&lt;/p&gt;

&lt;p&gt;Ο τεχνικός όρος για μια τέτοια γλώσσα είναι &lt;strong&gt;DSL — Domain-Specific Language&lt;/strong&gt;: μια γλώσσα σχεδιασμένη για έναν συγκεκριμένο σκοπό.&lt;/p&gt;

&lt;p&gt;Στην περίπτωσή μας, ο σκοπός είναι η περιγραφή φίλτρων, ταξινομήσεων και σελίδων.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Οι βασικές έννοιες χωρίς προϋποθέσεις
&lt;/h2&gt;

&lt;p&gt;Πριν περάσουμε στον κώδικα, αξίζει να ξεκαθαρίσουμε τους όρους που θα χρησιμοποιήσουμε.&lt;/p&gt;

&lt;h3&gt;
  
  
  API και endpoint
&lt;/h3&gt;

&lt;p&gt;Ένα &lt;strong&gt;API&lt;/strong&gt; είναι ένας τρόπος επικοινωνίας μεταξύ εφαρμογών.&lt;/p&gt;

&lt;p&gt;Ένα &lt;strong&gt;endpoint&lt;/strong&gt; είναι ένα συγκεκριμένο σημείο αυτού του API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="no"&gt;GET&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ένα frontend μπορεί να καλέσει αυτό το endpoint για να ζητήσει τα προϊόντα που θα εμφανίσει στην οθόνη.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query parameters
&lt;/h3&gt;

&lt;p&gt;Είναι οι παράμετροι που εμφανίζονται μετά το &lt;code&gt;?&lt;/code&gt; σε ένα URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;?page=2&amp;amp;pageSize=20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το &lt;code&gt;&amp;amp;&lt;/code&gt; χωρίζει τις διαφορετικές παραμέτρους.&lt;/p&gt;

&lt;p&gt;Στο συγκεκριμένο παράδειγμα:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Το &lt;code&gt;page&lt;/code&gt; έχει τιμή &lt;code&gt;2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Το &lt;code&gt;pageSize&lt;/code&gt; έχει τιμή &lt;code&gt;20&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Filtering
&lt;/h3&gt;

&lt;p&gt;Το φιλτράρισμα καθορίζει ποια δεδομένα θα συμπεριληφθούν.&lt;/p&gt;

&lt;p&gt;Παράδειγμα:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;«Θέλω προϊόντα με τιμή μεγαλύτερη ή ίση από 50 ευρώ».&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Sorting
&lt;/h3&gt;

&lt;p&gt;Η ταξινόμηση καθορίζει τη σειρά των αποτελεσμάτων.&lt;/p&gt;

&lt;p&gt;Παράδειγμα:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;«Θέλω πρώτα τα φθηνότερα προϊόντα».&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Pagination
&lt;/h3&gt;

&lt;p&gt;Η σελιδοποίηση χωρίζει τα αποτελέσματα σε μικρότερα τμήματα.&lt;/p&gt;

&lt;p&gt;Αν υπάρχουν 1.000 προϊόντα και ζητάμε 20 ανά σελίδα:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Η πρώτη σελίδα αντιστοιχεί στις πρώτες 20 θέσεις.&lt;/li&gt;
&lt;li&gt;Η δεύτερη στις επόμενες 20.&lt;/li&gt;
&lt;li&gt;Η τρίτη στις επόμενες 20.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Έτσι περιορίζουμε τον όγκο δεδομένων που επιστρέφει κάθε request και διευκολύνουμε την παρουσίασή τους.&lt;/p&gt;

&lt;h3&gt;
  
  
  Entity
&lt;/h3&gt;

&lt;p&gt;Ένα &lt;strong&gt;entity&lt;/strong&gt; είναι ένα αντικείμενο που αναπαριστά δεδομένα του συστήματος.&lt;/p&gt;

&lt;p&gt;Σε μια εφαρμογή με σχεσιακή βάση, συχνά αντιστοιχεί σε εγγραφές ενός πίνακα.&lt;/p&gt;

&lt;p&gt;Για παράδειγμα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nc"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nc"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  DTO
&lt;/h3&gt;

&lt;p&gt;Ένα &lt;strong&gt;DTO — Data Transfer Object&lt;/strong&gt; είναι το αντικείμενο που επιλέγουμε να μεταφέρουμε προς τον client.&lt;/p&gt;

&lt;p&gt;Μπορεί να περιέχει λιγότερα ή διαφορετικά πεδία από το entity.&lt;/p&gt;

&lt;p&gt;Η βάση μπορεί να γνωρίζει εσωτερικές πληροφορίες που δεν χρειάζεται να εκτίθενται στο response.&lt;/p&gt;

&lt;h3&gt;
  
  
  LINQ
&lt;/h3&gt;

&lt;p&gt;Το &lt;strong&gt;LINQ&lt;/strong&gt; μάς επιτρέπει να περιγράφουμε queries σε C#:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PriceCents&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το συγκεκριμένο query εκφράζει:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;«Κράτησε μόνο τα προϊόντα με τιμή τουλάχιστον 5.000 λεπτά».&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Entity Framework Core
&lt;/h3&gt;

&lt;p&gt;Το &lt;strong&gt;EF Core&lt;/strong&gt; είναι ένα ORM.&lt;/p&gt;

&lt;p&gt;Συνδέει τα αντικείμενα της εφαρμογής με τα δεδομένα της βάσης και μπορεί να μεταφράζει LINQ queries σε SQL.&lt;/p&gt;

&lt;p&gt;Το Sieve συνεργάζεται με αυτή τη διαδικασία. Η σύνδεση με τη βάση και η εκτέλεση του SQL παραμένουν ευθύνη του EF Core και του provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Ποιο πρόβλημα λύνει
&lt;/h2&gt;

&lt;p&gt;Ας υποθέσουμε ότι κατασκευάζουμε ένα endpoint προϊόντων χωρίς Sieve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsQueryable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Category&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minPriceCents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HasValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PriceCents&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;minPriceCents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxPriceCents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HasValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PriceCents&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;maxPriceCents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Για ένα μικρό endpoint, αυτή είναι μια λογική και συχνά επαρκής λύση.&lt;/p&gt;

&lt;p&gt;Η πολυπλοκότητα αυξάνεται όταν προσθέτουμε:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Περισσότερα πεδία.&lt;/li&gt;
&lt;li&gt;Διαφορετικούς operators ανά πεδίο.&lt;/li&gt;
&lt;li&gt;Πολλαπλά κριτήρια ταξινόμησης.&lt;/li&gt;
&lt;li&gt;Συνδυασμούς &lt;code&gt;AND&lt;/code&gt; και &lt;code&gt;OR&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Παρόμοια συμπεριφορά σε πολλά endpoints.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Με το Sieve, η απαίτηση μπορεί να περιγραφεί ως:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;filters=category==Accessories,priceCents&amp;gt;=5000,priceCents&amp;lt;=15000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Και να εφαρμοστεί με έναν κοινό μηχανισμό:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sieve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η αξία του βρίσκεται στην τυποποίηση μιας επαναλαμβανόμενης κατηγορίας queries.&lt;/p&gt;

&lt;p&gt;Αυτό μπορεί να προσφέρει:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Λιγότερο επαναλαμβανόμενο κώδικα.&lt;/li&gt;
&lt;li&gt;Κοινές συμβάσεις μεταξύ endpoints.&lt;/li&gt;
&lt;li&gt;Ευκολότερη προσθήκη επιτρεπόμενων πεδίων.&lt;/li&gt;
&lt;li&gt;Πιο προβλέψιμη συνεργασία backend και frontend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Υπάρχει όμως και κόστος: το API αποκτά μια γλώσσα queries που πρέπει να τεκμηριώνεται, να περιορίζεται και να συντηρείται.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Πώς λειτουργεί κάτω από την επιφάνεια
&lt;/h2&gt;

&lt;p&gt;Η βασική ροή είναι:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP request
    ↓
Παραλαβή και έλεγχος παραμέτρων
    ↓
Δημιουργία βασικού IQueryable
    ↓
Εφαρμογή filtering
    ↓
Εφαρμογή sorting
    ↓
Εφαρμογή pagination
    ↓
Επιλογή πεδίων για το response
    ↓
Μετάφραση από τον EF Core provider
    ↓
Εκτέλεση SQL
    ↓
JSON response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το Sieve συνθέτει εκφράσεις πάνω στο query.&lt;/p&gt;

&lt;p&gt;Η βάση εκτελεί το query όταν φτάσουμε σε μια λειτουργία που απαιτεί πραγματικά αποτελέσματα, όπως:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nc"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ή:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nc"&gt;CountAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αυτό μας οδηγεί στην πιο σημαντική τεχνική έννοια του άρθρου.&lt;/p&gt;

&lt;h3&gt;
  
  
  Η διαφορά ανάμεσα σε IQueryable και φορτωμένα δεδομένα
&lt;/h3&gt;

&lt;p&gt;Εξετάζουμε τον παρακάτω κώδικα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Products&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IsActive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Σε αυτό το σημείο συνήθως δεν έχουμε φορτώσει προϊόντα από τη βάση.&lt;/p&gt;

&lt;p&gt;Έχουμε δημιουργήσει την περιγραφή ενός query.&lt;/p&gt;

&lt;p&gt;Μπορούμε να συνεχίσουμε:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PriceCents&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Και αργότερα να εκτελέσουμε:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αυτή η αναβολή της εκτέλεσης ονομάζεται &lt;strong&gt;deferred execution&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Το Sieve αξιοποιεί αυτή τη δυνατότητα για να προσθέσει τις εκφράσεις του πριν εκτελεστεί το query.&lt;/p&gt;

&lt;h3&gt;
  
  
  Το λάθος που πρέπει να αποφεύγουμε
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sieve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsQueryable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Εδώ τα προϊόντα έχουν ήδη φορτωθεί στη μνήμη.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;AsQueryable()&lt;/code&gt; πάνω σε μια &lt;code&gt;List&amp;lt;Product&amp;gt;&lt;/code&gt; δεν επαναφέρει το query στη βάση.&lt;/p&gt;

&lt;p&gt;Το Sieve εφαρμόζεται πλέον στα αντικείμενα που έχουν ήδη φορτωθεί.&lt;/p&gt;

&lt;p&gt;Για ένα endpoint που διαβάζει από βάση, η επιθυμητή σειρά είναι:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsNoTracking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sieve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αυτό προϋποθέτει ότι οι εκφράσεις που προστίθενται μπορούν να μεταφραστούν από τον συγκεκριμένο database provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Οι εκδόσεις του παραδείγματος
&lt;/h2&gt;

&lt;p&gt;Το άρθρο χρησιμοποιεί:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Στοιχείο&lt;/th&gt;
&lt;th&gt;Έκδοση&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Target framework&lt;/td&gt;
&lt;td&gt;.NET 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sieve&lt;/td&gt;
&lt;td&gt;2.5.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EF Core SQLite provider&lt;/td&gt;
&lt;td&gt;10.0.14&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Η Sieve 2.5.5 είναι έκδοση του 2022 και το package στοχεύει το .NET Standard 2.1.&lt;/p&gt;

&lt;p&gt;Η ηλικία ενός release είναι στοιχείο που πρέπει να λαμβάνουμε υπόψη όταν αξιολογούμε μια εξάρτηση.&lt;/p&gt;

&lt;p&gt;Πριν από υιοθέτηση σε πραγματικό project, εξετάζουμε:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Τις λειτουργίες που χρειαζόμαστε.&lt;/li&gt;
&lt;li&gt;Την κατάσταση των releases και των σχετικών issues.&lt;/li&gt;
&lt;li&gt;Τη συμπεριφορά με τον δικό μας database provider.&lt;/li&gt;
&lt;li&gt;Την ευκολία μελλοντικής αντικατάστασης.&lt;/li&gt;
&lt;li&gt;Το κόστος συντήρησης του query contract.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Η συμβατότητα του target framework δεν αποδεικνύει από μόνη της ότι κάθε συνδυασμός runtime, EF Core και provider λειτουργεί όπως περιμένουμε.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ο κώδικας που ακολουθεί είναι εκπαιδευτικό παράδειγμα και δεν έχει εκτελεστεί ή δοκιμαστεί στο πλαίσιο αυτού του άρθρου.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  6. Η σύνταξη του Sieve
&lt;/h2&gt;

&lt;p&gt;Οι βασικές παράμετροι είναι:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;filters
sorts
page
pageSize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Filtering
&lt;/h3&gt;

&lt;p&gt;Η βασική μορφή είναι:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;πεδίο operator τιμή
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Στο πραγματικό query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;priceCents&amp;gt;=5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Στο παράδειγμά μας, οι τιμές αποθηκεύονται ως ακέραια λεπτά του ευρώ:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5000 λεπτά = 50 ευρώ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Οι βασικοί operators
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operator&lt;/th&gt;
&lt;th&gt;Σημασία&lt;/th&gt;
&lt;th&gt;Παράδειγμα&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;==&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ίσο&lt;/td&gt;
&lt;td&gt;&lt;code&gt;category==Accessories&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;!=&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Διαφορετικό&lt;/td&gt;
&lt;td&gt;&lt;code&gt;category!=Displays&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Μεγαλύτερο&lt;/td&gt;
&lt;td&gt;&lt;code&gt;priceCents&amp;gt;5000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;gt;=&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Μεγαλύτερο ή ίσο&lt;/td&gt;
&lt;td&gt;&lt;code&gt;priceCents&amp;gt;=5000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Μικρότερο&lt;/td&gt;
&lt;td&gt;&lt;code&gt;priceCents&amp;lt;15000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;=&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Μικρότερο ή ίσο&lt;/td&gt;
&lt;td&gt;&lt;code&gt;priceCents&amp;lt;=15000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@=&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Περιέχει&lt;/td&gt;
&lt;td&gt;&lt;code&gt;name@=Keyboard&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_=&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Αρχίζει με&lt;/td&gt;
&lt;td&gt;&lt;code&gt;name_=Laptop&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_-=&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Τελειώνει σε&lt;/td&gt;
&lt;td&gt;&lt;code&gt;name_-=Mouse&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;!@=&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Δεν περιέχει&lt;/td&gt;
&lt;td&gt;&lt;code&gt;name!@=Cover&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;!_=&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Δεν αρχίζει με&lt;/td&gt;
&lt;td&gt;&lt;code&gt;name!_=Laptop&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;!_-=&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Δεν τελειώνει σε&lt;/td&gt;
&lt;td&gt;&lt;code&gt;name!_-=Mouse&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Για αρκετές λειτουργίες κειμένου υπάρχει και μορφή χωρίς διάκριση πεζών και κεφαλαίων:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name@=*keyboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το &lt;code&gt;@=*&lt;/code&gt; εκφράζει contains χωρίς διάκριση πεζών και κεφαλαίων.&lt;/p&gt;

&lt;p&gt;Υπάρχουν αντίστοιχα μορφές όπως:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;==*
!=*
_=*
_-=*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  AND μεταξύ φίλτρων
&lt;/h3&gt;

&lt;p&gt;Το κόμμα συνδέει διαφορετικά φίλτρα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;category==Accessories,priceCents&amp;gt;=5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Διαβάζεται ως:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Category == Accessories
AND
PriceCents &amp;gt;= 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  OR μεταξύ τιμών
&lt;/h3&gt;

&lt;p&gt;Το &lt;code&gt;|&lt;/code&gt; περιγράφει εναλλακτικές τιμές:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;category==Accessories|Displays
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Διαβάζεται ως:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Category == Accessories
OR
Category == Displays
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  OR μεταξύ πεδίων
&lt;/h3&gt;

&lt;p&gt;Μπορούμε να εφαρμόσουμε την ίδια συνθήκη σε περισσότερα πεδία:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(name|category)@=*keyboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η σημασία είναι:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Το Name περιέχει keyboard
OR
το Category περιέχει keyboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η σύνταξη καλύπτει συγκεκριμένες μορφές συνδυασμών.&lt;/p&gt;

&lt;p&gt;Για αυθαίρετα επίπεδα ομαδοποίησης και σύνθετους Boolean κανόνες χρειάζεται ξεχωριστή αξιολόγηση ή ειδικό query contract.&lt;/p&gt;

&lt;h3&gt;
  
  
  Μια παγίδα με το «διαφορετικό»
&lt;/h3&gt;

&lt;p&gt;Το παρακάτω μπορεί να οδηγήσει σε λάθος προσδοκίες:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;category!=Accessories|Displays
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Οι εναλλακτικές τιμές συνδέονται με &lt;code&gt;OR&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Αν θέλουμε να αποκλείσουμε και τις δύο κατηγορίες, η σαφής μορφή είναι:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;category!=Accessories,category!=Displays
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Δηλαδή:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Category != Accessories
AND
Category != Displays
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sorting
&lt;/h3&gt;

&lt;p&gt;Αύξουσα ταξινόμηση:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sorts=priceCents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Φθίνουσα ταξινόμηση:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sorts=-priceCents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Πολλαπλά κριτήρια:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sorts=priceCents,name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η σειρά των κριτηρίων έχει σημασία:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Πρώτα ταξινόμηση κατά τιμή.&lt;/li&gt;
&lt;li&gt;Σε ισοβαθμία, ταξινόμηση κατά όνομα.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Pagination
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;page=2&amp;amp;pageSize=20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ζητάμε τη δεύτερη σελίδα, με είκοσι αποτελέσματα ανά σελίδα.&lt;/p&gt;

&lt;p&gt;Η offset σελιδοποίηση χρησιμοποιεί τον υπολογισμό:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;skip = (page - 1) × pageSize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Για τη δεύτερη σελίδα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;skip = (2 - 1) × 20 = 20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Παραλείπουμε τις πρώτες είκοσι θέσεις και παίρνουμε τις επόμενες είκοσι.&lt;/p&gt;

&lt;h3&gt;
  
  
  Escaping και URL encoding
&lt;/h3&gt;

&lt;p&gt;Αν μια τιμή περιέχει χαρακτήρες με ειδική σημασία για το Sieve, χρειάζεται escaping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name@=ACME\, Inc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το &lt;code&gt;\,&lt;/code&gt; δηλώνει ότι το κόμμα ανήκει στην τιμή.&lt;/p&gt;

&lt;p&gt;Αυτό είναι διαφορετικό από το URL encoding, που αφορά τη μεταφορά της παραμέτρου μέσα στο URL.&lt;/p&gt;

&lt;p&gt;Στην πράξη χρειαζόμαστε:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Σωστή σύνταξη για το Sieve.&lt;/li&gt;
&lt;li&gt;Σωστό encoding για το HTTP request.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Στα παραδείγματα θα χρησιμοποιήσουμε το &lt;code&gt;--data-urlencode&lt;/code&gt; του &lt;code&gt;curl&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Τι θα κατασκευάσουμε
&lt;/h2&gt;

&lt;p&gt;Θα δημιουργήσουμε ένα API καταλόγου προϊόντων:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="no"&gt;GET&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Θα υποστηρίζει:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filtering με βάση όνομα, κατηγορία και τιμή.&lt;/li&gt;
&lt;li&gt;Sorting με βάση αναγνωριστικό, όνομα, τιμή και ημερομηνία δημιουργίας.&lt;/li&gt;
&lt;li&gt;Pagination.&lt;/li&gt;
&lt;li&gt;Συνολικό πλήθος αποτελεσμάτων.&lt;/li&gt;
&lt;li&gt;Σταθερή ταξινόμηση στις ισοβαθμίες.&lt;/li&gt;
&lt;li&gt;Όρια στα εισερχόμενα queries.&lt;/li&gt;
&lt;li&gt;Επιστροφή μόνο ενεργών προϊόντων.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Θα χρησιμοποιήσουμε SQLite ώστε η τοπική βάση να βρίσκεται σε ένα αρχείο.&lt;/p&gt;

&lt;p&gt;Οι τιμές θα αποθηκεύονται ως ακέραια λεπτά του ευρώ.&lt;/p&gt;

&lt;p&gt;Αυτή είναι μια συγκεκριμένη επιλογή για έναν κατάλογο ενός νομίσματος με δύο δεκαδικά ψηφία. Ένα σύστημα πολλών νομισμάτων ή σύνθετων οικονομικών υπολογισμών χρειάζεται ευρύτερο μοντέλο χρημάτων και κανόνες στρογγυλοποίησης.&lt;/p&gt;

&lt;p&gt;Η δομή του project θα είναι:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SieveCatalog.Api/
├── Contracts/
│   └── ProductContracts.cs
├── Controllers/
│   └── ProductsController.cs
├── Data/
│   └── CatalogDbContext.cs
├── Models/
│   └── Product.cs
├── Properties/
│   └── launchSettings.json
├── Querying/
│   ├── CatalogSieveProcessor.cs
│   └── ProductQueryPolicy.cs
├── Program.cs
└── appsettings.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  8. Βήμα 1 — Δημιουργία του project
&lt;/h2&gt;

&lt;p&gt;Χρειαζόμαστε εγκατεστημένο .NET 10 SDK.&lt;/p&gt;

&lt;p&gt;Δημιουργούμε ένα κενό ASP.NET Core project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new web --name SieveCatalog.Api --framework net10.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Μπαίνουμε στον φάκελο:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; SieveCatalog.Api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Προσθέτουμε το Sieve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Sieve --version 2.5.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Προσθέτουμε τον SQLite provider του EF Core:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Microsoft.EntityFrameworkCore.Sqlite --version 10.0.14
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Στη συνέχεια δημιουργούμε τους φακέλους και τα αρχεία της προηγούμενης δομής.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Βήμα 2 — Το μοντέλο Product
&lt;/h2&gt;

&lt;p&gt;Στο αρχείο &lt;code&gt;Models/Product.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nc"&gt;SieveCatalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nc"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nc"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nc"&gt;Category&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nc"&gt;PriceCents&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;DateTime&lt;/span&gt; &lt;span class="nc"&gt;CreatedAtUtc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nc"&gt;IsActive&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το &lt;code&gt;IsActive&lt;/code&gt; θα χρησιμοποιείται από τον server για να αποφασίζει ποια προϊόντα εμφανίζονται.&lt;/p&gt;

&lt;p&gt;Η ημερομηνία &lt;code&gt;CreatedAtUtc&lt;/code&gt; θα χρησιμοποιείται για ταξινόμηση και στο παράδειγμα περιέχει τιμές UTC.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Βήμα 3 — Το DbContext και τα αρχικά δεδομένα
&lt;/h2&gt;

&lt;p&gt;Στο αρχείο &lt;code&gt;Data/CatalogDbContext.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;SieveCatalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nc"&gt;SieveCatalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CatalogDbContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;DbContextOptions&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CatalogDbContext&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DbContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;DbSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Products&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nc"&gt;OnModelCreating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;ModelBuilder&lt;/span&gt; &lt;span class="n"&gt;modelBuilder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modelBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Entity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HasMaxLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IsRequired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HasMaxLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IsRequired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HasIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IsActive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PriceCents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Id&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HasIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IsActive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CreatedAtUtc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Id&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HasData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Mechanical Keyboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Accessories"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;Seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Wireless Mouse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Accessories"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;Seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"USB-C Dock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Accessories"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;Seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"27-inch Monitor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Displays"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;Seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Laptop Pro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Computers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;140000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;Seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Laptop Air"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Computers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;95000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;Seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Keyboard Cover"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Accessories"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;Seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Retired Keyboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Accessories"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;Seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Travel Mouse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Accessories"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="nc"&gt;Seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;priceCents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;Category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;PriceCents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;priceCents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;IsActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;CreatedAtUtc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nc"&gt;DateTimeKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το &lt;code&gt;DbContext&lt;/code&gt; είναι το σημείο μέσω του οποίου συνθέτουμε και εκτελούμε queries για τα δεδομένα μας.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;HasData&lt;/code&gt; ορίζει τα αρχικά δεδομένα του παραδείγματος.&lt;/p&gt;

&lt;p&gt;Τα indexes είναι ενδεικτικοί υποψήφιοι για τα queries που μας ενδιαφέρουν. Η αποτελεσματικότητά τους εξαρτάται από τα πραγματικά φίλτρα, την ταξινόμηση και το execution plan.&lt;/p&gt;

&lt;p&gt;Η ύπαρξη ενός index πρέπει να συνδέεται με συγκεκριμένο workload.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Βήμα 4 — Ορίζουμε τα πεδία που εκθέτει το Sieve
&lt;/h2&gt;

&lt;p&gt;Το API χρειάζεται σαφή λίστα επιτρεπόμενων πεδίων.&lt;/p&gt;

&lt;p&gt;Για το παράδειγμά μας:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Όνομα στο API&lt;/th&gt;
&lt;th&gt;Ιδιότητα C#&lt;/th&gt;
&lt;th&gt;Filtering&lt;/th&gt;
&lt;th&gt;Sorting&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Όχι&lt;/td&gt;
&lt;td&gt;Ναι&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ναι&lt;/td&gt;
&lt;td&gt;Ναι&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;category&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Category&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ναι&lt;/td&gt;
&lt;td&gt;Όχι&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;priceCents&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PriceCents&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ναι&lt;/td&gt;
&lt;td&gt;Ναι&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;created&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CreatedAtUtc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Όχι&lt;/td&gt;
&lt;td&gt;Ναι&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Θα χρησιμοποιήσουμε το Fluent API του Sieve.&lt;/p&gt;

&lt;p&gt;Στο αρχείο &lt;code&gt;Querying/CatalogSieveProcessor.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;Sieve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;Sieve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;SieveCatalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nc"&gt;SieveCatalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Querying&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CatalogSieveProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;IOptions&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SieveOptions&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SieveProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="nc"&gt;SievePropertyMapper&lt;/span&gt; &lt;span class="nc"&gt;MapProperties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;SievePropertyMapper&lt;/span&gt; &lt;span class="n"&gt;mapper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;mapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Property&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CanSort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HasName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;mapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Property&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CanFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CanSort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HasName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;mapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Property&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CanFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HasName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;mapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Property&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PriceCents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CanFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CanSort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HasName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"priceCents"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;mapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Property&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CreatedAtUtc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CanSort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HasName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;mapper&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Οι βασικές λειτουργίες είναι:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CanFilter()&lt;/code&gt;: επιτρέπει filtering.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CanSort()&lt;/code&gt;: επιτρέπει sorting.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HasName()&lt;/code&gt;: καθορίζει το εξωτερικό όνομα του πεδίου.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Γιατί χρησιμοποιούμε aliases
&lt;/h3&gt;

&lt;p&gt;Ο client γνωρίζει το όνομα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η εφαρμογή γνωρίζει την ιδιότητα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nc"&gt;CreatedAtUtc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αν αλλάξει η ονομασία της ιδιότητας C#, μπορούμε να διατηρήσουμε το εξωτερικό όνομα και να προσαρμόσουμε το mapping.&lt;/p&gt;

&lt;p&gt;Έτσι τα query fields αντιμετωπίζονται ως μέρος του API contract.&lt;/p&gt;

&lt;h3&gt;
  
  
  Η εναλλακτική με attributes
&lt;/h3&gt;

&lt;p&gt;Το Sieve υποστηρίζει και attributes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;Sieve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Attributes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nc"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Sieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;CanFilter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;CanSort&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nc"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Για μικρά projects, αυτή είναι μια απλή επιλογή.&lt;/p&gt;

&lt;p&gt;Το Fluent API είναι χρήσιμο όταν θέλουμε οι αποφάσεις για τα queries να συγκεντρώνονται σε ξεχωριστό σημείο.&lt;/p&gt;

&lt;p&gt;Στο project του άρθρου συνεχίζουμε με το Fluent API.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Βήμα 5 — Σχεδιάζουμε request και response
&lt;/h2&gt;

&lt;p&gt;Στο αρχείο &lt;code&gt;Contracts/ProductContracts.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ComponentModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataAnnotations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nc"&gt;SieveCatalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contracts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductsQuery&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;StringLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;Filters&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;StringLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;Sorts&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nc"&gt;Page&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nc"&gt;PageSize&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;ProductDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nc"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nc"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nc"&gt;PriceCents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;PagedResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nc"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nc"&gt;PageSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nc"&gt;TotalCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nc"&gt;TotalPages&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nc"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Ceiling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;TotalCount&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nc"&gt;PageSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nc"&gt;HasNextPage&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nc"&gt;Page&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nc"&gt;TotalPages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Γιατί δικό μας request model
&lt;/h3&gt;

&lt;p&gt;Το Sieve παρέχει ήδη το &lt;code&gt;SieveModel&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Χρησιμοποιούμε δικό μας request model για να ορίσουμε:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Προεπιλεγμένη σελίδα.&lt;/li&gt;
&lt;li&gt;Προεπιλεγμένο μέγεθος σελίδας.&lt;/li&gt;
&lt;li&gt;Μέγιστο μέγεθος σελίδας.&lt;/li&gt;
&lt;li&gt;Μέγιστο μήκος φίλτρων και ταξινομήσεων.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Αργότερα θα το μετατρέψουμε σε &lt;code&gt;SieveModel&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Το όριο &lt;code&gt;Page &amp;lt;= 10000&lt;/code&gt; είναι επιλογή πολιτικής του συγκεκριμένου παραδείγματος.&lt;/p&gt;

&lt;p&gt;Το κατάλληλο όριο για μια πραγματική εφαρμογή εξαρτάται από τον όγκο των δεδομένων και τον τρόπο πλοήγησης.&lt;/p&gt;

&lt;h3&gt;
  
  
  Γιατί DTO
&lt;/h3&gt;

&lt;p&gt;Το response περιέχει μόνο τα πεδία που επιλέξαμε.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;IsActive&lt;/code&gt;, για παράδειγμα, χρησιμοποιείται εσωτερικά χωρίς να εμφανίζεται στο response.&lt;/p&gt;

&lt;p&gt;Το ποια πεδία επιστρέφονται και το ποια πεδία χρησιμοποιούνται σε φίλτρα είναι διαφορετικές αποφάσεις.&lt;/p&gt;

&lt;h2&gt;
  
  
  13. Βήμα 6 — Προσθέτουμε πολιτική για τα queries
&lt;/h2&gt;

&lt;p&gt;Μια δυναμική γλώσσα queries χρειάζεται όρια.&lt;/p&gt;

&lt;p&gt;Στο παράδειγμά μας θα επιτρέπουμε:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Έως τρία κριτήρια ταξινόμησης από τον client.&lt;/li&gt;
&lt;li&gt;Έως οκτώ filter terms.&lt;/li&gt;
&lt;li&gt;Έως τρία εναλλακτικά πεδία ανά term.&lt;/li&gt;
&lt;li&gt;Έως δέκα εναλλακτικές τιμές ανά term.&lt;/li&gt;
&lt;li&gt;Operators συμβατούς με τον τύπο του πεδίου.&lt;/li&gt;
&lt;li&gt;Ακέραιες τιμές για το &lt;code&gt;priceCents&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Θα προσθέτουμε επίσης το &lt;code&gt;id&lt;/code&gt; ως κριτήριο ταξινόμησης, όταν δεν υπάρχει ήδη.&lt;/p&gt;

&lt;p&gt;Στο αρχείο &lt;code&gt;Querying/ProductQueryPolicy.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Globalization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;Sieve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;SieveCatalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contracts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nc"&gt;SieveCatalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Querying&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InvalidProductQueryException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductQueryPolicy&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nc"&gt;HashSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;SortFields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StringComparer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Ordinal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;"priceCents"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;"created"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nc"&gt;HashSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;TextOperators&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StringComparer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Ordinal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"=="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"!="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"@="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"_="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"_-="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;"!@="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"!_="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"!_-="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;"==*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"!=*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"@=*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"_=*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"_-=*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;"!@=*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"!_=*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"!_-=*"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nc"&gt;HashSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;NumberOperators&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StringComparer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Ordinal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"=="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"!="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;="&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;SieveModel&lt;/span&gt; &lt;span class="nc"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;ProductsQuery&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sorts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sorts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"-created"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sorts&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sorts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InvalidProductQueryException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s2"&gt;"Use at most 3 sort fields."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sorts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StartsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nc"&gt;SortFields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InvalidProductQueryException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s2"&gt;"Unsupported sort field."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;sorts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;"-id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;sorts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SieveModel&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Filters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;Sorts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sorts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;Page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;PageSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PageSize&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;filters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetFiltersParsed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filters&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InvalidProductQueryException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s2"&gt;"Use at most 8 filter terms."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Operator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Values&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
                &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Names&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
                &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InvalidProductQueryException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s2"&gt;"Invalid filter or too many alternatives."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nc"&gt;TextOperators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Operator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InvalidProductQueryException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="s2"&gt;"Unsupported text filter."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"priceCents"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hasInvalidValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TryParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="nc"&gt;NumberStyles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="nc"&gt;CultureInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;InvariantCulture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nc"&gt;NumberOperators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Operator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;hasInvalidValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InvalidProductQueryException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="s2"&gt;"priceCents requires integers "&lt;/span&gt;
                            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"and a numeric operator."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InvalidProductQueryException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="s2"&gt;"Unsupported filter field."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Τι προσφέρει αυτό το επίπεδο
&lt;/h3&gt;

&lt;p&gt;Επιτρέπουμε:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;priceCents&amp;gt;=5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Απορρίπτουμε:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;priceCents@=cheap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το δεύτερο request προσπαθεί να εφαρμόσει λειτουργία κειμένου πάνω σε αριθμητικό πεδίο.&lt;/p&gt;

&lt;p&gt;Επιπλέον, το εξωτερικό όνομα είναι συγκεκριμένο:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;priceCents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η πολιτική χρησιμοποιεί ακριβή αντιστοίχιση ονομάτων. Θα ευθυγραμμίσουμε αυτή την επιλογή με το &lt;code&gt;CaseSensitive: true&lt;/code&gt; του Sieve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Γιατί υπάρχει πληροφορία πεδίων και στο mapping και στην policy
&lt;/h3&gt;

&lt;p&gt;Το mapping απαντά:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;«Ποια ιδιότητα αντιστοιχεί σε αυτό το όνομα και επιτρέπεται να χρησιμοποιηθεί;»&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Η policy απαντά:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;«Ποιους operators και ποια όρια επιτρέπει αυτό το endpoint;»&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Στο μικρό παράδειγμα κρατάμε εμφανείς τις δύο ευθύνες.&lt;/p&gt;

&lt;p&gt;Σε μεγαλύτερο σύστημα μπορούμε να οργανώσουμε αυτές τις πληροφορίες σε κοινά metadata, ώστε να αποφεύγονται αποκλίσεις.&lt;/p&gt;

&lt;h3&gt;
  
  
  Το όριο αυτού του validator
&lt;/h3&gt;

&lt;p&gt;Η policy ελέγχει τα terms που επιστρέφει ο parser του Sieve.&lt;/p&gt;

&lt;p&gt;Δεν αποτελεί ανεξάρτητο, πλήρη parser που αποδεικνύει την εγκυρότητα κάθε χαρακτήρα του αρχικού input.&lt;/p&gt;

&lt;p&gt;Για δημόσιο API με αυστηρές απαιτήσεις γραμματικής χρειάζεται επιπλέον σχεδιασμός: αυστηρότερος έλεγχος της αρχικής σύνταξης ή περισσότερο περιορισμένο request model.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;ThrowExceptions&lt;/code&gt; επίσης δεν αντικαθιστά ένα πλήρες validation contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  14. Βήμα 7 — Ο controller
&lt;/h2&gt;

&lt;p&gt;Στο αρχείο &lt;code&gt;Controllers/ProductsController.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AspNetCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;Sieve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;Sieve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;SieveCatalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contracts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;SieveCatalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;SieveCatalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Querying&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nc"&gt;SieveCatalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Controllers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"api/products"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductsController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;CatalogDbContext&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;ISieveProcessor&lt;/span&gt; &lt;span class="n"&gt;sieve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ControllerBase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ActionResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PagedResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProductDto&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;FromQuery&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nc"&gt;ProductsQuery&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;SieveModel&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProductQueryPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InvalidProductQueryException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Problem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;StatusCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Status400BadRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Invalid product query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Products&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsNoTracking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IsActive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;filtered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sieve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;applySorting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;applyPagination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;totalCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;filtered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CountAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sieve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;filtered&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;applyFiltering&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ProductDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PriceCents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PagedResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProductDto&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PageSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;totalCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αυτό είναι το κεντρικό σημείο του παραδείγματος.&lt;/p&gt;

&lt;h3&gt;
  
  
  Πρώτα οι υποχρεωτικοί περιορισμοί
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IsActive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η απόφαση ότι το endpoint εμφανίζει ενεργά προϊόντα ανήκει στον server.&lt;/p&gt;

&lt;p&gt;Ο client μπορεί να περιορίσει περαιτέρω αυτό το σύνολο μέσω φίλτρων.&lt;/p&gt;

&lt;h3&gt;
  
  
  Πρώτο Apply για filtering
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;filtered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sieve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;applySorting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;applyPagination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Σε αυτό το στάδιο εφαρμόζουμε μόνο τα φίλτρα.&lt;/p&gt;

&lt;p&gt;Χρειαζόμαστε το σύνολο όλων των αποτελεσμάτων που ικανοποιούν τις συνθήκες.&lt;/p&gt;

&lt;h3&gt;
  
  
  Μετράμε πριν από το pagination
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;totalCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;filtered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CountAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αν υπάρχουν 237 προϊόντα που ταιριάζουν και ζητάμε 20 ανά σελίδα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TotalCount = 237
Items.Count &amp;lt;= 20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αν μετρούσαμε μετά τη σελιδοποίηση, θα παίρναμε το πλήθος της συγκεκριμένης σελίδας.&lt;/p&gt;

&lt;h3&gt;
  
  
  Δεύτερο Apply για sorting και pagination
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sieve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;filtered&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;applyFiltering&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Τα φίλτρα έχουν ήδη εφαρμοστεί.&lt;/p&gt;

&lt;p&gt;Τώρα προσθέτουμε την ταξινόμηση και τη σελιδοποίηση.&lt;/p&gt;

&lt;h3&gt;
  
  
  Projection πριν από τη φόρτωση
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ProductDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PriceCents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αυτή η επιλογή πεδίων ονομάζεται &lt;strong&gt;projection&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Περιγράφουμε ποια δεδομένα χρειαζόμαστε πριν εκτελέσουμε το query.&lt;/p&gt;

&lt;h3&gt;
  
  
  Γιατί AsNoTracking
&lt;/h3&gt;

&lt;p&gt;Το endpoint διαβάζει δεδομένα.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;AsNoTracking()&lt;/code&gt; δηλώνει ότι δεν χρειαζόμαστε παρακολούθηση των entities για μεταγενέστερο update.&lt;/p&gt;

&lt;p&gt;Στο συγκεκριμένο projection επιστρέφουμε μόνο scalar δεδομένα μέσα σε DTO, επομένως δεν επιστρέφονται entities για tracking.&lt;/p&gt;

&lt;p&gt;Η παρουσία του &lt;code&gt;AsNoTracking()&lt;/code&gt; κάνει επίσης σαφή την πρόθεση του query.&lt;/p&gt;

&lt;h2&gt;
  
  
  15. Βήμα 8 — Το Program.cs
&lt;/h2&gt;

&lt;p&gt;Αντικαθιστούμε το περιεχόμενο του &lt;code&gt;Program.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;Sieve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;Sieve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;SieveCatalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;SieveCatalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Querying&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AddControllers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AddProblemDetails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AddDbContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CatalogDbContext&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UseSqlite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Catalog"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Configure&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SieveOptions&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Sieve"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AddScoped&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="nc"&gt;ISieveProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;CatalogSieveProcessor&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UseExceptionHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Τοπικό εκπαιδευτικό παράδειγμα.&lt;/span&gt;
&lt;span class="c1"&gt;// Για κανονική διαχείριση schema χρησιμοποιούμε migrations.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IsDevelopment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CreateAsyncScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ServiceProvider&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetRequiredService&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CatalogDbContext&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EnsureCreatedAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MapControllers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dependency injection
&lt;/h3&gt;

&lt;p&gt;Ο controller χρειάζεται:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nc"&gt;CatalogDbContext&lt;/span&gt;
&lt;span class="nc"&gt;ISieveProcessor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η εφαρμογή δημιουργεί και παρέχει αυτές τις υπηρεσίες μέσω του dependency injection container.&lt;/p&gt;

&lt;p&gt;Με:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AddScoped&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="nc"&gt;ISieveProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;CatalogSieveProcessor&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;δηλώνουμε ότι όπου ζητείται &lt;code&gt;ISieveProcessor&lt;/code&gt;, θα χρησιμοποιείται ο δικός μας processor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Γιατί Scoped
&lt;/h3&gt;

&lt;p&gt;Το scoped lifetime δημιουργεί μια instance μέσα στο scope του request.&lt;/p&gt;

&lt;p&gt;Είναι φυσική επιλογή για υπηρεσίες που συμμετέχουν στη διαχείριση ενός request και μπορούν να συνεργάζονται με άλλες scoped υπηρεσίες.&lt;/p&gt;

&lt;h3&gt;
  
  
  ProblemDetails
&lt;/h3&gt;

&lt;p&gt;Τα &lt;code&gt;ProblemDetails&lt;/code&gt; παρέχουν κοινή δομή για error responses.&lt;/p&gt;

&lt;p&gt;Στον controller επιστρέφουμε τα αναγνωρισμένα validation errors ως &lt;code&gt;400 Bad Request&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Τα μη αναμενόμενα exceptions ακολουθούν τον κεντρικό χειρισμό σφαλμάτων.&lt;/p&gt;

&lt;h3&gt;
  
  
  EnsureCreated και migrations
&lt;/h3&gt;

&lt;p&gt;Το &lt;code&gt;EnsureCreatedAsync()&lt;/code&gt; διευκολύνει το τοπικό παράδειγμα με καινούρια βάση.&lt;/p&gt;

&lt;p&gt;Για εφαρμογή που θα εξελίσσει το schema της χρειάζεται στρατηγική migrations.&lt;/p&gt;

&lt;p&gt;Τα &lt;code&gt;EnsureCreated&lt;/code&gt; και migrations δεν πρέπει να αντιμετωπίζονται ως εναλλάξιμοι μηχανισμοί αρχικοποίησης της ίδιας βάσης.&lt;/p&gt;

&lt;p&gt;Επίσης, το &lt;code&gt;EnsureCreatedAsync()&lt;/code&gt; δεν αποτελεί μηχανισμό εφαρμογής μεταγενέστερων αλλαγών στο schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  16. Βήμα 9 — Το appsettings.json
&lt;/h2&gt;

&lt;p&gt;Στο αρχείο &lt;code&gt;appsettings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"ConnectionStrings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;"Catalog"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Data Source=catalog.db"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"Sieve"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;"CaseSensitive"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"DefaultPageSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"MaxPageSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"ThrowExceptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"IgnoreNullsOnNotEqual"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"Logging"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;"LogLevel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;"Default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Information"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;"Microsoft.AspNetCore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Warning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;"Microsoft.EntityFrameworkCore.Database.Command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Information"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Οι βασικές επιλογές:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ρύθμιση&lt;/th&gt;
&lt;th&gt;Ευθύνη&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CaseSensitive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Αν τα ονόματα των πεδίων διακρίνουν πεζά και κεφαλαία&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DefaultPageSize&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Προεπιλεγμένο μέγεθος σελίδας&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MaxPageSize&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ανώτατο πλήθος αποτελεσμάτων που επιστρέφει η σελιδοποίηση&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ThrowExceptions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Αν τα exceptions προωθούνται αντί να καταστέλλονται&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IgnoreNullsOnNotEqual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Επηρεάζει τη συμμετοχή των &lt;code&gt;null&lt;/code&gt; στις συγκρίσεις διαφορετικότητας&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Στη συγκεκριμένη έκδοση, οι προεπιλογές των &lt;code&gt;DefaultPageSize&lt;/code&gt; και &lt;code&gt;MaxPageSize&lt;/code&gt; είναι &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Χρειάζεται επομένως να ορίζουμε συνειδητά τη δική μας πολιτική.&lt;/p&gt;

&lt;p&gt;Στο παράδειγμα τα βασικά όρια υπάρχουν και στο request model. Η ρύθμιση του processor προσθέτει ένα ακόμη επίπεδο προστασίας.&lt;/p&gt;

&lt;h3&gt;
  
  
  CaseSensitive και σύγκριση τιμών
&lt;/h3&gt;

&lt;p&gt;Με:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"CaseSensitive"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;το:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;priceCents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;είναι ένα συγκεκριμένο όνομα πεδίου.&lt;/p&gt;

&lt;p&gt;Αυτό είναι διαφορετικό από το αν:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;keyboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ταιριάζει με:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Keyboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η σύγκριση των τιμών επηρεάζεται από τον operator, τη μετάφραση του provider και τους κανόνες σύγκρισης της βάσης.&lt;/p&gt;

&lt;p&gt;Για παράδειγμα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name@=*keyboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;εκφράζει contains χωρίς διάκριση πεζών και κεφαλαίων.&lt;/p&gt;

&lt;p&gt;Η συμπεριφορά σε γλώσσες, τόνους και ειδικούς χαρακτήρες χρειάζεται αξιολόγηση με τη συγκεκριμένη βάση.&lt;/p&gt;

&lt;h2&gt;
  
  
  17. Βήμα 10 — Τοπική εκκίνηση
&lt;/h2&gt;

&lt;p&gt;Στο αρχείο &lt;code&gt;Properties/launchSettings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"profiles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;"commandName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Project"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;"launchBrowser"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;"applicationUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"http://localhost:5080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;"environmentVariables"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;"ASPNETCORE_ENVIRONMENT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Development"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η τοπική εκκίνηση γίνεται με:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet run --launch-profile http
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το development profile ενεργοποιεί την τοπική αρχικοποίηση της βάσης.&lt;/p&gt;

&lt;p&gt;Τα παρακάτω είναι παραδείγματα κλήσεων για τον αναγνώστη.&lt;/p&gt;

&lt;h3&gt;
  
  
  Όλα τα ορατά προϊόντα
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"http://localhost:5080/api/products"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η προεπιλεγμένη ταξινόμηση γίνεται:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-created,id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Δηλαδή:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Νεότερα προϊόντα πρώτα.&lt;/li&gt;
&lt;li&gt;Σε ίδια ημερομηνία, αύξουσα σειρά αναγνωριστικού.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Μια συγκεκριμένη κατηγορία
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl --get &lt;span class="s2"&gt;"http://localhost:5080/api/products"&lt;/span&gt; \
  --data-urlencode &lt;span class="s2"&gt;"filters=category==Accessories"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Τιμή μεταξύ 50 και 150 ευρώ
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl --get &lt;span class="s2"&gt;"http://localhost:5080/api/products"&lt;/span&gt; \
  --data-urlencode &lt;span class="s2"&gt;"filters=priceCents&amp;gt;=5000,priceCents&amp;lt;=15000"&lt;/span&gt; \
  --data-urlencode &lt;span class="s2"&gt;"sorts=priceCents"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Αναζήτηση στο όνομα
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl --get &lt;span class="s2"&gt;"http://localhost:5080/api/products"&lt;/span&gt; \
  --data-urlencode &lt;span class="s2"&gt;"filters=name@=*keyboard"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Συνδυασμός φίλτρου, sorting και pagination
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl --get &lt;span class="s2"&gt;"http://localhost:5080/api/products"&lt;/span&gt; \
  --data-urlencode &lt;span class="s2"&gt;"filters=category==Accessories"&lt;/span&gt; \
  --data-urlencode &lt;span class="s2"&gt;"sorts=priceCents"&lt;/span&gt; \
  --data-urlencode &lt;span class="s2"&gt;"page=1"&lt;/span&gt; \
  --data-urlencode &lt;span class="s2"&gt;"pageSize=2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Τα multiline παραδείγματα χρησιμοποιούν σύνταξη Bash.&lt;/p&gt;

&lt;p&gt;Σε PowerShell μπορούμε να χρησιμοποιήσουμε μία γραμμή:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;curl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exe&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"http://localhost:5080/api/products"&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;urlencode&lt;/span&gt; &lt;span class="s2"&gt;"filters=category==Accessories"&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;urlencode&lt;/span&gt; &lt;span class="s2"&gt;"sorts=priceCents"&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;urlencode&lt;/span&gt; &lt;span class="s2"&gt;"page=1"&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;urlencode&lt;/span&gt; &lt;span class="s2"&gt;"pageSize=2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Με βάση τα αρχικά δεδομένα και τους κανόνες του παραδείγματος, το αναμενόμενο response έχει τη μορφή:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Keyboard Cover"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Accessories"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;"priceCents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Wireless Mouse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Accessories"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;"priceCents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3500&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"pageSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"totalCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"totalPages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"hasNextPage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το ανενεργό &lt;code&gt;Retired Keyboard&lt;/code&gt; δεν συμμετέχει ούτε στα items ούτε στο συνολικό πλήθος.&lt;/p&gt;

&lt;h2&gt;
  
  
  18. Γιατί χρειάζεται σταθερή ταξινόμηση
&lt;/h2&gt;

&lt;p&gt;Στα δεδομένα μας υπάρχουν δύο προϊόντα με ίδια τιμή:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wireless Mouse → 3500
Travel Mouse   → 3500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η ταξινόμηση:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sorts=priceCents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;δεν καθορίζει πλήρως τη μεταξύ τους σειρά.&lt;/p&gt;

&lt;p&gt;Η policy προσθέτει:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sorts=priceCents,id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Έτσι η ισοβαθμία λύνεται με ένα μοναδικό αναγνωριστικό.&lt;/p&gt;

&lt;p&gt;Αυτό έχει σημασία στη σελιδοποίηση.&lt;/p&gt;

&lt;p&gt;Χωρίς πλήρως καθορισμένη σειρά, ισότιμα αποτελέσματα μπορούν να εμφανιστούν με διαφορετική σειρά μεταξύ queries.&lt;/p&gt;

&lt;p&gt;Η μοναδική ταξινόμηση αντιμετωπίζει την ασάφεια των ισοβαθμιών.&lt;/p&gt;

&lt;p&gt;Οι ταυτόχρονες εισαγωγές και διαγραφές εξακολουθούν να μπορούν να μετακινήσουν τις θέσεις των δεδομένων όταν χρησιμοποιούμε offset pagination.&lt;/p&gt;

&lt;h2&gt;
  
  
  19. Οι αποφάσεις που χρειάζονται προσοχή σε production
&lt;/h2&gt;

&lt;h3&gt;
  
  
  19.1 Οι περιορισμοί πρόσβασης προηγούνται
&lt;/h3&gt;

&lt;p&gt;Σε μια εφαρμογή πολλών οργανισμών μπορεί να υπάρχει:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nc"&gt;TenantId&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ο οργανισμός στον οποίο έχει πρόσβαση ο χρήστης πρέπει να προκύπτει από έμπιστο server-side context.&lt;/p&gt;

&lt;p&gt;Ένα ενδεικτικό βασικό query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Products&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsNoTracking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TenantId&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;currentTenantId&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IsActive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Στη συνέχεια εφαρμόζονται τα φίλτρα του client πάνω στο &lt;code&gt;visible&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;TenantId&lt;/code&gt; αυτού του παραδείγματος είναι επιπλέον πεδίο ενός διαφορετικού, multi-tenant μοντέλου.&lt;/p&gt;

&lt;p&gt;Η ουσιαστική αρχή είναι ότι οι περιορισμοί πρόσβασης ανήκουν στον server.&lt;/p&gt;

&lt;p&gt;Τα global query filters του EF Core μπορούν να βοηθήσουν στην εφαρμογή κοινών περιορισμών, όπως tenant isolation και soft deletion.&lt;/p&gt;

&lt;h3&gt;
  
  
  19.2 Ένα πεδίο μπορεί να αποκαλύπτει πληροφορία μέσω φίλτρων
&lt;/h3&gt;

&lt;p&gt;Ένα ευαίσθητο πεδίο μπορεί να απουσιάζει από το DTO αλλά να παραμένει προσβάσιμο μέσω φίλτρων.&lt;/p&gt;

&lt;p&gt;Αν ο client μπορεί να φιλτράρει πάνω σε αυτό και να βλέπει &lt;code&gt;TotalCount&lt;/code&gt;, ίσως μπορεί να συναγάγει πληροφορίες.&lt;/p&gt;

&lt;p&gt;Γι’ αυτό αποφασίζουμε ξεχωριστά:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ποια πεδία επιστρέφονται.&lt;/li&gt;
&lt;li&gt;Ποια πεδία φιλτράρονται.&lt;/li&gt;
&lt;li&gt;Ποια πεδία ταξινομούνται.&lt;/li&gt;
&lt;li&gt;Αν παρέχεται συνολικό πλήθος.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  19.3 Το μικρό pageSize δεν εγγυάται φθηνό query
&lt;/h3&gt;

&lt;p&gt;Ένα request που επιστρέφει δέκα προϊόντα μπορεί να χρειάζεται αναζήτηση σε εκατομμύρια εγγραφές.&lt;/p&gt;

&lt;p&gt;Παράδειγμα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;filters=name@=*a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το κόστος εξαρτάται από το φίλτρο, τα indexes, τη βάση και το πλήθος των υποψήφιων εγγραφών.&lt;/p&gt;

&lt;p&gt;Τα όρια στο input είναι χρήσιμα. Η πραγματική απόδοση απαιτεί αξιολόγηση στα πραγματικά δεδομένα.&lt;/p&gt;

&lt;h3&gt;
  
  
  19.4 Το MaxPageSize χρειάζεται σαφή πολιτική
&lt;/h3&gt;

&lt;p&gt;Όταν ο client ζητά υπερβολικά μεγάλο page size, μπορούμε:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Να απορρίψουμε το request.&lt;/li&gt;
&lt;li&gt;Να το προσαρμόσουμε σε επιτρεπόμενη τιμή.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Στο παράδειγμα επιλέξαμε απόρριψη:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nc"&gt;PageSize&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Έτσι το request και τα metadata έχουν την ίδια σημασία.&lt;/p&gt;

&lt;p&gt;Αν επιλέξουμε προσαρμογή, η αποτελεσματική τιμή πρέπει να χρησιμοποιείται με συνέπεια στον υπολογισμό του offset και στο response.&lt;/p&gt;

&lt;p&gt;Στη Sieve 2.5.5, ο υπολογισμός του &lt;code&gt;Skip&lt;/code&gt; χρησιμοποιεί το ζητούμενο page size, ενώ το &lt;code&gt;Take&lt;/code&gt; περιορίζεται από το μέγιστο όριο.&lt;/p&gt;

&lt;p&gt;Η κανονικοποίηση του input πριν από το &lt;code&gt;Apply&lt;/code&gt; αποφεύγει ασυμφωνίες αυτής της μορφής.&lt;/p&gt;

&lt;h3&gt;
  
  
  19.5 Το Count και τα items προκύπτουν από δύο queries
&lt;/h3&gt;

&lt;p&gt;Ο controller εκτελεί:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;filtered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CountAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;και έπειτα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ανάμεσα στα δύο queries μπορεί να αλλάξουν τα δεδομένα.&lt;/p&gt;

&lt;p&gt;Σε έναν συνηθισμένο κατάλογο αυτή η χρονική απόκλιση μπορεί να είναι αποδεκτή.&lt;/p&gt;

&lt;p&gt;Σε σύστημα που χρειάζεται αυστηρά συνεπή εικόνα εξετάζουμε transaction, isolation level και τις δυνατότητες της βάσης.&lt;/p&gt;

&lt;p&gt;Το ακριβές συνολικό πλήθος έχει επίσης κόστος.&lt;/p&gt;

&lt;p&gt;Ορισμένα interfaces χρειάζονται μόνο να γνωρίζουν αν υπάρχει επόμενη σελίδα. Εκεί μπορεί να ταιριάζει σχεδιασμός που ζητά ένα επιπλέον αποτέλεσμα αντί για ξεχωριστό &lt;code&gt;Count&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  19.6 Δεν εκτελούμε παράλληλα operations στο ίδιο DbContext
&lt;/h3&gt;

&lt;p&gt;Η παρακάτω προσέγγιση πρέπει να αποφεύγεται στην ίδια instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;countTask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filtered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CountAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;itemsTask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WhenAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;countTask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;itemsTask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το EF Core δεν υποστηρίζει πολλαπλές παράλληλες λειτουργίες στο ίδιο &lt;code&gt;DbContext&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Στο παράδειγμά μας οι λειτουργίες εκτελούνται διαδοχικά.&lt;/p&gt;

&lt;h3&gt;
  
  
  19.7 Διαχωρίζουμε τα λάθη του client από τα λάθη του server
&lt;/h3&gt;

&lt;p&gt;Το:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;priceCents&amp;gt;=abc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;είναι λάθος input.&lt;/p&gt;

&lt;p&gt;Ένα query που αποτυγχάνει επειδή ο provider δεν μεταφράζει μια έκφραση είναι διαφορετική κατηγορία προβλήματος.&lt;/p&gt;

&lt;p&gt;Μετατρέπουμε σε &lt;code&gt;400&lt;/code&gt; τα σφάλματα που αναγνωρίζει η query policy.&lt;/p&gt;

&lt;p&gt;Τα υπόλοιπα ακολουθούν τον κεντρικό χειρισμό σφαλμάτων.&lt;/p&gt;

&lt;p&gt;Η γενική μετατροπή κάθε exception σε &lt;code&gt;400 Bad Request&lt;/code&gt; δυσκολεύει τη διάγνωση προβλημάτων της εφαρμογής.&lt;/p&gt;

&lt;h2&gt;
  
  
  20. Απόδοση και database provider
&lt;/h2&gt;

&lt;p&gt;Το Sieve οργανώνει τη σύνθεση του query.&lt;/p&gt;

&lt;p&gt;Η απόδοση προκύπτει από το query που τελικά εκτελείται και από το περιβάλλον της βάσης.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contains και indexes
&lt;/h3&gt;

&lt;p&gt;Η αναζήτηση:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name@=Keyboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;έχει διαφορετικά χαρακτηριστικά από μια αναζήτηση με ισότητα.&lt;/p&gt;

&lt;p&gt;Ένα συνηθισμένο index δεν επιταχύνει αυτόματα κάθε αναζήτηση υποσυμβολοσειράς.&lt;/p&gt;

&lt;p&gt;Για πραγματικό search feature μπορεί να χρειάζονται:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full-text search.&lt;/li&gt;
&lt;li&gt;Ειδικά indexes του database engine.&lt;/li&gt;
&lt;li&gt;Κανονικοποίηση κειμένου.&lt;/li&gt;
&lt;li&gt;Ξεχωριστός σχεδιασμός αναζήτησης.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Case-insensitive αναζητήσεις
&lt;/h3&gt;

&lt;p&gt;Λειτουργίες που μετατρέπουν κείμενο με &lt;code&gt;ToUpper()&lt;/code&gt; ή &lt;code&gt;ToLower()&lt;/code&gt; μπορούν να επηρεάσουν τη χρήση indexes.&lt;/p&gt;

&lt;p&gt;Για ελληνικά δεδομένα πρέπει επίσης να ορίσουμε τι σημαίνει «ίδιο κείμενο»:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Αγνοούνται τα πεζά και κεφαλαία;&lt;/li&gt;
&lt;li&gt;Αγνοούνται οι τόνοι;&lt;/li&gt;
&lt;li&gt;Πώς αντιμετωπίζονται διαφορετικές Unicode αναπαραστάσεις;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Το &lt;code&gt;@=*&lt;/code&gt; εκφράζει μια πρόθεση σύγκρισης.&lt;/p&gt;

&lt;p&gt;Οι κανόνες του συνολικού search feature χρειάζονται συγκεκριμένο σχεδιασμό.&lt;/p&gt;

&lt;h3&gt;
  
  
  Βαθιά σελιδοποίηση
&lt;/h3&gt;

&lt;p&gt;Το request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;page=5000&amp;amp;pageSize=100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;έχει διαφορετικό κόστος από την πρώτη σελίδα.&lt;/p&gt;

&lt;p&gt;Η offset σελιδοποίηση χρειάζεται να προσπεράσει πολλές προηγούμενες θέσεις.&lt;/p&gt;

&lt;p&gt;Για interfaces που κινούνται κυρίως σε επόμενη και προηγούμενη σελίδα, μπορεί να είναι καταλληλότερο το &lt;strong&gt;keyset pagination&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Αντί να ζητάμε:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;«Παράλειψε 499.900 εγγραφές».&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ζητάμε:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;«Δώσε μου τις επόμενες εγγραφές μετά από αυτή τη συγκεκριμένη θέση ταξινόμησης».&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Το ενσωματωμένο pagination του Sieve χρησιμοποιεί &lt;code&gt;Skip&lt;/code&gt; και &lt;code&gt;Take&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Η μετάβαση σε cursor ή keyset pagination χρειάζεται επιπλέον σχεδιασμό.&lt;/p&gt;

&lt;h3&gt;
  
  
  Διαφορές μεταξύ providers
&lt;/h3&gt;

&lt;p&gt;Το ίδιο LINQ query μπορεί να έχει διαφορετική μετάφραση και διαφορετικούς περιορισμούς σε SQLite, SQL Server ή PostgreSQL.&lt;/p&gt;

&lt;p&gt;Η επιλογή provider επηρεάζει:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Τους τύπους που μπορούμε να συγκρίνουμε και να ταξινομούμε.&lt;/li&gt;
&lt;li&gt;Τη συμπεριφορά των strings.&lt;/li&gt;
&lt;li&gt;Τον χειρισμό των &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Τις διαθέσιμες συναρτήσεις.&lt;/li&gt;
&lt;li&gt;Τη χρήση indexes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Η συμπεριφορά με τη βάση που χρησιμοποιεί η εφαρμογή αποτελεί μέρος της αξιολόγησης της λύσης.&lt;/p&gt;

&lt;h2&gt;
  
  
  21. Custom filters για κανόνες του domain
&lt;/h2&gt;

&lt;p&gt;Μερικές φορές ένα φίλτρο δεν αντιστοιχεί σε μία απλή ιδιότητα.&lt;/p&gt;

&lt;p&gt;Η εφαρμογή μπορεί, για παράδειγμα, να ορίζει ζώνες τιμών:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ζώνη&lt;/th&gt;
&lt;th&gt;Κανόνας&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;budget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Κάτω από 50 ευρώ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;standard&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Από 50 έως κάτω από 200 ευρώ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;premium&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Από 200 ευρώ και πάνω&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ο client θα μπορούσε να ζητά:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PriceBand==budget|standard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το Sieve υποστηρίζει custom filter methods μέσω του &lt;code&gt;ISieveCustomFilterMethods&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Το παρακάτω είναι προαιρετική επέκταση του βασικού project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Η custom μέθοδος
&lt;/h3&gt;

&lt;p&gt;Δημιουργούμε το &lt;code&gt;Querying/ProductCustomFilters.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;Sieve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nc"&gt;SieveCatalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nc"&gt;SieveCatalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Querying&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductCustomFilters&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ISieveCustomFilterMethods&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;IQueryable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;PriceBand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;IQueryable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"=="&lt;/span&gt;
            &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
                &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s2"&gt;"budget"&lt;/span&gt;
                    &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;"standard"&lt;/span&gt;
                    &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;"premium"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s2"&gt;"Unsupported PriceBand filter."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;includeBudget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"budget"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;includeStandard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"standard"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;includePremium&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"premium"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;includeBudget&lt;/span&gt;
                &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PriceCents&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;||&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;includeStandard&lt;/span&gt;
                &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PriceCents&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;
                &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PriceCents&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;20000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;||&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;includePremium&lt;/span&gt;
                &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PriceCents&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;20000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Τα &lt;code&gt;values.Contains(...)&lt;/code&gt; ελέγχουν τις λίγες τιμές που έστειλε ο client.&lt;/p&gt;

&lt;p&gt;Το query των προϊόντων παραμένει &lt;code&gt;IQueryable&amp;lt;Product&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Η custom μέθοδος συνθέτει τις επιλεγμένες ζώνες με &lt;code&gt;OR&lt;/code&gt;. Αυτή η σημασιολογία είναι δική μας ευθύνη μέσα στο custom filter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Registration
&lt;/h3&gt;

&lt;p&gt;Προσθέτουμε στο &lt;code&gt;Program.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AddScoped&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="nc"&gt;ISieveCustomFilterMethods&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;ProductCustomFilters&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Προσαρμόζουμε επίσης τον constructor του processor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CatalogSieveProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;IOptions&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SieveOptions&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;ISieveCustomFilterMethods&lt;/span&gt; &lt;span class="n"&gt;customFilters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SieveProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customFilters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Διατηρούμε το MapProperties&lt;/span&gt;
    &lt;span class="c1"&gt;// του βασικού παραδείγματος.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Επέκταση της query policy
&lt;/h3&gt;

&lt;p&gt;Η υπάρχουσα policy απορρίπτει άγνωστα filter fields.&lt;/p&gt;

&lt;p&gt;Μέσα στον έλεγχο των &lt;code&gt;term.Names&lt;/code&gt;, πριν από το τελικό &lt;code&gt;else&lt;/code&gt;, προσθέτουμε:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"PriceBand"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Operator&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"=="&lt;/span&gt;
        &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s2"&gt;"budget"&lt;/span&gt;
                &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;"standard"&lt;/span&gt;
                &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;"premium"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InvalidProductQueryException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s2"&gt;"Unsupported PriceBand filter."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η αλλαγή στην policy είναι απαραίτητη για να λειτουργήσει η επέκταση στο συγκεκριμένο project.&lt;/p&gt;

&lt;h2&gt;
  
  
  22. Nested properties και aliases
&lt;/h2&gt;

&lt;p&gt;Σε ένα διαφορετικό μοντέλο, μια παραγγελία μπορεί να έχει πελάτη:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order
  └── Customer
        └── Name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το Fluent API μπορεί να χαρτογραφήσει μια nested property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;mapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Property&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CanFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CanSort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HasName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"customer"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ο client χρησιμοποιεί:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;filters=customer@=*anna
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το &lt;code&gt;Order&lt;/code&gt; αυτού του παραδείγματος είναι ενδεικτικό μοντέλο και δεν ανήκει στο project του καταλόγου προϊόντων.&lt;/p&gt;

&lt;p&gt;Για relationships εξετάζουμε επίσης:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Τη συμπεριφορά των &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Τη SQL μετάφραση.&lt;/li&gt;
&lt;li&gt;Το κόστος των joins.&lt;/li&gt;
&lt;li&gt;Τους κανόνες ορατότητας των σχετικών δεδομένων.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  23. Πού ανήκει το Sieve στην αρχιτεκτονική
&lt;/h2&gt;

&lt;p&gt;Στο εκπαιδευτικό project ο controller συντονίζει:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Τον έλεγχο του request.&lt;/li&gt;
&lt;li&gt;Τη σύνθεση του query.&lt;/li&gt;
&lt;li&gt;Την εκτέλεση.&lt;/li&gt;
&lt;li&gt;Τη δημιουργία του response.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Σε μεγαλύτερη εφαρμογή, το ίδιο έργο μπορεί να μεταφερθεί σε query handler ή υπηρεσία ανάγνωσης.&lt;/p&gt;

&lt;p&gt;Μια ενδεικτική κατανομή:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API
  └── Παραλαμβάνει το HTTP request

Application
  └── Συντονίζει την περίπτωση χρήσης

Infrastructure ή query implementation
  └── Συνεργάζεται με EF Core και Sieve

Domain
  └── Περιγράφει επιχειρησιακές έννοιες και κανόνες
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η ακριβής κατανομή εξαρτάται από την αρχιτεκτονική του project.&lt;/p&gt;

&lt;p&gt;Ένα χρήσιμο κριτήριο είναι να περιορίζεται η γνώση του Sieve στα σημεία που συνθέτουν read queries.&lt;/p&gt;

&lt;p&gt;Όσο περισσότερα άσχετα σημεία εξαρτώνται από μια βιβλιοθήκη, τόσο αυξάνεται το κόστος αντικατάστασής της.&lt;/p&gt;

&lt;p&gt;Το επίπεδο απομόνωσης πρέπει να είναι ανάλογο με την πραγματική ανάγκη.&lt;/p&gt;

&lt;p&gt;Μια μικρή εφαρμογή δεν χρειάζεται απαραίτητα περισσότερα layers.&lt;/p&gt;

&lt;h2&gt;
  
  
  24. Γιατί να επιλέξω το Sieve
&lt;/h2&gt;

&lt;p&gt;Το Sieve αξίζει να εξεταστεί όταν:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Υπάρχουν πολλές λίστες ή διαχειριστικοί πίνακες.&lt;/li&gt;
&lt;li&gt;Χρειάζεται κοινή σύνταξη filtering και sorting.&lt;/li&gt;
&lt;li&gt;Οι clients χρειάζονται ελεγχόμενη ευελιξία.&lt;/li&gt;
&lt;li&gt;Τα read queries βασίζονται σε &lt;code&gt;IQueryable&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Η διαθέσιμη γλώσσα καλύπτει τις απαιτήσεις.&lt;/li&gt;
&lt;li&gt;Η ομάδα αποδέχεται τη συντήρηση αυτού του contract.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ένα back office με προϊόντα, πελάτες και παραγγελίες μπορεί να ωφεληθεί από κοινές συμβάσεις:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;filters=...
sorts=...
page=...
pageSize=...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αυτό διευκολύνει και τη δημιουργία κοινών frontend components.&lt;/p&gt;

&lt;h3&gt;
  
  
  Πότε αρκεί χειροκίνητο LINQ
&lt;/h3&gt;

&lt;p&gt;Αν ένα endpoint έχει λίγα, σταθερά φίλτρα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="na"&gt;GET /api/products?category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Accessories&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;minPriceCents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ένα συγκεκριμένο request model και λίγες LINQ συνθήκες μπορεί να είναι ευκολότερα στην κατανόηση και στη συντήρηση.&lt;/p&gt;

&lt;p&gt;Η δυναμική σύνταξη έχει αξία όταν λύνει επαναλαμβανόμενη ανάγκη ευελιξίας.&lt;/p&gt;

&lt;h3&gt;
  
  
  Σύγκριση επιλογών
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Επιλογή&lt;/th&gt;
&lt;th&gt;Πότε αξίζει να εξεταστεί&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Χειροκίνητο LINQ και συγκεκριμένα request DTOs&lt;/td&gt;
&lt;td&gt;Λίγα, σταθερά φίλτρα και σαφείς περιπτώσεις χρήσης&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sieve&lt;/td&gt;
&lt;td&gt;Κοινό filtering, sorting και pagination σε πολλά endpoints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OData&lt;/td&gt;
&lt;td&gt;Ανάγκη για ευρύτερο query protocol, όπως επιλογή πεδίων και expansion συσχετίσεων&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gridify&lt;/td&gt;
&lt;td&gt;Αξιολόγηση διαφορετικής γλώσσας δυναμικών LINQ queries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ειδική υποδομή αναζήτησης&lt;/td&gt;
&lt;td&gt;Relevance ranking, γλωσσική ανάλυση ή εξειδικευμένες απαιτήσεις search&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Η χρήσιμη σύγκριση εξετάζει το συμβόλαιο που θέλουμε να παρέχουμε και το κόστος που μπορούμε να συντηρούμε.&lt;/p&gt;

&lt;h2&gt;
  
  
  25. Οι συνηθέστερες παγίδες
&lt;/h2&gt;

&lt;h3&gt;
  
  
  «Έκανα ToListAsync και μετά έβαλα Sieve»
&lt;/h3&gt;

&lt;p&gt;Τα δεδομένα έχουν ήδη φορτωθεί.&lt;/p&gt;

&lt;p&gt;Κρατάμε το query ως &lt;code&gt;IQueryable&lt;/code&gt; μέχρι να ολοκληρωθούν οι περιορισμοί και η επιλογή πεδίων.&lt;/p&gt;

&lt;h3&gt;
  
  
  «Έβαλα MaxPageSize, άρα το query είναι ελαφρύ»
&lt;/h3&gt;

&lt;p&gt;Το πλήθος των επιστρεφόμενων εγγραφών είναι μόνο ένα μέρος του κόστους.&lt;/p&gt;

&lt;p&gt;Εξετάζουμε φίλτρα, count, sorting, offset και indexes.&lt;/p&gt;

&lt;h3&gt;
  
  
  «Το πεδίο υπάρχει στο entity, άρα μπορεί να χρησιμοποιηθεί»
&lt;/h3&gt;

&lt;p&gt;Η έκθεση πεδίων είναι συνειδητή απόφαση.&lt;/p&gt;

&lt;p&gt;Στο παράδειγμα το &lt;code&gt;IsActive&lt;/code&gt; υπάρχει στο entity αλλά δεν εκτίθεται ως δυναμικό φίλτρο.&lt;/p&gt;

&lt;h3&gt;
  
  
  «Το ThrowExceptions κάνει όλο το validation»
&lt;/h3&gt;

&lt;p&gt;Χρειαζόμαστε πολιτική για πεδία, operators, τιμές και όρια.&lt;/p&gt;

&lt;h3&gt;
  
  
  «Αφού δεν επιστρέφω το πεδίο, είναι ασφαλές να φιλτράρω πάνω του»
&lt;/h3&gt;

&lt;p&gt;Τα φίλτρα και τα counts μπορούν να αποκαλύπτουν πληροφορίες έμμεσα.&lt;/p&gt;

&lt;h3&gt;
  
  
  «Η ταξινόμηση κατά ημερομηνία αρκεί»
&lt;/h3&gt;

&lt;p&gt;Όταν υπάρχουν ίδιες ημερομηνίες, χρειάζεται επιπλέον κριτήριο που κάνει τη σειρά μοναδική.&lt;/p&gt;

&lt;h3&gt;
  
  
  «Ένα valid query πρέπει να βρίσκει αποτελέσματα»
&lt;/h3&gt;

&lt;p&gt;Ένα έγκυρο query μπορεί να επιστρέφει κενή λίστα.&lt;/p&gt;

&lt;p&gt;Αυτό διαφέρει από request με μη αποδεκτή τιμή ή σύνταξη.&lt;/p&gt;

&lt;p&gt;Στο δικό μας contract, μια σελίδα πέρα από τα διαθέσιμα αποτελέσματα μπορεί να επιστρέφει:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"pageSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"totalCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"totalPages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"hasNextPage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η συμπεριφορά χρειάζεται να είναι τεκμηριωμένη και συνεπής.&lt;/p&gt;

&lt;h2&gt;
  
  
  26. Τι πρέπει να τεκμηριώνει το API
&lt;/h2&gt;

&lt;p&gt;Η φράση «το endpoint υποστηρίζει Sieve» αφήνει πολλές σημαντικές λεπτομέρειες ανοιχτές.&lt;/p&gt;

&lt;p&gt;Ο client χρειάζεται να γνωρίζει:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Θέμα&lt;/th&gt;
&lt;th&gt;Παράδειγμα του άρθρου&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Filterable fields&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;category&lt;/code&gt;, &lt;code&gt;priceCents&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sortable fields&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;priceCents&lt;/code&gt;, &lt;code&gt;created&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Μονάδα τιμής&lt;/td&gt;
&lt;td&gt;Ακέραια λεπτά του ευρώ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default sorting&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-created,id&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default page&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default page size&lt;/td&gt;
&lt;td&gt;&lt;code&gt;20&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Μέγιστο page size&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validation errors&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;400 Bad Request&lt;/code&gt; για τα αναγνωρισμένα σφάλματα&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Μηδενικά αποτελέσματα&lt;/td&gt;
&lt;td&gt;Επιτυχές response με κενό &lt;code&gt;items&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ορατά δεδομένα&lt;/td&gt;
&lt;td&gt;Ενεργά προϊόντα&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Συνολικό πλήθος&lt;/td&gt;
&lt;td&gt;Μετά τα φίλτρα και πριν από το pagination&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Μια αλλαγή σε αυτά τα στοιχεία μπορεί να αλλάξει τη συμπεριφορά των clients.&lt;/p&gt;

&lt;p&gt;Το query contract αξίζει την ίδια προσοχή με τα πεδία του JSON response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Να Θυμάσαι..
&lt;/h2&gt;

&lt;p&gt;Ένα endpoint που επιστρέφει λίστες είναι ένα μικρό σύστημα αποφάσεων.&lt;/p&gt;

&lt;p&gt;Ποια δεδομένα επιτρέπεται να δει ο χρήστης; Ποια ερωτήματα μπορεί να υποβάλει; Πώς ορίζεται η σειρά των αποτελεσμάτων; Πόση πληροφορία θα επιστρέψουμε; Ποιο κόστος είναι αποδεκτό για τη βάση;&lt;/p&gt;

&lt;p&gt;Το Sieve μάς προσφέρει έναν κοινό μηχανισμό για ένα σημαντικό μέρος αυτής της δουλειάς. Μπορεί να μειώσει την επανάληψη, να οργανώσει τα read endpoints και να δώσει στους clients χρήσιμη ευελιξία.&lt;/p&gt;

&lt;p&gt;Η ποιότητα της λύσης εξαρτάται από τις επιλογές γύρω από τη βιβλιοθήκη: το σωστό &lt;code&gt;IQueryable&lt;/code&gt;, τα επιτρεπόμενα πεδία, τα όρια των requests, τη σταθερή ταξινόμηση, τη διαχείριση σφαλμάτων και τη γνώση της βάσης δεδομένων.&lt;/p&gt;

&lt;p&gt;Για έναν junior developer, η μελέτη του Sieve είναι μια καλή αφορμή να κατανοήσει πώς ένα request μετατρέπεται σταδιακά σε query και τελικά σε δεδομένα.&lt;/p&gt;

&lt;p&gt;Για έναν senior developer, το ενδιαφέρον βρίσκεται στο πώς αυτή η ευελιξία μετατρέπεται σε ένα σταθερό και συντηρήσιμο συμβόλαιο, με σαφείς κανόνες και προβλέψιμη συμπεριφορά.&lt;/p&gt;

&lt;p&gt;Η απόφαση να χρησιμοποιήσουμε το Sieve είναι καλή όταν μπορούμε να εξηγήσουμε ποια επανάληψη αφαιρεί, ποια δυνατότητα προσθέτει και ποιες ευθύνες παραμένουν στην εφαρμογή μας.&lt;/p&gt;

&lt;p&gt;Αυτή η κατανόηση μάς επιτρέπει να αξιοποιούμε ένα εργαλείο με έλεγχο — και να γνωρίζουμε πότε μια απλούστερη ή διαφορετική λύση ταιριάζει καλύτερα στο πρόβλημα που έχουμε μπροστά μας.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Deadlocks με ένα συγκεκριμένο παράδειγμα</title>
      <dc:creator>nikosst</dc:creator>
      <pubDate>Wed, 04 Nov 2026 14:20:55 +0000</pubDate>
      <link>https://dev.to/__b63657/deadlocks-giati-emphanizontai-kai-pos-ta-meionoume-1om7</link>
      <guid>https://dev.to/__b63657/deadlocks-giati-emphanizontai-kai-pos-ta-meionoume-1om7</guid>
      <description>&lt;p&gt;Η Μαρία αγοράζει από το ηλεκτρονικό κατάστημα. Ταυτόχρονα, ο Νίκος αγοράζει από το φυσικό κατάστημα.&lt;/p&gt;

&lt;p&gt;Οι δύο εφαρμογές ενημερώνουν την ίδια βάση SQL Server.&lt;/p&gt;

&lt;p&gt;Για κάθε αγορά πρέπει να αυξηθούν δύο στατιστικά:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Id&lt;/th&gt;
&lt;th&gt;Στατιστικό&lt;/th&gt;
&lt;th&gt;Τρέχουσα τιμή&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Συνολικές παραγγελίες&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Σημερινές παραγγελίες&lt;/td&gt;
&lt;td&gt;&lt;code&gt;20&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Κάθε σημερινή αγορά αυξάνει &lt;strong&gt;και τις δύο τιμές κατά ένα&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Τα IDs προσδιορίζουν τα στατιστικά, όχι τους πελάτες. Και οι δύο αγορές ενημερώνουν τις ίδιες δύο γραμμές.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Οι αλλαγές γίνονται μέσα σε συναλλαγή
&lt;/h2&gt;

&lt;p&gt;Κάθε αγορά έχει τη δική της συναλλαγή, δηλαδή ομαδοποιεί τις δύο αλλαγές ώστε να οριστικοποιηθούν μαζί.&lt;/p&gt;

&lt;p&gt;Όταν μια συναλλαγή ενημερώσει μια γραμμή, μια δεύτερη που θέλει να ενημερώσει την ίδια γραμμή πρέπει να περιμένει την ολοκλήρωση της πρώτης.&lt;/p&gt;

&lt;p&gt;Αυτή η προσωρινή προστασία ονομάζεται &lt;strong&gt;κλείδωμα lock&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Πώς εμφανίζεται το deadlock
&lt;/h2&gt;

&lt;p&gt;Στο παράδειγμά μας, οι δύο εφαρμογές έχουν διαφορετική σειρά εντολών:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ηλεκτρονικό κατάστημα: πρώτα Id=1, μετά Id=2.
Εφαρμογή ταμείου:     πρώτα Id=2, μετά Id=1.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Οι εντολές μπορούν να εκτελεστούν ως εξής&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Το ηλεκτρονικό κατάστημα ενημερώνει το `Id=1`, χωρίς ακόμη να ολοκληρώσει τη συναλλαγή του.
2. Το ταμείο ενημερώνει το `Id=2`, χωρίς ακόμη να ολοκληρώσει τη δική του.
3. Το ηλεκτρονικό κατάστημα προσπαθεί να ενημερώσει το `Id=2`. Περιμένει το ταμείο.
4. Το ταμείο προσπαθεί να ενημερώσει το `Id=1`. Περιμένει το ηλεκτρονικό κατάστημα.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Κάθε συναλλαγή περιμένει να ολοκληρωθεί η άλλη. Καμία δεν μπορεί να συνεχίσει.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Αυτό ονομάζεται &lt;strong&gt;deadlock&lt;/strong&gt;, δηλαδή αδιέξοδο από κυκλική αναμονή.&lt;/p&gt;

&lt;p&gt;Ο SQL Server εντοπίζει το αδιέξοδο, ακυρώνει μία από τις συναλλαγές και επιστρέφει το σφάλμα &lt;code&gt;1205&lt;/code&gt;. Η άλλη μπορεί πλέον να συνεχίσει.&lt;/p&gt;

&lt;h2&gt;
  
  
  Πώς αποφεύγουμε τον συγκεκριμένο κύκλο
&lt;/h2&gt;

&lt;p&gt;Αλλάζουμε τον κώδικα ώστε &lt;strong&gt;και οι δύο εφαρμογές να ενημερώνουν πρώτα το Id=1 και μετά το Id=2&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Το παρακάτω εκτελείται ξεχωριστά για κάθε αγορά:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="n"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;Counters&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;Counters&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Τώρα η εκτέλεση μπορεί να γίνει έτσι:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Το ηλεκτρονικό κατάστημα ενημερώνει το &lt;code&gt;Id=1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Το ταμείο προσπαθεί επίσης να ενημερώσει το &lt;code&gt;Id=1&lt;/code&gt; και περιμένει. &lt;strong&gt;Η δεύτερη εντολή του, για το Id=2, δεν έχει εκτελεστεί ακόμη.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Το ηλεκτρονικό κατάστημα ενημερώνει το &lt;code&gt;Id=2&lt;/code&gt; και κάνει commit, δηλαδή οριστικοποιεί τις αλλαγές του.&lt;/li&gt;
&lt;li&gt;Το ταμείο συνεχίζει ενημερώνει το &lt;code&gt;Id=1&lt;/code&gt;, μετά το &lt;code&gt;Id=2&lt;/code&gt; και κάνει commit.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Εδώ περιμένει μόνο το ταμείο. Το ηλεκτρονικό κατάστημα μπορεί να ολοκληρωθεί.&lt;/p&gt;

&lt;p&gt;Αυτή είναι απλή αναμονή, γνωστή ως &lt;strong&gt;blocking&lt;/strong&gt;. Δεν υπάρχει ο προηγούμενος κύκλος.&lt;/p&gt;

&lt;h2&gt;
  
  
  Τι κρατάμε στην πράξη
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Οι σχετικές εφαρμογές πρέπει να ενημερώνουν τα κοινά δεδομένα με συνεπή σειρά.&lt;/li&gt;
&lt;li&gt;Κρατάμε μικρή τη διάρκεια των συναλλαγών.&lt;/li&gt;
&lt;li&gt;Αν ακυρωθεί μια συναλλαγή λόγω deadlock, μπορούμε να επαναλάβουμε ολόκληρη την εργασία από την αρχή, με περιορισμένες προσπάθειες.&lt;/li&gt;
&lt;li&gt;Αν τα deadlocks επαναλαμβάνονται, εξετάζουμε ποιες εντολές περιμένουν ποιες.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Η ίδια σειρά πρόσβασης αποτρέπει τον συγκεκριμένο κύκλο του παραδείγματος. Άλλοι συνδυασμοί εντολών και δεδομένων μπορεί να προκαλέσουν διαφορετικά deadlocks.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>sql</category>
    </item>
    <item>
      <title>TransactionScope πότε το χρησιμοποιούμε και πώς διαφέρει από το BeginTransactionAsync</title>
      <dc:creator>nikosst</dc:creator>
      <pubDate>Wed, 04 Nov 2026 14:13:30 +0000</pubDate>
      <link>https://dev.to/__b63657/transactionscope-me-apla-logia-4327</link>
      <guid>https://dev.to/__b63657/transactionscope-me-apla-logia-4327</guid>
      <description>&lt;p&gt;Θέλουμε να αποθηκεύσουμε μια παραγγελία και το ιστορικό της.&lt;/p&gt;

&lt;p&gt;Αν αποθηκευτεί η παραγγελία αλλά αποτύχει το ιστορικό, η απαίτηση της εφαρμογής μας είναι να ακυρωθούν και τα δύο.&lt;/p&gt;

&lt;p&gt;Αυτό σημαίνει ότι θέλουμε &lt;strong&gt;οι δύο αποθηκεύσεις να ανήκουν στην ίδια συναλλαγή&lt;/strong&gt; δηλαδή να οριστικοποιηθούν μαζί ή να ακυρωθούν μαζί.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;BeginTransactionAsync()&lt;/code&gt; μπορεί ήδη να καλύψει αυτή την ανάγκη. Το &lt;code&gt;TransactionScope&lt;/code&gt; προσφέρει έναν διαφορετικό τρόπο οργάνωσης της συμμετοχής στη συναλλαγή.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Με ένα DbContext συνήθως αρκεί το BeginTransactionAsync
&lt;/h2&gt;

&lt;p&gt;Το &lt;code&gt;DbContext&lt;/code&gt; είναι το αντικείμενο μέσω του οποίου διαβάζουμε και γράφουμε δεδομένα με το EF Core.&lt;/p&gt;

&lt;p&gt;Στο παράδειγμα, το &lt;code&gt;db&lt;/code&gt; είναι ένα ήδη ρυθμισμένο context και το &lt;code&gt;order&lt;/code&gt; μια νέα παραγγελία:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BeginTransactionAsync&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OrderHistory&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;OrderId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Δημιουργήθηκε παραγγελία"&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CommitAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Οι δύο αποθηκεύσεις χρησιμοποιούν το ίδιο &lt;code&gt;db&lt;/code&gt; και τη συναλλαγή που ανοίξαμε σε αυτό.&lt;/p&gt;

&lt;p&gt;Αν αποτύχει η δεύτερη αποθήκευση και βγούμε από το μπλοκ χωρίς commit, η ενεργή συναλλαγή ακυρώνεται.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Δεν χρειαζόμαστε TransactionScope μόνο και μόνο επειδή έχουμε δύο αποθηκεύσεις.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Το ίδιο ισχύει αν μεταφέρουμε τις αποθηκεύσεις σε διαφορετικές μεθόδους: εφόσον χρησιμοποιούν το ίδιο context και την ίδια ενεργή συναλλαγή, παραμένουν μαζί.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Ποια ανάγκη καλύπτει το TransactionScope
&lt;/h2&gt;

&lt;p&gt;Ας αλλάξουμε λίγο το παράδειγμα:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Η παραγγελία αποθηκεύεται μέσω EF Core.&lt;/li&gt;
&lt;li&gt;Ένα παλιότερο κομμάτι της εφαρμογής γράφει το ιστορικό απευθείας με SQL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Έχουμε δύο τρόπους αποθήκευσης που πρέπει να συμφωνούν.&lt;/p&gt;

&lt;p&gt;Μπορούμε να τους συνδέσουμε στην ίδια συναλλαγή δίνοντας ρητά την κοινή σύνδεση και τη συναλλαγή όπου χρειάζεται.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;TransactionScope&lt;/code&gt; προσφέρει μια άλλη επιλογή: &lt;strong&gt;κάνει διαθέσιμη μια κοινή συναλλαγή όσο εκτελείται ο κώδικας μέσα στο scope&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Το κρίσιμο σημείο είναι ο τρόπος συμμετοχής.&lt;/p&gt;

&lt;p&gt;Για SQL Server, η βιβλιοθήκη &lt;strong&gt;Microsoft.Data.SqlClient&lt;/strong&gt; χειρίζεται τη σύνδεση της εφαρμογής με τη βάση. Όταν ανοίξει μια σύνδεση μέσα σε ενεργό &lt;code&gt;TransactionScope&lt;/code&gt;, μπορεί να εντοπίσει την κοινή συναλλαγή και να συμμετάσχει αυτόματα, με τις κατάλληλες ρυθμίσεις.&lt;/p&gt;

&lt;p&gt;Έτσι, οι εντολές που εκτελούνται μέσω αυτής της σύνδεσης συμμετέχουν στη συναλλαγή χωρίς να τους δώσουμε ξεχωριστά το αντικείμενο &lt;code&gt;transaction&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Αυτή η διαθέσιμη «τρέχουσα συναλλαγή» ονομάζεται &lt;strong&gt;ambient transaction&lt;/strong&gt;. &lt;a href="https://learn.microsoft.com/en-us/dotnet/framework/data/transactions/implementing-an-implicit-transaction-using-transaction-scope" rel="noopener noreferrer"&gt;Τεκμηρίωση Microsoft&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Παράδειγμα με EF Core και απευθείας SQL
&lt;/h2&gt;

&lt;p&gt;Χρησιμοποιούμε &lt;strong&gt;μία σύνδεση προς SQL Server&lt;/strong&gt; και για τις δύο αποθηκεύσεις.&lt;/p&gt;

&lt;p&gt;Πριν δούμε τον κώδικα:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;connectionString&lt;/code&gt;: τα στοιχεία σύνδεσης προς τη βάση.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SqlConnection&lt;/code&gt;: το αντικείμενο που χειρίζεται αυτή τη σύνδεση.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;options&lt;/code&gt;: οι ρυθμίσεις που λένε στο EF να χρησιμοποιήσει τη συγκεκριμένη σύνδεση.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;command&lt;/code&gt;: μια εντολή SQL που θα στείλουμε στη βάση.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Οι επιλογές του scope σημαίνουν:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Επιλογή&lt;/th&gt;
&lt;th&gt;Σημασία&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Required&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Χρησιμοποίησε υπάρχουσα κοινή συναλλαγή ή δημιούργησε μία.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ReadCommitted&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Μη διαβάζεις μη οριστικοποιημένες αλλαγές άλλων συναλλαγών.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Enabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Διατήρησε την κοινή συναλλαγή διαθέσιμη και μετά από &lt;code&gt;await&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Το παράδειγμα προϋποθέτει ένα &lt;code&gt;ShopDbContext&lt;/code&gt; που δέχεται τις ρυθμίσεις στον constructor και έναν πίνακα &lt;code&gt;OrderHistory&lt;/code&gt; με πεδία &lt;code&gt;OrderId&lt;/code&gt; και &lt;code&gt;Message&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Data.SqlClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Transactions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TransactionScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;TransactionScopeOption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TransactionOptions&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;IsolationLevel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
                &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsolationLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadCommitted&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;TransactionScopeAsyncFlowOption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enabled&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SqlConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connectionString&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OpenAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;DbContextOptionsBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ShopDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSqlServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ShopDbContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// Πρώτη αποθήκευση: μέσω EF Core.&lt;/span&gt;
            &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="c1"&gt;// Δεύτερη αποθήκευση: απευθείας SQL.&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateCommand&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
                &lt;span class="s"&gt;"INSERT INTO OrderHistory (OrderId, Message) "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                &lt;span class="s"&gt;"VALUES (@orderId, N'Δημιουργήθηκε παραγγελία');"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@orderId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteNonQueryAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Complete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η παράμετρος &lt;code&gt;@orderId&lt;/code&gt; μεταφέρει το ID της παραγγελίας στην SQL εντολή.&lt;/p&gt;

&lt;p&gt;Παρατήρησε ότι:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Δημιουργούμε πρώτα το scope.&lt;/li&gt;
&lt;li&gt;Ανοίγουμε τη σύνδεση μέσα σε αυτό.&lt;/li&gt;
&lt;li&gt;Το EF και η απευθείας SQL χρησιμοποιούν την ίδια σύνδεση.&lt;/li&gt;
&lt;li&gt;Καλούμε &lt;code&gt;Complete()&lt;/code&gt; μόνο αφού πετύχουν και οι δύο αποθηκεύσεις.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Δεν καλούμε &lt;code&gt;BeginTransactionAsync()&lt;/code&gt; μέσα στο scope. Η σύνδεση έχει ήδη συμμετάσχει στη συναλλαγή του.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Τι γίνεται σε επιτυχία ή αποτυχία
&lt;/h2&gt;

&lt;p&gt;Αν αποτύχει η SQL εντολή του ιστορικού, η εκτέλεση δεν φτάνει στο &lt;code&gt;Complete()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Βγαίνουμε από το &lt;code&gt;using&lt;/code&gt; και η συναλλαγή ακυρώνεται. Ακυρώνεται επομένως και η παραγγελία που έγραψε το EF.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;catch&lt;/code&gt; καταγράφει και προωθεί το σφάλμα. Δεν χρειάζεται να καλέσει ξεχωριστά &lt;code&gt;RollbackAsync()&lt;/code&gt; για τη συναλλαγή του scope.&lt;/p&gt;

&lt;p&gt;Αν πετύχουν όλα, το &lt;code&gt;Complete()&lt;/code&gt; δηλώνει ότι η εργασία ολοκληρώθηκε επιτυχώς.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Το Complete δεν σημαίνει ότι έγινε ήδη το τελικό commit.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Αν το scope δημιούργησε τη συναλλαγή, η διαδικασία του commit γίνεται στην έξοδο από το &lt;code&gt;using&lt;/code&gt;. Αν συμμετέχει σε εξωτερική συναλλαγή, η τελική ολοκλήρωση εξαρτάται από εκείνον που την ελέγχει. &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.transactions.transactionscope.complete" rel="noopener noreferrer"&gt;Τεκμηρίωση Microsoft&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Θα μπορούσαμε να χρησιμοποιήσουμε BeginTransactionAsync και εδώ;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Ναι.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Θα χρειαζόταν να χρησιμοποιήσουμε την ίδια σύνδεση και να δώσουμε την ανοιχτή συναλλαγή στην απευθείας SQL εντολή.&lt;/p&gt;

&lt;p&gt;Η Microsoft παρουσιάζει και αυτόν τον τρόπο συνδυασμού EF Core και SQL. &lt;a href="https://learn.microsoft.com/en-us/ef/core/saving/transactions#using-external-dbtransactions-relational-databases-only" rel="noopener noreferrer"&gt;Κοινή συναλλαγή μεταξύ διαφορετικών τρόπων πρόσβασης&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Η επιλογή επομένως είναι:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Τρόπος&lt;/th&gt;
&lt;th&gt;Πώς συμμετέχουν οι εντολές&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BeginTransactionAsync()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ανοίγουμε μια συγκεκριμένη συναλλαγή και τη χρησιμοποιούμε ρητά όπου χρειάζεται.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TransactionScope&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Οι βιβλιοθήκες που υποστηρίζουν αυτόν τον μηχανισμό μπορούν να εντοπίσουν την κοινή συναλλαγή.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Το scope μπορεί να είναι βολικό όταν περισσότερα τμήματα πρόσβασης σε δεδομένα υποστηρίζουν αυτόν τον μηχανισμό και θέλουμε να περιορίσουμε τη μεταφορά του αντικειμένου transaction μεταξύ τους.&lt;/p&gt;

&lt;p&gt;Για μια απλή εφαρμογή με ένα &lt;code&gt;DbContext&lt;/code&gt;, θα ξεκινούσα συνήθως με &lt;code&gt;BeginTransactionAsync()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Οι λεπτομέρειες που χρειάζονται προσοχή
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Το scope δεν περιλαμβάνει αυτόματα κάθε ενέργεια του κώδικα.&lt;/strong&gt; Μια αποστολή email ή μια κλήση υπηρεσίας πληρωμών δεν ακυρώνεται επειδή ακυρώθηκε η συναλλαγή της βάσης.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Η σύνδεση στο παράδειγμα ανοίγει μέσα στο scope.&lt;/strong&gt; Αν ήταν ήδη ανοιχτή πριν από αυτό, δεν πρέπει να υποθέσουμε ότι συμμετέχει αυτόματα.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Με άλλη βάση χρειάζεται έλεγχος υποστήριξης.&lt;/strong&gt; Η συμπεριφορά εξαρτάται από τη βιβλιοθήκη που συνδέει την εφαρμογή με τη βάση.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Πολλές συνδέσεις μπορεί να απαιτήσουν πρόσθετο συντονισμό.&lt;/strong&gt; Μπορεί να προκύψει κατανεμημένη συναλλαγή: διαφορετικοί πόροι πρέπει να συμφωνήσουν για το κοινό commit. Στο σύγχρονο .NET, η σχετική υποστήριξη του &lt;code&gt;System.Transactions&lt;/code&gt; είναι για Windows από .NET 7 και μπορεί να απαιτεί την υπηρεσία συντονισμού MSDTC. Το παράδειγμα χρησιμοποιεί μία σύνδεση.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Το scope δεν προσφέρει ασύγχρονο commit/rollback.&lt;/strong&gt; Η ολοκλήρωσή του κατά την έξοδο από το &lt;code&gt;using&lt;/code&gt; είναι συγχρονιστική.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/ef/core/saving/transactions#limitations-of-systemtransactions" rel="noopener noreferrer"&gt;Υποστήριξη και περιορισμοί του TransactionScope στο EF Core&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Αν το TransactionScope δημιουργήσει νέα συναλλαγή και δεν επιλέξουμε διαφορετική ρύθμιση, χρησιμοποιεί Serializable. Πρόκειται για αυστηρή προστασία από ταυτόχρονες αλλαγές, βοηθά να παραμένουν έγκυρες οι αποφάσεις που βασίζονται στα δεδομένα που διαβάσαμε, αλλά μπορεί να προκαλέσει περισσότερη αναμονή. &lt;a href="https://learn.microsoft.com/en-us/dotnet/framework/data/transactions/implementing-an-implicit-transaction-using-transaction-scope#setting-the-transactionscope-isolation-level" rel="noopener noreferrer"&gt;Προεπιλεγμένο επίπεδο απομόνωσης&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Για να καταλάβεις τι σημαίνουν τα &lt;code&gt;ReadCommitted&lt;/code&gt; και &lt;code&gt;Serializable&lt;/code&gt;, διάβασε το &lt;a href="https://dev.to/__b63657/isolation-levels-me-apla-paradeigmata-106e"&gt;Isolation levels με απλά παραδείγματα&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;a href="mailto:nikosstit@gmail.com"&gt;nikosstit@gmail.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>entityframework</category>
      <category>sql</category>
    </item>
    <item>
      <title>Isolation levels με απλά παραδείγματα</title>
      <dc:creator>nikosst</dc:creator>
      <pubDate>Wed, 04 Nov 2026 14:12:42 +0000</pubDate>
      <link>https://dev.to/__b63657/isolation-levels-me-apla-paradeigmata-106e</link>
      <guid>https://dev.to/__b63657/isolation-levels-me-apla-paradeigmata-106e</guid>
      <description>&lt;p&gt;Η αγορά σου διαβάζει ότι ένα προϊόν κοστίζει 100€.&lt;/p&gt;

&lt;p&gt;Πριν ολοκληρωθεί, ένας διαχειριστής προσπαθεί να αλλάξει την τιμή σε 120€.&lt;/p&gt;

&lt;p&gt;Χρειαζόμαστε κανόνες για το τι επιτρέπεται να διαβάσει η αγορά και πότε κάποια εργασία πρέπει να περιμένει.&lt;/p&gt;

&lt;p&gt;Αυτούς τους κανόνες επιλέγουμε μέσω του &lt;strong&gt;επιπέδου απομόνωσης isolation level&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Στα παραδείγματα, η αγορά και η αλλαγή τιμής εκτελούνται σε ξεχωριστές συναλλαγές. Οι εξηγήσεις αφορούν SQL Server.&lt;/p&gt;

&lt;h2&gt;
  
  
  ReadCommitted βλέπουμε οριστικοποιημένες αλλαγές
&lt;/h2&gt;

&lt;p&gt;Διαβάζουμε αλλαγές άλλων συναλλαγών μόνο αφού οριστικοποιηθούν, δηλαδή αφού γίνει &lt;strong&gt;commit&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Διαβάζουμε τιμή 100€.
Ο διαχειριστής την αλλάζει σε 120€ και κάνει commit.
Ξαναδιαβάζουμε και βρίσκουμε 120€.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Άρα, δύο αναγνώσεις μέσα στη δική μας συναλλαγή μπορούν να δώσουν διαφορετικές τιμές.&lt;/p&gt;

&lt;p&gt;Είναι συνηθισμένη αφετηρία για καθημερινές εργασίες.&lt;/p&gt;

&lt;h2&gt;
  
  
  ReadUncommitted βλέπουμε και προσωρινές αλλαγές
&lt;/h2&gt;

&lt;p&gt;Επιτρέπεται να διαβάσουμε μια αλλαγή πριν οριστικοποιηθεί.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Η τιμή είναι 100€.
Ο διαχειριστής την αλλάζει προσωρινά σε 80€.
Διαβάζουμε 80€.
Ο διαχειριστής ακυρώνει την αλλαγή με rollback.
Η τιμή παραμένει 100€.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Βασιστήκαμε σε μια τιμή που τελικά ακυρώθηκε.&lt;/p&gt;

&lt;p&gt;Το χρησιμοποιούμε μόνο όταν αποδεχόμαστε συνειδητά τέτοια ασυνεπή αποτελέσματα.&lt;/p&gt;

&lt;h2&gt;
  
  
  RepeatableRead δεν αλλάζουν οι γραμμές που διαβάσαμε
&lt;/h2&gt;

&lt;p&gt;Θέλουμε να ξαναδιαβάσουμε το ίδιο προϊόν χωρίς να έχει αλλάξει ενδιάμεσα από άλλη συναλλαγή.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Διαβάζουμε τιμή 100€.
Ο διαχειριστής προσπαθεί να την αλλάξει σε 120€.
Η αλλαγή του περιμένει.
Ξαναδιαβάζουμε και βρίσκουμε 100€.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η προστασία διαρκεί μέχρι να ολοκληρωθεί η συναλλαγή μας.&lt;/p&gt;

&lt;p&gt;Υπάρχει όμως ένας περιορισμός &lt;strong&gt;μπορούν να προστεθούν νέα προϊόντα&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Αν αναζητήσουμε όλα τα προϊόντα έως 100€, κάποιος μπορεί να προσθέσει νέο προϊόν των 90€. Στην επόμενη ίδια αναζήτηση μπορεί να εμφανιστεί και αυτό.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.data.isolationlevel" rel="noopener noreferrer"&gt;Τεκμηρίωση Microsoft για τα επίπεδα απομόνωσης&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Serializable προστατεύουμε και τα αποτελέσματα της αναζήτησης
&lt;/h2&gt;

&lt;p&gt;Έχουμε δύο υπαλλήλους που χρησιμοποιούν την ίδια εφαρμογή και βάση SQL Server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ο Νίκος ζητά μια αναφορά με όλα τα προϊόντα που κοστίζουν έως 100€.&lt;/li&gt;
&lt;li&gt;Η Μαρία καταχωρίζει ένα νέο προϊόν που κοστίζει 90€.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Η εφαρμογή δημιουργεί την αναφορά μέσα σε συναλλαγή με &lt;code&gt;Serializable&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Η σειρά είναι:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Η εφαρμογή του Νίκου ξεκινά τη συναλλαγή και διαβάζει τα προϊόντα έως 100€.
2. Πριν ολοκληρωθεί αυτή η συναλλαγή, η Μαρία πατά «Αποθήκευση» για το νέο προϊόν των 90€.
3. Η εφαρμογή στέλνει στη βάση την εντολή προσθήκης. Αυτή η εντολή περιμένει, επειδή το νέο προϊόν θα άλλαζε τα αποτελέσματα της αναζήτησης του Νίκου.
4. Αν η πρώτη συναλλαγή επαναλάβει την αναζήτηση, δεν εμφανίζεται το νέο προϊόν της Μαρίας.
5. Όταν ολοκληρωθεί η πρώτη συναλλαγή με commit, η προσθήκη της Μαρίας μπορεί να συνεχιστεί.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η συναλλαγή διαρκεί όσο εκτελείται η εργασία στη βάση, όχι όσο ο Νίκος κοιτάζει την αναφορά στην οθόνη.&lt;/p&gt;

&lt;p&gt;Αυτή είναι η επιπλέον προστασία του &lt;code&gt;Serializable&lt;/code&gt;, εμποδίζει άλλες συναλλαγές να προσθέσουν αποτελέσματα στην ίδια αναζήτησή μας όσο διαρκεί η συναλλαγή.&lt;/p&gt;

&lt;p&gt;Το κόστος είναι ότι άλλες εργασίες μπορεί να περιμένουν περισσότερο.&lt;/p&gt;

&lt;h2&gt;
  
  
  Snapshot διαβάζουμε μια σταθερή εικόνα
&lt;/h2&gt;

&lt;p&gt;Η βάση μάς επιτρέπει να διαβάζουμε μια σταθερή αρχική εικόνα των δεδομένων, μαζί με τις δικές μας αλλαγές.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Διαβάζουμε τιμή 100€.
Ο διαχειριστής αλλάζει την τιμή σε 120€ και κάνει commit.
Ξαναδιαβάζουμε και εξακολουθούμε να βλέπουμε 100€.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η διαφορά από το &lt;code&gt;RepeatableRead&lt;/code&gt; είναι ότι εδώ ο διαχειριστής μπορεί να αποθηκεύσει την αλλαγή του ενώ εμείς συνεχίζουμε να διαβάζουμε την παλιότερη εικόνα.&lt;/p&gt;

&lt;p&gt;Είναι χρήσιμο για αναφορές με πολλές αναζητήσεις που πρέπει να βασίζονται στην ίδια χρονική εικόνα.&lt;/p&gt;

&lt;p&gt;Χρειάζεται ενεργοποίηση στη βάση. Αν επιχειρήσουμε ενημερώσεις, μπορεί να εμφανιστούν συγκρούσεις με αλλαγές άλλων συναλλαγών. Δεν παρέχει όλες τις εγγυήσεις του &lt;code&gt;Serializable&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/snapshot-isolation-in-sql-server" rel="noopener noreferrer"&gt;Τεκμηρίωση Microsoft για Snapshot&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Πώς επιλέγουμε επίπεδο στο EF Core
&lt;/h2&gt;

&lt;p&gt;Δίνουμε την επιλογή στη &lt;code&gt;BeginTransactionAsync()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BeginTransactionAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsolationLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadCommitted&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Αναγνώσεις και αλλαγές στη βάση.&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CommitAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το &lt;code&gt;ReadCommitted&lt;/code&gt; είναι η προεπιλογή του SQL Server. Η ακριβής συμπεριφορά του εξαρτάται και από τις ρυθμίσεις της βάσης. &lt;a href="https://learn.microsoft.com/en-us/sql/t-sql/statements/set-transaction-isolation-level-transact-sql" rel="noopener noreferrer"&gt;Τεκμηρίωση Microsoft&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Επιλέγουμε επίπεδο σύμφωνα με την ανάγκη της εργασίας αν αρκούν οριστικοποιημένα δεδομένα, αν χρειαζόμαστε σταθερή εικόνα ή αν πρέπει να προστατεύσουμε μια απόφαση μέχρι να ολοκληρωθεί η συναλλαγή.&lt;/p&gt;




&lt;p&gt;&lt;a href="mailto:nikosstit@gmail.com"&gt;nikosstit@gmail.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>entityframework</category>
      <category>sql</category>
    </item>
    <item>
      <title>BeginTransactionAsync CommitAsync και RollbackAsync με απλό παράδειγμα</title>
      <dc:creator>nikosst</dc:creator>
      <pubDate>Wed, 04 Nov 2026 14:11:48 +0000</pubDate>
      <link>https://dev.to/__b63657/begintransactionasync-commitasync-kai-rollbackasync-me-aplo-paradeigma-2a53</link>
      <guid>https://dev.to/__b63657/begintransactionasync-commitasync-kai-rollbackasync-me-aplo-paradeigma-2a53</guid>
      <description>&lt;p&gt;Έστω ότι μια διαδικασία αποθηκεύει πρώτα παραγγελία και μετά τιμολόγιο.&lt;/p&gt;

&lt;p&gt;Θέλουμε να αποθηκευτούν και τα δύο. Αν αποτύχει κάποιο βήμα, θέλουμε να ακυρωθούν οι αλλαγές της διαδικασίας.&lt;/p&gt;

&lt;p&gt;Χρησιμοποιούμε τρεις βασικές μεθόδους:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Μέθοδος&lt;/th&gt;
&lt;th&gt;Σημασία&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BeginTransactionAsync()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ξεκίνα τη συναλλαγή.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CommitAsync()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Οριστικοποίησε τις αλλαγές της.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RollbackAsync()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ακύρωσε τις αλλαγές της.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Παράδειγμα
&lt;/h2&gt;

&lt;p&gt;Το &lt;code&gt;db&lt;/code&gt; είναι το αντικείμενο πρόσβασης στη βάση μέσω EF Core.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BeginTransactionAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ProductId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Quantity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Invoice&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;OrderId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CommitAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η πρώτη αποθήκευση γράφει την παραγγελία και μας δίνει το ID της.&lt;/p&gt;

&lt;p&gt;Η δεύτερη γράφει το τιμολόγιο.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;CommitAsync()&lt;/code&gt; οριστικοποιεί τη συναλλαγή.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;await using&lt;/code&gt; εξασφαλίζει τον καθαρισμό της όταν φύγουμε από αυτό το τμήμα κώδικα. Αν η δεύτερη αποθήκευση αποτύχει πριν φτάσουμε στο commit, η ενεργή συναλλαγή ακυρώνεται κατά την αποδέσμευσή της.&lt;/p&gt;

&lt;h2&gt;
  
  
  Το SaveChanges δεν είναι το ίδιο με το Commit
&lt;/h2&gt;

&lt;p&gt;Μέσα σε δικό μας transaction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SaveChangesAsync()&lt;/code&gt; σημαίνει «στείλε τις αλλαγές στη βάση».&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CommitAsync()&lt;/code&gt; σημαίνει «οριστικοποίησε τη συναλλαγή».&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Το commit δεν αποθηκεύει μόνο του αλλαγές που υπάρχουν ακόμη αποκλειστικά στη μνήμη. &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.storage.idbcontexttransaction.commitasync?view=efcore-10.0" rel="noopener noreferrer"&gt;Τεκμηρίωση Microsoft&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Αυτόματο rollback όταν αποτύχει η διαδικασία
&lt;/h2&gt;

&lt;p&gt;Αποθηκεύουμε παραγγελία και τιμολόγιο μαζί. Τα &lt;code&gt;order&lt;/code&gt; και &lt;code&gt;invoice&lt;/code&gt; είναι τα αντίστοιχα αντικείμενα που έχουμε ετοιμάσει.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BeginTransactionAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CommitAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αν αποτύχει η αποθήκευση του τιμολογίου, βγαίνουμε από το &lt;code&gt;try&lt;/code&gt; χωρίς commit.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;await using&lt;/code&gt; φροντίζει να ολοκληρωθεί η χρήση της συναλλαγής. Εφόσον αυτή παραμένει ενεργή χωρίς commit, ακυρώνεται και η εισαγωγή της παραγγελίας.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Δεν χρειάζεται να προσθέσουμε ούτε &lt;code&gt;RollbackAsync()&lt;/code&gt; ούτε &lt;code&gt;finally&lt;/code&gt; για αυτή την ακύρωση.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;catch&lt;/code&gt; εδώ καταγράφει το σφάλμα και το προωθεί. Δεν είναι αυτό που προκαλεί το αυτόματο rollback.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. RollbackAsync όταν θέλουμε ακύρωση τώρα και συνεχίζουμε
&lt;/h2&gt;

&lt;p&gt;Δημιουργούμε μια παραγγελία και επιχειρούμε να μειώσουμε το απόθεμα.&lt;/p&gt;

&lt;p&gt;Η παρακάτω ενημέρωση γίνεται μόνο αν το απόθεμα επαρκεί. Η &lt;code&gt;ExecuteUpdateAsync()&lt;/code&gt; επιστρέφει πόσες γραμμές ενημερώθηκαν.&lt;/p&gt;

&lt;p&gt;Αν επιστρέψει &lt;code&gt;0&lt;/code&gt;, ακυρώνουμε την παραγγελία &lt;strong&gt;πριν συνεχίσουμε με την ενημέρωση του πελάτη&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BeginTransactionAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;updated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Products&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProductId&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stock&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteUpdateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;setters&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setters&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stock&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;updated&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RollbackAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CommitAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Η δική μας μέθοδος που ενημερώνει τον πελάτη.&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;NotifyCustomerAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Εδώ δεν προέκυψε απαραίτητα σφάλμα. Η έλλειψη αποθέματος είναι αποτέλεσμα ελέγχου.&lt;/p&gt;

&lt;p&gt;Καλούμε &lt;code&gt;RollbackAsync()&lt;/code&gt; ώστε η ακύρωση να έχει ολοκληρωθεί &lt;strong&gt;πριν καλέσουμε την ενημέρωση του πελάτη&lt;/strong&gt;. Δεν περιμένουμε να φτάσουμε στο τέλος του μπλοκ που περιέχει το &lt;code&gt;await using&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ο κανόνας είναι απλός:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Με &lt;code&gt;await using&lt;/code&gt;, η ενεργή συναλλαγή ακυρώνεται αυτόματα όταν βγούμε από το μπλοκ χωρίς commit.&lt;/li&gt;
&lt;li&gt;Καλούμε &lt;code&gt;RollbackAsync()&lt;/code&gt; όταν θέλουμε να την ακυρώσουμε νωρίτερα, ενώ η εκτέλεση συνεχίζεται.&lt;/li&gt;
&lt;li&gt;Μετά από επιτυχημένο commit, δεν μπορούμε να ακυρώσουμε εκείνη τη συναλλαγή με rollback.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a href="mailto:nikosstit@gmail.com"&gt;nikosstit@gmail.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>entityframework</category>
    </item>
    <item>
      <title>Τι είναι ένα transaction και τι κάνει ήδη το EF Core</title>
      <dc:creator>nikosst</dc:creator>
      <pubDate>Wed, 04 Nov 2026 13:13:09 +0000</pubDate>
      <link>https://dev.to/__b63657/transactions-sto-net-kai-ef-core-me-transactionscope-isolation-levels-kai-deadlocks-50op</link>
      <guid>https://dev.to/__b63657/transactions-sto-net-kai-ef-core-me-transactionscope-isolation-levels-kai-deadlocks-50op</guid>
      <description>&lt;p&gt;Μια αγορά χρειάζεται δύο αλλαγές:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Να δημιουργηθεί η παραγγελία.&lt;/li&gt;
&lt;li&gt;Να μειωθεί το απόθεμα.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Αν αποθηκευτεί μόνο η παραγγελία, η αγορά έχει μείνει μισή.&lt;/p&gt;

&lt;p&gt;Ένα &lt;strong&gt;transaction&lt;/strong&gt;, δηλαδή μια &lt;strong&gt;συναλλαγή&lt;/strong&gt;, ομαδοποιεί αλλαγές ώστε να ολοκληρωθούν όλες μαζί ή να ακυρωθούν όλες μαζί.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ένα απλό παράδειγμα
&lt;/h2&gt;

&lt;p&gt;Θέλουμε να αποθηκεύσουμε μια παραγγελία και μια καταγραφή ιστορικού.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;db&lt;/code&gt; είναι ένα ήδη ρυθμισμένο &lt;code&gt;DbContext&lt;/code&gt;: το αντικείμενο μέσω του οποίου διαβάζουμε και γράφουμε δεδομένα με το EF Core.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ProductId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Quantity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuditEntries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AuditEntry&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Δημιουργήθηκε παραγγελία"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το &lt;code&gt;Add()&lt;/code&gt; καταγράφει στη μνήμη τι θέλουμε να προσθέσουμε.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;SaveChangesAsync()&lt;/code&gt; στέλνει τις αλλαγές στη βάση.&lt;/p&gt;

&lt;p&gt;Με τις προεπιλεγμένες ρυθμίσεις και υποστήριξη συναλλαγών, το EF αποθηκεύει τις αλλαγές αυτής της κλήσης ατομικά: όλες μαζί ή καμία. &lt;a href="https://learn.microsoft.com/en-us/ef/core/saving/transactions#default-transaction-behavior" rel="noopener noreferrer"&gt;Τεκμηρίωση Microsoft&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Πότε χρειάζεται προσοχή
&lt;/h2&gt;

&lt;p&gt;Δύο ξεχωριστές αποθηκεύσεις δεν αποτελούν αυτόματα μία κοινή συναλλαγή:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αν αποτύχει η δεύτερη κλήση, η παραγγελία μπορεί να έχει ήδη αποθηκευτεί.&lt;/p&gt;

&lt;p&gt;Το ίδιο &lt;code&gt;DbContext&lt;/code&gt; δεν σημαίνει ότι υπάρχει συνεχώς ανοιχτό transaction.&lt;/p&gt;

&lt;p&gt;Όταν πολλά βήματα πρέπει να ολοκληρωθούν μαζί, μπορούμε να ανοίξουμε δική μας συναλλαγή. Αυτό καλύπτει το επόμενο άρθρο.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/__b63657/begintransactionasync-commitasync-kai-rollbackasync-me-aplo-paradeigma-2a53"&gt;BeginTransactionAsync CommitAsync και RollbackAsync με απλό παράδειγμα&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="mailto:nikosstit@gmail.com"&gt;nikosstit@gmail.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>entityframework</category>
    </item>
    <item>
      <title>Retry με Polly v8 στο .NET: πόσες φορές χτυπάμε το κουδούνι;</title>
      <dc:creator>nikosst</dc:creator>
      <pubDate>Tue, 03 Nov 2026 21:39:09 +0000</pubDate>
      <link>https://dev.to/__b63657/retry-me-polly-v8-sto-net-poses-phores-khtupame-to-koudouni-88c</link>
      <guid>https://dev.to/__b63657/retry-me-polly-v8-sto-net-poses-phores-khtupame-to-koudouni-88c</guid>
      <description>&lt;p&gt;Ένας διανομέας χτυπάει το κουδούνι. Δεν ακούς. Περιμένει λίγο, ξαναχτυπάει και ανοίγεις.&lt;/p&gt;

&lt;p&gt;Αυτό είναι το &lt;strong&gt;Retry&lt;/strong&gt;: επαναλαμβάνουμε μια αποτυχημένη ενέργεια όταν υπάρχει πιθανότητα η επόμενη προσπάθεια να πετύχει.&lt;/p&gt;

&lt;p&gt;Φυσικά, ο διανομέας χρειάζεται όριο. Ο γείτονας του τρίτου δεν χρωστάει να ακούσει όλα τα retries μας.&lt;/p&gt;

&lt;p&gt;Στο .NET, η &lt;strong&gt;Polly v8&lt;/strong&gt; μάς βοηθά να ορίσουμε πότε ξαναπροσπαθούμε, πόσο περιμένουμε και πότε σταματάμε.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Πότε το χρησιμοποιούμε&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Σε πιθανώς προσωρινές αστοχίες: ένα API κάνει restart, μια σύνδεση διακόπτεται ή ο server επιστρέφει &lt;code&gt;503 Service Unavailable&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Ένα λανθασμένο request ή ένα πρόβλημα δικαιωμάτων χρειάζεται διόρθωση. Όπως και η λάθος διεύθυνση παράδοσης.&lt;/p&gt;

&lt;p&gt;Στο παράδειγμα θα επαναλαμβάνουμε μόνο τις κλήσεις που αποτυγχάνουν με &lt;code&gt;503&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Δημιουργία και NuGet&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Με εγκατεστημένο .NET 10 SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RetryDemo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;net10.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RetryDemo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Polly.Core&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;8.5.2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Χρησιμοποιούμε συγκεκριμένη έκδοση της σειράς v8 για το παράδειγμα. &lt;a href="https://www.nuget.org/packages/Polly.Core/8.5.2" rel="noopener noreferrer"&gt;NuGet: Polly.Core&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Θα έχουμε τρία αρχεία:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RetryDemo/
├── Program.cs
├── CatalogClient.cs
└── DemoCatalogHandler.cs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Η πολιτική — &lt;code&gt;Program.cs&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Αντικαθιστούμε το υπάρχον περιεχόμενο:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Polly&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Polly.Retry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Polly.Timeout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ResiliencePipelineBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;RetryStrategyOptions&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ShouldHandle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PredicateBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpRequestException&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
                &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceUnavailable&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;MaxRetryAttempts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Delay&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMilliseconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;BackoffType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DelayBackoffType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;UseJitter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;OnRetry&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Retry &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AttemptNumber&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DemoCatalogHandler&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://catalog.example/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;catalog&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CatalogClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;None&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;HttpRequestException&lt;/span&gt; &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="n"&gt;TimeoutRejectedException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Ο κατάλογος δεν είναι διαθέσιμος."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Οι ρυθμίσεις σημαίνουν:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ShouldHandle&lt;/code&gt;: retry μόνο για &lt;code&gt;503&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MaxRetryAttempts = 2&lt;/code&gt;: μία αρχική προσπάθεια και μέχρι δύο επαναλήψεις.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Delay&lt;/code&gt;: βασική αναμονή &lt;code&gt;200 ms&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Exponential&lt;/code&gt;: η αναμονή αυξάνεται.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UseJitter&lt;/code&gt;: οι καθυστερήσεις μεταβάλλονται ώστε πολλοί clients να μη συγχρονίζονται.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Η Polly σταματά μόλις πετύχει η κλήση ή εξαντληθούν οι επιτρεπόμενες επαναλήψεις. &lt;a href="https://www.pollydocs.org/strategies/retry.html" rel="noopener noreferrer"&gt;Polly: Retry&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Το εξωτερικό &lt;code&gt;AddTimeout&lt;/code&gt; βάζει συνολικό όριο πέντε δευτερολέπτων, μαζί με τις αναμονές. Η ακύρωση λειτουργεί συνεργατικά μέσω του cancellation token. &lt;a href="https://www.pollydocs.org/strategies/timeout.html" rel="noopener noreferrer"&gt;Polly: Timeout&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Η HTTP κλήση — &lt;code&gt;CatalogClient.cs&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Δημιουργούμε το αρχείο:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Polly&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CatalogClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ResiliencePipeline&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"products/1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAsStringAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το &lt;code&gt;ExecuteAsync&lt;/code&gt; εκτελεί την κλήση μέσα στην pipeline.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;EnsureSuccessStatusCode()&lt;/code&gt; μετατρέπει μια αποτυχημένη HTTP απάντηση σε &lt;code&gt;HttpRequestException&lt;/code&gt;. Αν το status είναι &lt;code&gt;503&lt;/code&gt;, η πολιτική επιτρέπει επανάληψη.&lt;/p&gt;

&lt;p&gt;Κάθε προσπάθεια χρησιμοποιεί το token της pipeline και αποδεσμεύει την απάντησή της με &lt;code&gt;using&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Η προσομοίωση — &lt;code&gt;DemoCatalogHandler.cs&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Για εύκολη δοκιμή, ένας HTTP handler δημιουργεί τις απαντήσεις τοπικά: δύο φορές &lt;code&gt;503&lt;/code&gt; και μετά &lt;code&gt;200&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DemoCatalogHandler&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HttpMessageHandler&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;_attempts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpResponseMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;HttpRequestMessage&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThrowIfCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Interlocked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;_attempts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceUnavailable&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"HTTP προσπάθεια &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpResponseMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StringContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Mechanical keyboard"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το URL του παραδείγματος είναι ενδεικτικό. Τις απαντήσεις τις δίνει ο handler.&lt;/p&gt;

&lt;p&gt;Για πραγματικό API, χρησιμοποιούμε &lt;code&gt;new HttpClient()&lt;/code&gt; στη θέση του &lt;code&gt;new HttpClient(new DemoCatalogHandler())&lt;/code&gt; και ορίζουμε την πραγματική &lt;code&gt;BaseAddress&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Εκτέλεση&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αποτέλεσμα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP προσπάθεια 1: 503
Retry 1
HTTP προσπάθεια 2: 503
Retry 2
HTTP προσπάθεια 3: 200
Mechanical keyboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Σε κάθε νέα εκκίνηση της εφαρμογής, το σενάριο ξεκινά από την αρχή.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Και η αντικαταβολή;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Αν μια πληρωμή ολοκληρωθεί αλλά χαθεί η απάντηση, ένα timeout δεν αποδεικνύει ότι η χρέωση απέτυχε.&lt;/p&gt;

&lt;p&gt;Γι’ αυτό, σε ενέργειες που αλλάζουν δεδομένα, χρειαζόμαστε ασφαλή επανάληψη — για παράδειγμα, idempotency key που αναγνωρίζει και διαχειρίζεται σωστά ο server.&lt;/p&gt;

&lt;p&gt;Ο διανομέας ξαναχτυπάει. Η ίδια αντικαταβολή πρέπει να καταχωριστεί μία φορά.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Πού μπαίνει στην αρχιτεκτονική&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Οι ευθύνες εδώ είναι μικρές και σαφείς: το &lt;code&gt;Program&lt;/code&gt; συνθέτει την εφαρμογή, το &lt;code&gt;CatalogClient&lt;/code&gt; διαβάζει δεδομένα και ο δοκιμαστικός handler προσομοιώνει τον πάροχο.&lt;/p&gt;

&lt;p&gt;Σε μια εφαρμογή με Clean Architecture, ο HTTP client και η πολιτική resilience ανήκουν στην Infrastructure. Το Application χρησιμοποιεί ένα interface για την ανάγνωση, διατηρώντας τις τεχνικές λεπτομέρειες έξω από το use case.&lt;/p&gt;




&lt;p&gt;&lt;a href="mailto:nikosstit@gmail.com"&gt;nikosstit@gmail.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>architecture</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SOAP με απλά λόγια</title>
      <dc:creator>nikosst</dc:creator>
      <pubDate>Mon, 02 Nov 2026 21:20:11 +0000</pubDate>
      <link>https://dev.to/__b63657/soap-giati-to-tou-internet-khresimopoieitai-akoma-4hc6</link>
      <guid>https://dev.to/__b63657/soap-giati-to-tou-internet-khresimopoieitai-akoma-4hc6</guid>
      <description>&lt;p&gt;Καταλαβαίνουμε το SOAP βήμα βήμα, με ένα απλό παράδειγμα προϊόντος, XML και JSON, λίγο C# και μια καθαρή εικόνα της Clean Architecture.&lt;/p&gt;




&lt;p&gt;Έχεις ένα ηλεκτρονικό κατάστημα και θέλεις να εμφανίσεις την τιμή ενός προϊόντος. Η τιμή βρίσκεται στο σύστημα του προμηθευτή σου. Η εφαρμογή σου πρέπει να τη ζητήσει, να πάρει μια απάντηση και να εμφανίσει το σωστό ποσό στον πελάτη.&lt;/p&gt;

&lt;p&gt;Ας υποθέσουμε ότι ο προμηθευτής χρησιμοποιεί SOAP. Θα ακολουθήσουμε αυτή τη μία επικοινωνία από την αρχή μέχρι το τέλος: ζητάμε την τιμή του προϊόντος &lt;code&gt;123&lt;/code&gt; και παίρνουμε την απάντηση &lt;code&gt;19.90 EUR&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Το SOAP είναι ένας συμφωνημένος τρόπος επικοινωνίας μεταξύ εφαρμογών.&lt;/strong&gt; Η λέξη «πρωτόκολλο» σημαίνει εδώ ότι οι δύο πλευρές ακολουθούν κοινούς κανόνες για τα μηνύματα που ανταλλάσσουν. Το SOAP χρησιμοποιεί XML για τη μορφή αυτών των μηνυμάτων. &lt;a href="https://www.w3.org/TR/soap12-part0/" rel="noopener noreferrer"&gt;Εισαγωγή του W3C στο SOAP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Στο παράδειγμά μας, η επικοινωνία είναι αυτή:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Το κατάστημά μας                         Ο προμηθευτής

«Πόσο κοστίζει το προϊόν 123;»  ────────►
                              ◄────────  «Κοστίζει 19,90 ευρώ»
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Οι εφαρμογές μεταφέρουν αυτή την ερώτηση και την απάντηση σε μια συγκεκριμένη δομή. Πρώτα θα καταλάβουμε την XML και μετά θα τη βάλουμε μέσα σε ένα μήνυμα SOAP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Η XML οργανώνει τις πληροφορίες με ετικέτες.&lt;/strong&gt; Για παράδειγμα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Product&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Id&amp;gt;&lt;/span&gt;123&lt;span class="nt"&gt;&amp;lt;/Id&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Price&amp;gt;&lt;/span&gt;19.90&lt;span class="nt"&gt;&amp;lt;/Price&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Product&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Μπορούμε να το διαβάσουμε σαν πρόταση: «Υπάρχει ένα προϊόν. Ο κωδικός του είναι 123 και η τιμή του είναι 19.90».&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;&amp;lt;Product&amp;gt;&lt;/code&gt; ανοίγει το στοιχείο του προϊόντος και το &lt;code&gt;&amp;lt;/Product&amp;gt;&lt;/code&gt; το κλείνει. Μέσα του υπάρχουν δύο μικρότερα στοιχεία: το &lt;code&gt;Id&lt;/code&gt; και το &lt;code&gt;Price&lt;/code&gt;. Αυτή η σχέση δημιουργεί μια δομή σαν δέντρο, όπου κάθε στοιχείο μπορεί να περιέχει άλλα στοιχεία ή μια τιμή.&lt;/p&gt;

&lt;p&gt;Τα ονόματα έχουν σημασία: το &lt;code&gt;&amp;lt;Price&amp;gt;&lt;/code&gt; είναι διαφορετικό από το &lt;code&gt;&amp;lt;price&amp;gt;&lt;/code&gt;, επειδή η XML ξεχωρίζει πεζά και κεφαλαία. Επίσης, κάθε στοιχείο πρέπει να κλείνει σωστά και τα στοιχεία να μπαίνουν το ένα μέσα στο άλλο με σωστή σειρά. &lt;a href="https://www.w3.org/TR/xml/" rel="noopener noreferrer"&gt;Βασικοί κανόνες XML&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Η XML επιτρέπει και &lt;strong&gt;attributes&lt;/strong&gt;, δηλαδή πρόσθετες πληροφορίες μέσα στο αρχικό tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Price&lt;/span&gt; &lt;span class="na"&gt;currency=&lt;/span&gt;&lt;span class="s"&gt;"EUR"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;19.90&lt;span class="nt"&gt;&amp;lt;/Price&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Εδώ το &lt;code&gt;19.90&lt;/code&gt; είναι το περιεχόμενο του στοιχείου &lt;code&gt;Price&lt;/code&gt;. Το &lt;code&gt;currency="EUR"&lt;/code&gt; είναι ένα attribute που μας λέει ότι το ποσό είναι σε ευρώ.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Το SOAP βάζει την πληροφορία μέσα σε έναν τυποποιημένο φάκελο.&lt;/strong&gt; Στο δικό μας αίτημα θέλουμε να καλέσουμε τη λειτουργία &lt;code&gt;GetPrice&lt;/code&gt;, δηλαδή «βρες τιμή», για το προϊόν &lt;code&gt;123&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;soap:Envelope&lt;/span&gt;
    &lt;span class="na"&gt;xmlns:soap=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.xmlsoap.org/soap/envelope/"&lt;/span&gt;
    &lt;span class="na"&gt;xmlns:shop=&lt;/span&gt;&lt;span class="s"&gt;"urn:shop"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;soap:Body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;shop:GetPrice&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;shop:ProductId&amp;gt;&lt;/span&gt;123&lt;span class="nt"&gt;&amp;lt;/shop:ProductId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/shop:GetPrice&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/soap:Body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/soap:Envelope&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το διαβάζουμε από έξω προς τα μέσα:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Το &lt;code&gt;Envelope&lt;/code&gt; είναι ο εξωτερικός φάκελος του μηνύματος.&lt;/li&gt;
&lt;li&gt;Το &lt;code&gt;Body&lt;/code&gt; είναι το μέρος που περιέχει την πληροφορία που στέλνουμε.&lt;/li&gt;
&lt;li&gt;Το &lt;code&gt;GetPrice&lt;/code&gt; είναι η λειτουργία που ζητάμε από την υπηρεσία.&lt;/li&gt;
&lt;li&gt;Το &lt;code&gt;ProductId&lt;/code&gt; δίνει τον κωδικό του προϊόντος: &lt;code&gt;123&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Το SOAP ορίζει τα &lt;code&gt;Envelope&lt;/code&gt; και &lt;code&gt;Body&lt;/code&gt;. Ο προμηθευτής ορίζει τα &lt;code&gt;GetPrice&lt;/code&gt; και &lt;code&gt;ProductId&lt;/code&gt;, σύμφωνα με τη δική του υπηρεσία. Έτσι ξέρουμε ποιο κομμάτι αφορά τον γενικό τρόπο επικοινωνίας και ποιο αφορά τη συγκεκριμένη δουλειά που θέλουμε να γίνει. &lt;a href="https://www.w3.org/TR/2000/NOTE-SOAP-20000508/" rel="noopener noreferrer"&gt;Δομή μηνύματος SOAP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Μπορεί να βλέπεις και ένα &lt;code&gt;Header&lt;/code&gt; πριν από το &lt;code&gt;Body&lt;/code&gt;. Είναι προαιρετικό και μεταφέρει πρόσθετες πληροφορίες για την επεξεργασία του μηνύματος. Στο απλό μας παράδειγμα δεν το χρειαζόμαστε.&lt;/p&gt;

&lt;p&gt;Τα &lt;code&gt;soap:&lt;/code&gt; και &lt;code&gt;shop:&lt;/code&gt; είναι μικρά αναγνωριστικά που ξεχωρίζουν δύο ομάδες στοιχείων. Το &lt;code&gt;soap:&lt;/code&gt; συνδέεται με τους κανόνες του SOAP και το &lt;code&gt;shop:&lt;/code&gt; με τα στοιχεία της υπηρεσίας μας. Οι δηλώσεις &lt;code&gt;xmlns&lt;/code&gt; δείχνουν σε ποιο &lt;strong&gt;namespace&lt;/strong&gt;, δηλαδή σε ποια ομάδα ονομάτων, ανήκει κάθε prefix.&lt;/p&gt;

&lt;p&gt;Κράτησε αυτή τη λεπτομέρεια για την ανάγνωση του XML: κάποιος μπορεί να χρησιμοποιεί &lt;code&gt;s:&lt;/code&gt; αντί για &lt;code&gt;soap:&lt;/code&gt;. Εφόσον το συνδέει με το ίδιο namespace, η σημασία παραμένει ίδια. Ο κώδικάς μας θα αναγνωρίζει το namespace και το όνομα του στοιχείου. &lt;a href="https://www.w3.org/TR/xml-names/" rel="noopener noreferrer"&gt;Namespaces στην XML&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Το αίτημα φτάνει στη διεύθυνση της υπηρεσίας.&lt;/strong&gt; Αυτή η διεύθυνση ονομάζεται endpoint. Στο παράδειγμά μας, η εφαρμογή στέλνει το XML με HTTP POST. Η υπηρεσία διαβάζει τον κωδικό &lt;code&gt;123&lt;/code&gt;, αναζητά την τιμή και επιστρέφει ένα νέο XML μήνυμα.&lt;/p&gt;

&lt;p&gt;Χρησιμοποιούμε τη μορφή SOAP 1.1 σε όλα τα παραδείγματα, ώστε να ακολουθούμε σταθερά τους ίδιους κανόνες. Για μια πραγματική υπηρεσία, ο πάροχος δίνει το endpoint και τις απαιτούμενες ρυθμίσεις αποστολής. &lt;a href="https://www.w3.org/TR/2000/NOTE-SOAP-20000508/" rel="noopener noreferrer"&gt;Μεταφορά SOAP 1.1 μέσω HTTP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Η απάντηση της υποθετικής υπηρεσίας μας είναι:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;soap:Envelope&lt;/span&gt;
    &lt;span class="na"&gt;xmlns:soap=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.xmlsoap.org/soap/envelope/"&lt;/span&gt;
    &lt;span class="na"&gt;xmlns:shop=&lt;/span&gt;&lt;span class="s"&gt;"urn:shop"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;soap:Body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;shop:GetPriceResponse&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;shop:Price&lt;/span&gt; &lt;span class="na"&gt;currency=&lt;/span&gt;&lt;span class="s"&gt;"EUR"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;19.90&lt;span class="nt"&gt;&amp;lt;/shop:Price&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/shop:GetPriceResponse&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/soap:Body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/soap:Envelope&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ο φάκελος παραμένει ίδιος. Το περιεχόμενο του &lt;code&gt;Body&lt;/code&gt; έχει αλλάξει: τώρα περιέχει &lt;code&gt;GetPriceResponse&lt;/code&gt;, δηλαδή την απάντηση στη λειτουργία που καλέσαμε.&lt;/p&gt;

&lt;p&gt;Η πληροφορία που χρειαζόμαστε βρίσκεται εδώ:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;shop:Price&lt;/span&gt; &lt;span class="na"&gt;currency=&lt;/span&gt;&lt;span class="s"&gt;"EUR"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;19.90&lt;span class="nt"&gt;&amp;lt;/shop:Price&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Άρα, η τιμή είναι &lt;code&gt;19.90&lt;/code&gt; και το νόμισμα &lt;code&gt;EUR&lt;/code&gt;. Στο XML η δεκαδική τιμή γράφεται με τελεία· στην οθόνη μπορούμε να την εμφανίσουμε ως &lt;code&gt;19,90 €&lt;/code&gt;. Η διαδρομή που ακολουθούμε για να τη βρούμε είναι:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Envelope → Body → GetPriceResponse → Price
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αυτή είναι και η διαδρομή που θα ακολουθήσει ο κώδικάς μας.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Αν κάτι αποτύχει, η απάντηση μπορεί να περιέχει &lt;code&gt;Fault&lt;/code&gt;.&lt;/strong&gt; Για παράδειγμα, ο προμηθευτής μπορεί να επιστρέψει ένα SOAP σφάλμα όταν δεν μπορεί να επεξεργαστεί το αίτημα. Το &lt;code&gt;Fault&lt;/code&gt; βρίσκεται μέσα στο &lt;code&gt;Body&lt;/code&gt; και περιγράφει το πρόβλημα. Ένας κανονικός SOAP client ελέγχει και αυτή την περίπτωση. Παρακάτω θα διαβάσουμε την επιτυχημένη απάντηση του παραδείγματος. &lt;a href="https://www.w3.org/TR/2000/NOTE-SOAP-20000508/" rel="noopener noreferrer"&gt;Σφάλματα SOAP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Στο C# διαβάζουμε την απάντηση σαν δέντρο.&lt;/strong&gt; Ας υποθέσουμε ότι η HTTP κλήση έχει ήδη ολοκληρωθεί και το XML της απάντησης βρίσκεται σε μια μεταβλητή &lt;code&gt;responseXml&lt;/code&gt;, ως κείμενο.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Xml&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Xml.Linq&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;XDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;responseXml&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;XNamespace&lt;/span&gt; &lt;span class="n"&gt;soap&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://schemas.xmlsoap.org/soap/envelope/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;XNamespace&lt;/span&gt; &lt;span class="n"&gt;shop&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"urn:shop"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;priceElement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Root&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;soap&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"Body"&lt;/span&gt;&lt;span class="p"&gt;)?&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shop&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"GetPriceResponse"&lt;/span&gt;&lt;span class="p"&gt;)?&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shop&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"Price"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;priceElement&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Δεν βρέθηκε τιμή στην απάντηση."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;XmlConvert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToDecimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;priceElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;currency&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;priceElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Τιμή: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ας το δούμε βήμα βήμα:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Το &lt;code&gt;XDocument.Parse(responseXml)&lt;/code&gt; διαβάζει το κείμενο και δημιουργεί ένα δέντρο XML.&lt;/li&gt;
&lt;li&gt;Τα δύο &lt;code&gt;XNamespace&lt;/code&gt; αντιστοιχούν στα namespaces που είδαμε στην απάντηση.&lt;/li&gt;
&lt;li&gt;Το &lt;code&gt;document.Root&lt;/code&gt; μάς δίνει το εξωτερικό στοιχείο, το &lt;code&gt;Envelope&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Με κάθε &lt;code&gt;Element(...)&lt;/code&gt; κατεβαίνουμε ένα επίπεδο: στο &lt;code&gt;Body&lt;/code&gt;, μετά στο &lt;code&gt;GetPriceResponse&lt;/code&gt; και τέλος στο &lt;code&gt;Price&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Το &lt;code&gt;priceElement.Value&lt;/code&gt; διαβάζει το κείμενο &lt;code&gt;19.90&lt;/code&gt;. Το &lt;code&gt;XmlConvert.ToDecimal&lt;/code&gt; το μετατρέπει σε αριθμό τύπου &lt;code&gt;decimal&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Το &lt;code&gt;Attribute("currency")&lt;/code&gt; διαβάζει το νόμισμα &lt;code&gt;EUR&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Τα &lt;code&gt;?&lt;/code&gt; στην αλυσίδα επιτρέπουν στην αναζήτηση να σταματήσει αν λείπει κάποιο στοιχείο. Ο έλεγχος &lt;code&gt;if&lt;/code&gt; μάς δίνει τότε ένα κατανοητό σφάλμα, αντί να συνεχίσουμε σαν να βρήκαμε τιμή.&lt;/p&gt;

&lt;p&gt;Έτσι, από ένα μεγάλο XML μήνυμα καταλήγουμε σε δύο απλές τιμές της εφαρμογής: έναν αριθμό και ένα νόμισμα. &lt;a href="https://learn.microsoft.com/en-us/dotnet/standard/linq/linq-xml-overview" rel="noopener noreferrer"&gt;Ανάγνωση XML με LINQ to XML&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.xml.xmlconvert.todecimal?view=net-10.0" rel="noopener noreferrer"&gt;Μετατροπή σε decimal&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Το JSON μπορεί να περιγράψει την ίδια πληροφορία με διαφορετική γραφή.&lt;/strong&gt; Η τιμή του προϊόντος μας θα μπορούσε να εμφανίζεται έτσι:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;19.90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το XML χρησιμοποιεί tags και attributes. Το JSON χρησιμοποιεί ονόματα πεδίων και τιμές. Για ένα τόσο απλό μήνυμα, το JSON είναι συνήθως πιο σύντομο και διαβάζεται εύκολα ως αντικείμενο.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Αυτό που θέλουμε να περιγράψουμε&lt;/th&gt;
&lt;th&gt;XML&lt;/th&gt;
&lt;th&gt;JSON&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Τιμή προϊόντος&lt;/td&gt;
&lt;td&gt;Περιεχόμενο του στοιχείου &lt;code&gt;Price&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Τιμή του πεδίου &lt;code&gt;price&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Νόμισμα&lt;/td&gt;
&lt;td&gt;Attribute &lt;code&gt;currency="EUR"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Πεδίο &lt;code&gt;"currency": "EUR"&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Οργάνωση&lt;/td&gt;
&lt;td&gt;Στοιχεία μέσα σε άλλα στοιχεία&lt;/td&gt;
&lt;td&gt;Αντικείμενα και πίνακες&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Η βασική διάκριση είναι η εξής: &lt;strong&gt;XML και JSON είναι μορφές δεδομένων. Το SOAP είναι πρωτόκολλο επικοινωνίας που χρησιμοποιεί XML.&lt;/strong&gt; Το JSON από μόνο του δεν ορίζει πού στέλνουμε ένα μήνυμα ή ποια λειτουργία θα εκτελέσει ο παραλήπτης. &lt;a href="https://www.rfc-editor.org/rfc/rfc8259" rel="noopener noreferrer"&gt;Προδιαγραφή JSON&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Το WSDL μάς λέει τι περιμένει η υπηρεσία.&lt;/strong&gt; Επιστρέφοντας στον προμηθευτή μας, πρέπει κάπως να μάθουμε ότι η λειτουργία λέγεται &lt;code&gt;GetPrice&lt;/code&gt;, ότι δέχεται &lt;code&gt;ProductId&lt;/code&gt; και ποια μορφή έχει η απάντηση.&lt;/p&gt;

&lt;p&gt;Αυτές τις πληροφορίες περιγράφει το WSDL, ένα αρχείο που λειτουργεί σαν τεχνικός οδηγός της υπηρεσίας. Περιλαμβάνει τις διαθέσιμες λειτουργίες, τα μηνύματα που ανταλλάσσονται και τις διευθύνσεις επικοινωνίας. Έτσι, ο προγραμματιστής μπορεί να υλοποιήσει τη σύνδεση βάσει συγκεκριμένης συμφωνίας. &lt;a href="https://www.w3.org/TR/wsdl.html" rel="noopener noreferrer"&gt;Περιγραφή υπηρεσιών με WSDL&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Συχνά το project περιέχει ήδη έναν client που έχει δημιουργηθεί από το WSDL. Τον αναγνωρίζεις από τις μεθόδους που αντιστοιχούν στις λειτουργίες της υπηρεσίας. Αυτός αναλαμβάνει την κατασκευή και την ανάγνωση των μηνυμάτων, ώστε ο υπόλοιπος κώδικας να καλεί συνηθισμένες μεθόδους C#. &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/additional-tools/dotnet-svcutil-guide" rel="noopener noreferrer"&gt;Δημιουργία SOAP client σε .NET&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Τα DTD και XSD αφορούν τους κανόνες των δεδομένων.&lt;/strong&gt; Ένα XML μπορεί να είναι σωστά γραμμένο, αλλά να λείπει μια πληροφορία που απαιτεί η υπηρεσία. Για παράδειγμα, το αίτημα μπορεί να έχει σωστά tags αλλά να μην περιέχει κωδικό προϊόντος.&lt;/p&gt;

&lt;p&gt;Το &lt;strong&gt;DTD&lt;/strong&gt; είναι ένας τρόπος περιγραφής της επιτρεπόμενης δομής ενός XML εγγράφου. Μπορεί να ορίζει ποια στοιχεία πρέπει να υπάρχουν και με ποια σειρά. Στα μηνύματα SOAP, όμως, απαγορεύεται η δήλωση &lt;code&gt;DOCTYPE&lt;/code&gt;, μέσω της οποίας δηλώνεται DTD. &lt;a href="https://www.w3.org/TR/xml/" rel="noopener noreferrer"&gt;DTD στην XML&lt;/a&gt;, &lt;a href="https://www.w3.org/TR/soap12-part1/" rel="noopener noreferrer"&gt;Περιορισμός στα μηνύματα SOAP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Στα συμβόλαια SOAP συναντάμε συνήθως &lt;strong&gt;XSD&lt;/strong&gt;. Το XSD μπορεί να ορίζει και τύπους δεδομένων: για παράδειγμα, ότι η τιμή είναι δεκαδικός αριθμός. Έτσι, όπου εφαρμόζεται επικύρωση με αυτό το schema, η τιμή &lt;code&gt;19.90&lt;/code&gt; μπορεί να γίνει δεκτή, ενώ το κείμενο &lt;code&gt;δεκαεννέα ευρώ&lt;/code&gt; απορρίπτεται από το ίδιο αριθμητικό πεδίο. &lt;a href="https://www.w3.org/TR/xmlschema-0/" rel="noopener noreferrer"&gt;Εισαγωγή στο XML Schema&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Οι τρεις έννοιες συνδέονται με το παράδειγμά μας ως εξής:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Έννοια&lt;/th&gt;
&lt;th&gt;Τι μας λέει για την υπηρεσία τιμών&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SOAP&lt;/td&gt;
&lt;td&gt;Πώς οργανώνουμε το μήνυμα που ανταλλάσσουμε.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WSDL&lt;/td&gt;
&lt;td&gt;Ποια λειτουργία καλούμε και πώς επικοινωνούμε μαζί της.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XSD&lt;/td&gt;
&lt;td&gt;Ποια στοιχεία και ποιους τύπους δεδομένων περιμένει το συμβόλαιο.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Στην Clean Architecture δίνουμε στον κώδικα SOAP μια συγκεκριμένη θέση.&lt;/strong&gt; Η εφαρμογή του καταστήματος χρειάζεται την τιμή του προϊόντος. Ο τρόπος με τον οποίο μιλάμε με τον προμηθευτή ανήκει στη σύνδεση με το εξωτερικό σύστημα.&lt;/p&gt;

&lt;p&gt;Μπορούμε να οργανώσουμε αυτή τη μικρή διαδρομή ως εξής:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Μέρος της εφαρμογής&lt;/th&gt;
&lt;th&gt;Ευθύνη στο παράδειγμά μας&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Application&lt;/td&gt;
&lt;td&gt;Ορίζει ότι χρειάζεται να μπορεί να ζητήσει την τιμή ενός προϊόντος.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure&lt;/td&gt;
&lt;td&gt;Καλεί την υπηρεσία SOAP και μετατρέπει την απάντηση σε δεδομένα της εφαρμογής.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;Δέχεται το αίτημα του δικού μας χρήστη και επιστρέφει το αποτέλεσμα.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Στο Application μπορούμε να έχουμε ένα interface, δηλαδή μια συμφωνία για τη λειτουργία «βρες τιμή». Η κλάση που υλοποιεί τη σύνδεση SOAP βρίσκεται στο Infrastructure και ακολουθεί αυτή τη συμφωνία. Ο κώδικας ανάγνωσης XML που είδαμε ανήκει εκεί, μαζί με την υπόλοιπη επικοινωνία με τον προμηθευτή.&lt;/p&gt;

&lt;p&gt;Η βασική αρχή είναι ότι ο κώδικας της επιχειρηματικής λειτουργίας εξαρτάται από αυτή τη συμφωνία. Η υλοποίηση της εξωτερικής σύνδεσης εξαρτάται επίσης από τη συμφωνία του Application. Με αυτόν τον τρόπο, η επιχειρηματική λειτουργία μπορεί να χρησιμοποιήσει διαφορετική υλοποίηση όταν αλλάξει ο τρόπος επικοινωνίας. &lt;a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html" rel="noopener noreferrer"&gt;Η αρχή των εξαρτήσεων στην Clean Architecture&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Η διαδρομή των δεδομένων είναι πλέον ξεκάθαρη:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ο χρήστης ζητά την τιμή
          ↓
Η εφαρμογή ζητά τα δεδομένα από τον προμηθευτή
          ↓
Η σύνδεση SOAP στέλνει το XML και διαβάζει την απάντηση
          ↓
Η εφαρμογή παίρνει την τιμή 19.90 και το νόμισμα EUR
          ↓
Ο χρήστης βλέπει 19,90 €
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Οι εταιρείες χρησιμοποιούν SOAP όταν εξυπηρετεί τις διασυνδέσεις τους.&lt;/strong&gt; Μπορεί ο προμηθευτής να διαθέτει αυτή τη συγκεκριμένη υπηρεσία ή μια εταιρεία να έχει ήδη πολλές λειτουργικές συνδέσεις που βασίζονται σε αυτή. Η αλλαγή απαιτεί συντονισμό και ελέγχους, οπότε χρειάζεται αντίστοιχο όφελος για την επιχείρηση.&lt;/p&gt;

&lt;p&gt;Για τον προγραμματιστή που αναλαμβάνει τη σύνδεση, το πρακτικό σημείο εκκίνησης είναι το ίδιο: βρίσκει ποια λειτουργία πρέπει να καλέσει, τι περιμένει το αίτημα και πού βρίσκεται η πληροφορία στην απάντηση. Στο δικό μας παράδειγμα, όλη αυτή η επικοινωνία εξυπηρετεί μία απλή ανάγκη: να εμφανιστεί η σωστή τιμή του προϊόντος στην οθόνη.&lt;/p&gt;




&lt;p&gt;&lt;a href="mailto:nikosstit@gmail.com"&gt;nikosstit@gmail.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>soap</category>
      <category>dotnet</category>
      <category>architecture</category>
      <category>beginners</category>
    </item>
    <item>
      <title>FusionCache στο .NET: Πώς ένας Barista μπορεί να μας μάθει το Caching ☕</title>
      <dc:creator>nikosst</dc:creator>
      <pubDate>Sun, 01 Nov 2026 23:46:44 +0000</pubDate>
      <link>https://dev.to/__b63657/fusioncache-sto-net-pos-enas-barista-mporei-na-mas-mathei-to-caching-3o3g</link>
      <guid>https://dev.to/__b63657/fusioncache-sto-net-pos-enas-barista-mporei-na-mas-mathei-to-caching-3o3g</guid>
      <description>&lt;h1&gt;
  
  
  FusionCache στο .NET: Πώς ένας Barista μπορεί να μας μάθει το Caching ☕
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Εισαγωγή
&lt;/h2&gt;

&lt;p&gt;Φαντάσου ότι μπαίνεις κάθε πρωί στο ίδιο coffee shop και παραγγέλνεις τον αγαπημένο σου καφέ.&lt;/p&gt;

&lt;p&gt;Την πρώτη φορά, ο barista σε ρωτάει:&lt;/p&gt;

&lt;p&gt;— Τι θα θέλατε;&lt;/p&gt;

&lt;p&gt;— Έναν cappuccino με γάλα αμυγδάλου, χωρίς ζάχαρη.&lt;/p&gt;

&lt;p&gt;Ο barista ετοιμάζει τον καφέ και εξυπηρετεί την παραγγελία σου.&lt;/p&gt;

&lt;p&gt;Την επόμενη ημέρα επιστρέφεις. Αυτή τη φορά, πριν προλάβεις να μιλήσεις, ο barista σε αναγνωρίζει:&lt;/p&gt;

&lt;p&gt;— Έναν cappuccino με γάλα αμυγδάλου, χωρίς ζάχαρη;&lt;/p&gt;

&lt;p&gt;— Ακριβώς!&lt;/p&gt;

&lt;p&gt;Ο barista δεν χρειάστηκε να σε ρωτήσει ξανά. Θυμόταν την προηγούμενη παραγγελία σου.&lt;/p&gt;

&lt;p&gt;Αυτή η απλή ιδέα βρίσκεται πίσω από μία από τις σημαντικότερες τεχνικές βελτιστοποίησης στις εφαρμογές: το &lt;strong&gt;Caching&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Στον κόσμο του .NET, μία από τις βιβλιοθήκες που μας βοηθούν να υλοποιήσουμε αυτή τη λογική είναι η &lt;strong&gt;FusionCache&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Σκοπός αυτού του άρθρου είναι να κατανοήσουμε τι είναι το FusionCache, γιατί το χρειαζόμαστε, πώς λειτουργεί και πώς μπορούμε να το χρησιμοποιήσουμε σε μία πραγματική εφαρμογή .NET.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Τι είναι το Caching;
&lt;/h2&gt;

&lt;p&gt;Ας ξεκινήσουμε από τα βασικά.&lt;/p&gt;

&lt;p&gt;Όταν μία εφαρμογή χρειάζεται δεδομένα, συνήθως τα αναζητά σε κάποια πηγή, όπως:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Μία βάση δεδομένων.&lt;/li&gt;
&lt;li&gt;Ένα εξωτερικό API.&lt;/li&gt;
&lt;li&gt;Ένα αρχείο.&lt;/li&gt;
&lt;li&gt;Μία υπηρεσία άλλου συστήματος.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Για παράδειγμα, έχουμε ένα API που επιστρέφει τα στοιχεία ενός προϊόντος.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_dbContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Products&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefaultAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Κάθε φορά που καλείται αυτή η μέθοδος, εκτελείται ένα ερώτημα στη βάση δεδομένων.&lt;/p&gt;

&lt;p&gt;Αν το ίδιο προϊόν ζητηθεί 100 φορές, η εφαρμογή μπορεί να εκτελέσει 100 φορές την ίδια αναζήτηση.&lt;/p&gt;

&lt;p&gt;Αυτό δεν είναι απαραίτητα αποδοτικό.&lt;/p&gt;

&lt;p&gt;Εδώ έρχεται το Caching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caching είναι η προσωρινή αποθήκευση δεδομένων, ώστε να μπορούμε να τα επαναχρησιμοποιήσουμε χωρίς να χρειάζεται να αναζητήσουμε ξανά την αρχική πηγή.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Αντί να ρωτάμε κάθε φορά τη βάση δεδομένων, αποθηκεύουμε προσωρινά το αποτέλεσμα.&lt;/p&gt;

&lt;p&gt;Την επόμενη φορά που θα χρειαστούμε το ίδιο δεδομένο, το αναζητούμε πρώτα στην Cache.&lt;/p&gt;

&lt;p&gt;Αν υπάρχει, το επιστρέφουμε αμέσως.&lt;/p&gt;

&lt;p&gt;Αν δεν υπάρχει, αναζητούμε τα δεδομένα από την αρχική πηγή και αποθηκεύουμε το αποτέλεσμα.&lt;/p&gt;

&lt;p&gt;Αυτή είναι η βασική ιδέα.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Κατανοώντας το Caching μέσα από το παράδειγμα του barista
&lt;/h2&gt;

&lt;p&gt;Ας επιστρέψουμε στο coffee shop.&lt;/p&gt;

&lt;p&gt;Θα αντιστοιχίσουμε τα βασικά στοιχεία του Caching με την καθημερινή λειτουργία του καταστήματος.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Έννοια&lt;/th&gt;
&lt;th&gt;Παράδειγμα με τον barista&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cache&lt;/td&gt;
&lt;td&gt;Η μνήμη του barista&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache Key&lt;/td&gt;
&lt;td&gt;Το αναγνωριστικό του πελάτη&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cached Value&lt;/td&gt;
&lt;td&gt;Η αγαπημένη παραγγελία του πελάτη&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache Hit&lt;/td&gt;
&lt;td&gt;Ο barista θυμάται την παραγγελία&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache Miss&lt;/td&gt;
&lt;td&gt;Ο barista δεν θυμάται και ρωτά ξανά&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expiration&lt;/td&gt;
&lt;td&gt;Ο barista ξεχνά την παλιά παραγγελία μετά από κάποιο διάστημα&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ας δούμε τη διαδικασία.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Πρώτη επίσκεψη — Cache Miss&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ο πελάτης παραγγέλνει για πρώτη φορά.&lt;/p&gt;

&lt;p&gt;Ο barista δεν γνωρίζει τις προτιμήσεις του. Ρωτάει, καταγράφει την παραγγελία και την εξυπηρετεί.&lt;/p&gt;

&lt;p&gt;Στην εφαρμογή μας, αυτό σημαίνει ότι δεν υπάρχει το ζητούμενο δεδομένο στην Cache. Επομένως, πρέπει να το αναζητήσουμε στην αρχική πηγή.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Δεύτερη επίσκεψη — Cache Hit&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ο ίδιος πελάτης επιστρέφει.&lt;/p&gt;

&lt;p&gt;Ο barista θυμάται την παραγγελία του και δεν χρειάζεται να τον ρωτήσει ξανά.&lt;/p&gt;

&lt;p&gt;Στην εφαρμογή μας, το δεδομένο υπάρχει ήδη στην Cache και επιστρέφεται χωρίς νέο ερώτημα στη βάση δεδομένων.&lt;/p&gt;

&lt;p&gt;Αυτό ακριβώς θέλουμε να πετύχουμε.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Τι είναι το Cache Key και γιατί είναι σημαντικό;
&lt;/h2&gt;

&lt;p&gt;Ο barista πρέπει να γνωρίζει σε ποιον πελάτη αντιστοιχεί κάθε παραγγελία.&lt;/p&gt;

&lt;p&gt;Δεν αρκεί να θυμάται ότι κάποιος παραγγέλνει cappuccino. Πρέπει να γνωρίζει ποιος είναι ο συγκεκριμένος πελάτης.&lt;/p&gt;

&lt;p&gt;Αντίστοιχα, στην εφαρμογή μας χρειαζόμαστε ένα &lt;strong&gt;Cache Key&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Το Cache Key είναι ένα μοναδικό αναγνωριστικό που χρησιμοποιούμε για να αποθηκεύσουμε και να αναζητήσουμε ένα δεδομένο.&lt;/p&gt;

&lt;p&gt;Για παράδειγμα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cacheKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"product:42"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αυτό το κλειδί μπορεί να αντιστοιχεί στο προϊόν με ID 42.&lt;/p&gt;

&lt;p&gt;Ένα άλλο προϊόν θα έχει διαφορετικό κλειδί:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cacheKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"product:43"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Με αυτόν τον τρόπο, μπορούμε να αποθηκεύουμε πολλά διαφορετικά δεδομένα χωρίς να μπερδεύονται μεταξύ τους.&lt;/p&gt;

&lt;p&gt;Στην πράξη, τα Cache Keys πρέπει να σχεδιάζονται προσεκτικά. Για παράδειγμα, αν τα δεδομένα εξαρτώνται από τον χρήστη, το κλειδί μπορεί να περιλαμβάνει και το User ID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cacheKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"user:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:orders"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Έτσι, οι παραγγελίες κάθε χρήστη αποθηκεύονται ξεχωριστά.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Τι είναι το FusionCache;
&lt;/h2&gt;

&lt;p&gt;Το FusionCache είναι μία open-source βιβλιοθήκη για .NET, η οποία προσφέρει ένα πιο ολοκληρωμένο μοντέλο caching.&lt;/p&gt;

&lt;p&gt;Δεν είναι απλώς μία αποθήκη δεδομένων.&lt;/p&gt;

&lt;p&gt;Συνδυάζει διαφορετικούς μηχανισμούς caching και προσφέρει επιπλέον δυνατότητες, όπως:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In-memory caching.&lt;/li&gt;
&lt;li&gt;Distributed caching.&lt;/li&gt;
&lt;li&gt;Fail-safe.&lt;/li&gt;
&lt;li&gt;Cache stampede protection.&lt;/li&gt;
&lt;li&gt;Backplane notifications.&lt;/li&gt;
&lt;li&gt;Διαχείριση λήξης δεδομένων.&lt;/li&gt;
&lt;li&gt;Ανθεκτικότητα σε προσωρινά προβλήματα εξωτερικών υπηρεσιών.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Η βασική ιδέα είναι να έχουμε μία ενιαία προσέγγιση για το caching, χωρίς να χρειάζεται να υλοποιούμε μόνοι μας όλες αυτές τις λειτουργίες.&lt;/p&gt;

&lt;p&gt;Για να καταλάβουμε πώς λειτουργεί, πρέπει πρώτα να γνωρίσουμε δύο βασικές έννοιες: το L1 και το L2 Cache.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Τι είναι το L1 και το L2 Cache;
&lt;/h2&gt;

&lt;p&gt;Ας υποθέσουμε ότι το coffee shop έχει δύο τρόπους να θυμάται τις παραγγελίες.&lt;/p&gt;

&lt;p&gt;Ο πρώτος είναι η προσωπική μνήμη κάθε barista.&lt;/p&gt;

&lt;p&gt;Ο δεύτερος είναι ένα κοινό τετράδιο στο οποίο καταγράφονται οι παραγγελίες, ώστε να μπορούν να τις δουν όλοι οι barista.&lt;/p&gt;

&lt;p&gt;Αυτή η αναλογία μάς βοηθά να κατανοήσουμε τα δύο επίπεδα caching.&lt;/p&gt;

&lt;h3&gt;
  
  
  L1 Cache — In-Memory Cache
&lt;/h3&gt;

&lt;p&gt;Το L1 Cache είναι η τοπική μνήμη της εφαρμογής.&lt;/p&gt;

&lt;p&gt;Στο παράδειγμά μας, είναι η προσωπική μνήμη του barista.&lt;/p&gt;

&lt;p&gt;Αν ένας barista θυμάται την παραγγελία σου, μπορεί να σε εξυπηρετήσει αμέσως.&lt;/p&gt;

&lt;p&gt;Στο .NET, τα δεδομένα αποθηκεύονται στη μνήμη της συγκεκριμένης διεργασίας (application process).&lt;/p&gt;

&lt;p&gt;Πλεονεκτήματα:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Πολύ γρήγορη πρόσβαση.&lt;/li&gt;
&lt;li&gt;Δεν απαιτεί επικοινωνία με εξωτερική υπηρεσία.&lt;/li&gt;
&lt;li&gt;Μικρό latency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Υπάρχει όμως ένας σημαντικός περιορισμός.&lt;/p&gt;

&lt;p&gt;Αν έχουμε περισσότερα από ένα application instances, κάθε instance έχει τη δική του τοπική μνήμη.&lt;/p&gt;

&lt;h3&gt;
  
  
  L2 Cache — Distributed Cache
&lt;/h3&gt;

&lt;p&gt;Τώρα ας υποθέσουμε ότι το coffee shop έχει τρεις barista.&lt;/p&gt;

&lt;p&gt;Ο καθένας θυμάται τις παραγγελίες που έχει εξυπηρετήσει προσωπικά.&lt;/p&gt;

&lt;p&gt;Όμως, αν ένας πελάτης εξυπηρετηθεί από διαφορετικό barista, ο δεύτερος μπορεί να μη γνωρίζει την παραγγελία του.&lt;/p&gt;

&lt;p&gt;Για να αντιμετωπίσουμε αυτό το πρόβλημα, δημιουργούμε ένα κοινό τετράδιο.&lt;/p&gt;

&lt;p&gt;Όλοι οι barista μπορούν να ανατρέξουν σε αυτό και να βρουν τις καταγεγραμμένες παραγγελίες.&lt;/p&gt;

&lt;p&gt;Στην αρχιτεκτονική των εφαρμογών, αυτό αντιστοιχεί στο Distributed Cache.&lt;/p&gt;

&lt;p&gt;Ένα συνηθισμένο παράδειγμα είναι το Redis.&lt;/p&gt;

&lt;p&gt;Το Redis βρίσκεται έξω από τη μνήμη της εφαρμογής και μπορεί να χρησιμοποιείται από πολλά διαφορετικά application instances.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Χαρακτηριστικό&lt;/th&gt;
&lt;th&gt;L1 Cache&lt;/th&gt;
&lt;th&gt;L2 Cache&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Αποθήκευση&lt;/td&gt;
&lt;td&gt;Τοπική μνήμη&lt;/td&gt;
&lt;td&gt;Εξωτερική υπηρεσία&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Πρόσβαση&lt;/td&gt;
&lt;td&gt;Πολύ γρήγορη&lt;/td&gt;
&lt;td&gt;Μέσω δικτύου&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Κοινή χρήση&lt;/td&gt;
&lt;td&gt;Όχι αυτόματα&lt;/td&gt;
&lt;td&gt;Ναι&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Παράδειγμα&lt;/td&gt;
&lt;td&gt;Memory Cache&lt;/td&gt;
&lt;td&gt;Redis&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Το FusionCache μπορεί να συνδυάσει αυτά τα δύο επίπεδα.&lt;/p&gt;

&lt;p&gt;Πρώτα αναζητά τα δεδομένα στο L1. Αν δεν τα βρει, μπορεί να αναζητήσει στο L2. Αν δεν υπάρχουν ούτε εκεί, εκτελεί το factory που ανακτά τα δεδομένα από την αρχική πηγή.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Τι είναι τα διαφορετικά Application Instances;
&lt;/h2&gt;

&lt;p&gt;Αυτή είναι μία έννοια που αξίζει να κατανοήσουμε καλά.&lt;/p&gt;

&lt;p&gt;Ας υποθέσουμε ότι έχουμε μία εφαρμογή .NET η οποία εξυπηρετεί πολλούς χρήστες.&lt;/p&gt;

&lt;p&gt;Για να αντιμετωπίσουμε την αυξημένη κίνηση, την εκτελούμε σε τρία διαφορετικά instances.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Users
                   |
                   |
              Load Balancer
                   |
          ---------------------
          |         |         |
          v         v         v
       App 1     App 2     App 3
          |         |         |
          v         v         v
        L1        L1        L1
          |         |         |
          -------- Redis -------
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Κάθε application instance είναι μία ξεχωριστή εκτέλεση της εφαρμογής.&lt;/p&gt;

&lt;p&gt;Αυτό σημαίνει ότι:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Το App 1 έχει τη δική του μνήμη.&lt;/li&gt;
&lt;li&gt;Το App 2 έχει τη δική του μνήμη.&lt;/li&gt;
&lt;li&gt;Το App 3 έχει τη δική του μνήμη.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ακόμη και αν όλα εκτελούν τον ίδιο κώδικα, δεν μοιράζονται αυτόματα την τοπική τους μνήμη.&lt;/p&gt;

&lt;p&gt;Για παράδειγμα:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ο χρήστης ζητά το προϊόν 42.&lt;/li&gt;
&lt;li&gt;Το Load Balancer στέλνει το request στο App 1.&lt;/li&gt;
&lt;li&gt;Το App 1 αναζητά το προϊόν στη δική του L1 Cache.&lt;/li&gt;
&lt;li&gt;Δεν το βρίσκει, οπότε αναζητά στο Redis.&lt;/li&gt;
&lt;li&gt;Αν δεν υπάρχει ούτε στο Redis, το ανακτά από τη βάση δεδομένων.&lt;/li&gt;
&lt;li&gt;Το αποτέλεσμα αποθηκεύεται στην Cache.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Λίγο αργότερα, ο ίδιος χρήστης ζητά ξανά το προϊόν 42.&lt;/p&gt;

&lt;p&gt;Αυτή τη φορά το request μπορεί να φτάσει στο App 2.&lt;/p&gt;

&lt;p&gt;Το App 2 δεν έχει απαραίτητα το προϊόν στη δική του L1 Cache. Μπορεί όμως να το βρει στο κοινό Redis.&lt;/p&gt;

&lt;p&gt;Αυτός είναι ένας από τους βασικούς λόγους που χρησιμοποιούμε Distributed Caching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Προσοχή:&lt;/strong&gt; Το application instance δεν είναι το ίδιο πράγμα με το object instance της κλάσης FusionCache. Είναι διαφορετικές έννοιες. Κάθε application instance έχει τη δική του διεργασία και τη δική του τοπική μνήμη.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Πώς λειτουργεί το FusionCache στην πράξη;
&lt;/h2&gt;

&lt;p&gt;Ας δούμε τη διαδικασία με ένα απλό παράδειγμα.&lt;/p&gt;

&lt;p&gt;Έχουμε μία εφαρμογή που επιστρέφει πληροφορίες προϊόντων.&lt;/p&gt;

&lt;p&gt;Θέλουμε να αποφύγουμε την επαναλαμβανόμενη αναζήτηση του ίδιου προϊόντος στη βάση δεδομένων.&lt;/p&gt;

&lt;p&gt;Η λογική είναι:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
   |
   v
FusionCache
   |
   v
Check L1 Cache
   |
   |-- Found --&amp;gt; Return Data
   |
   |-- Not Found
          |
          v
       Check L2 Cache
          |
          |-- Found --&amp;gt; Return Data
          |
          |-- Not Found
                 |
                 v
             Database
                 |
                 v
             Store Result
                 |
                 v
             Return Data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το σημαντικό είναι ότι ο κώδικας της εφαρμογής μας δεν χρειάζεται να διαχειρίζεται χειροκίνητα κάθε επίπεδο.&lt;/p&gt;

&lt;p&gt;Χρησιμοποιεί το FusionCache ως ενιαίο σημείο πρόσβασης.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Υλοποίηση FusionCache σε .NET
&lt;/h2&gt;

&lt;p&gt;Ας περάσουμε σε ένα πρακτικό παράδειγμα.&lt;/p&gt;

&lt;h3&gt;
  
  
  Βήμα 1: Εγκατάσταση του NuGet Package
&lt;/h3&gt;

&lt;p&gt;Εγκαθιστούμε το package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package ZiggyCreatures.FusionCache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Βήμα 2: Registration στο Program.cs
&lt;/h3&gt;

&lt;p&gt;Στο &lt;code&gt;Program.cs&lt;/code&gt; προσθέτουμε:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddFusionCache&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Με αυτόν τον τρόπο δηλώνουμε το FusionCache στο Dependency Injection container.&lt;/p&gt;

&lt;h3&gt;
  
  
  Βήμα 3: Χρήση μέσω Dependency Injection
&lt;/h3&gt;

&lt;p&gt;Ας υποθέσουμε ότι έχουμε ένα &lt;code&gt;ProductService&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IFusionCache&lt;/span&gt; &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;AppDbContext&lt;/span&gt; &lt;span class="n"&gt;_dbContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ProductService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;IFusionCache&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;AppDbContext&lt;/span&gt; &lt;span class="n"&gt;dbContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_cache&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_dbContext&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dbContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cacheKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"product:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetOrSetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_dbContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Products&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefaultAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ας δούμε τι συμβαίνει.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Δημιουργούμε το Cache Key&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cacheKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"product:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το κλειδί προσδιορίζει ποιο προϊόν αναζητούμε.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Καλούμε το GetOrSetAsync&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetOrSetAsync&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αυτή είναι η βασική μέθοδος που χρησιμοποιούμε για να αναζητήσουμε ένα δεδομένο στην Cache ή να το δημιουργήσουμε αν δεν υπάρχει.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Ορίζουμε το Factory&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_dbContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Products&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefaultAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το factory είναι η λειτουργία που εκτελείται όταν το δεδομένο δεν υπάρχει στην Cache και πρέπει να ανακτηθεί από την αρχική πηγή.&lt;/p&gt;

&lt;p&gt;Στο παράδειγμά μας, η αρχική πηγή είναι η βάση δεδομένων.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Ορίζουμε τη διάρκεια&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το αποτέλεσμα παραμένει στην Cache για πέντε λεπτά, σύμφωνα με τη διάρκεια που έχουμε ορίσει.&lt;/p&gt;

&lt;p&gt;Την επόμενη φορά που θα ζητηθεί το ίδιο προϊόν μέσα σε αυτό το διάστημα, το FusionCache μπορεί να επιστρέψει το αποθηκευμένο αποτέλεσμα χωρίς να εκτελέσει ξανά το factory.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Τι είναι το Expiration;
&lt;/h2&gt;

&lt;p&gt;Επιστρέφουμε στο coffee shop.&lt;/p&gt;

&lt;p&gt;Ο barista θυμάται την παραγγελία σου σήμερα.&lt;/p&gt;

&lt;p&gt;Αλλά τι γίνεται αν επιστρέψεις μετά από έξι μήνες;&lt;/p&gt;

&lt;p&gt;Μπορεί να έχει αλλάξει η προτίμησή σου. Ίσως πλέον παραγγέλνεις espresso αντί για cappuccino.&lt;/p&gt;

&lt;p&gt;Δεν θέλουμε να κρατάμε όλες τις πληροφορίες για πάντα.&lt;/p&gt;

&lt;p&gt;Αυτό ακριβώς αντιμετωπίζει το Expiration.&lt;/p&gt;

&lt;p&gt;Το Expiration καθορίζει για πόσο χρονικό διάστημα ένα δεδομένο θεωρείται έγκυρο στην Cache.&lt;/p&gt;

&lt;p&gt;Για παράδειγμα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Σημαίνει ότι η συγκεκριμένη καταχώρηση έχει διάρκεια πέντε λεπτών.&lt;/p&gt;

&lt;p&gt;Μετά τη λήξη της, η εφαρμογή θα χρειαστεί να ανακτήσει ξανά τα δεδομένα.&lt;/p&gt;

&lt;p&gt;Η διάρκεια πρέπει να επιλέγεται με βάση τη φύση των δεδομένων.&lt;/p&gt;

&lt;p&gt;Για παράδειγμα:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Τα στοιχεία ενός προϊόντος μπορεί να έχουν διάρκεια αρκετών λεπτών.&lt;/li&gt;
&lt;li&gt;Τα δεδομένα ενός καταλόγου που αλλάζει σπάνια μπορεί να έχουν μεγαλύτερη διάρκεια.&lt;/li&gt;
&lt;li&gt;Τα δεδομένα που αλλάζουν συνεχώς χρειάζονται μικρότερη διάρκεια ή διαφορετική στρατηγική ενημέρωσης.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Δεν υπάρχει μία ιδανική διάρκεια για όλα τα δεδομένα.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Τι είναι το Backplane;
&lt;/h2&gt;

&lt;p&gt;Ας επιστρέψουμε στο coffee shop με τους τρεις barista.&lt;/p&gt;

&lt;p&gt;Ο πρώτος barista έχει αποθηκεύσει στη μνήμη του ότι ο πελάτης προτιμά cappuccino.&lt;/p&gt;

&lt;p&gt;Κάποια στιγμή, ο πελάτης αλλάζει την παραγγελία του και πλέον θέλει espresso.&lt;/p&gt;

&lt;p&gt;Ο πρώτος barista ενημερώνει το κοινό τετράδιο.&lt;/p&gt;

&lt;p&gt;Όμως, τι γίνεται αν οι υπόλοιποι barista εξακολουθούν να θυμούνται την παλιά παραγγελία;&lt;/p&gt;

&lt;p&gt;Χρειαζόμαστε έναν μηχανισμό ενημέρωσης.&lt;/p&gt;

&lt;p&gt;Στο FusionCache, αυτός ο μηχανισμός ονομάζεται &lt;strong&gt;Backplane&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Το Backplane επιτρέπει σε διαφορετικά application instances να ενημερώνονται όταν συμβαίνουν συγκεκριμένες αλλαγές στην Cache.&lt;/p&gt;

&lt;p&gt;Για παράδειγμα:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Το App 1 ενημερώνει ή αφαιρεί ένα cached value.&lt;/li&gt;
&lt;li&gt;Εκπέμπεται μία ειδοποίηση μέσω του Backplane.&lt;/li&gt;
&lt;li&gt;Τα υπόλοιπα application instances λαμβάνουν την ειδοποίηση.&lt;/li&gt;
&lt;li&gt;Μπορούν να αντιδράσουν ανάλογα, για παράδειγμα αφαιρώντας την αντίστοιχη τοπική καταχώρηση.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Έτσι μειώνουμε τον κίνδυνο να συνεχίσουν διαφορετικά instances να χρησιμοποιούν παλιά δεδομένα.&lt;/p&gt;

&lt;p&gt;Το Redis μπορεί να χρησιμοποιηθεί και ως Distributed Cache και ως Backplane, αλλά πρόκειται για διαφορετικούς ρόλους και αντίστοιχες ρυθμίσεις.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Σημαντικό:&lt;/strong&gt; Το Backplane δεν είναι απαραίτητα ένας μηχανισμός μεταφοράς ολόκληρων των cached values. Ο βασικός του ρόλος είναι η επικοινωνία και η ενημέρωση των instances για αλλαγές στην Cache.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Τι είναι το Fail-Safe;
&lt;/h2&gt;

&lt;p&gt;Ας υποθέσουμε ότι ο barista θυμάται την αγαπημένη σου παραγγελία.&lt;/p&gt;

&lt;p&gt;Μία ημέρα, όμως, το σύστημα του καταστήματος παρουσιάζει πρόβλημα και δεν μπορεί να αναζητήσει νέες πληροφορίες.&lt;/p&gt;

&lt;p&gt;Τι θα ήταν προτιμότερο;&lt;/p&gt;

&lt;p&gt;Να μη σου δώσει καθόλου καφέ ή να χρησιμοποιήσει την τελευταία γνωστή παραγγελία σου;&lt;/p&gt;

&lt;p&gt;Σε πολλές περιπτώσεις, η δεύτερη επιλογή είναι προτιμότερη.&lt;/p&gt;

&lt;p&gt;Αυτή είναι η βασική ιδέα του &lt;strong&gt;Fail-Safe&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Το FusionCache μπορεί, εφόσον έχει ενεργοποιηθεί και ρυθμιστεί κατάλληλα, να επιστρέψει μία προηγούμενη cached τιμή ακόμη και αν η προσπάθεια ανανέωσης των δεδομένων αποτύχει.&lt;/p&gt;

&lt;p&gt;Για παράδειγμα:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Έχουμε αποθηκεύσει τα στοιχεία ενός προϊόντος.&lt;/li&gt;
&lt;li&gt;Η διάρκεια της κανονικής Cache λήγει.&lt;/li&gt;
&lt;li&gt;Το FusionCache προσπαθεί να ανακτήσει νέα δεδομένα από τη βάση.&lt;/li&gt;
&lt;li&gt;Η βάση δεδομένων δεν είναι προσωρινά διαθέσιμη.&lt;/li&gt;
&lt;li&gt;Αν υπάρχει διαθέσιμη παλαιότερη τιμή μέσα στα όρια του Fail-Safe, μπορεί να επιστραφεί αυτή.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Αυτό βοηθά την εφαρμογή να συνεχίσει να εξυπηρετεί αιτήματα ακόμη και όταν μία εξωτερική εξάρτηση παρουσιάζει προσωρινό πρόβλημα.&lt;/p&gt;

&lt;p&gt;Φυσικά, δεν είναι κατάλληλο για κάθε είδους δεδομένα. Για παράδειγμα, δεν θα θέλαμε απαραίτητα να επιστρέψουμε παλιά δεδομένα για ένα οικονομικό υπόλοιπο ή μία κρίσιμη συναλλαγή.&lt;/p&gt;

&lt;p&gt;Το Fail-Safe πρέπει να χρησιμοποιείται με βάση τις απαιτήσεις της κάθε επιχειρησιακής λειτουργίας.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Τι είναι το Cache Stampede;
&lt;/h2&gt;

&lt;p&gt;Ας υποθέσουμε ότι το αγαπημένο σου coffee shop έχει μία πολύ δημοφιλή προσφορά.&lt;/p&gt;

&lt;p&gt;Η πληροφορία της προσφοράς έχει αποθηκευτεί στην Cache και εξυπηρετεί χιλιάδες χρήστες.&lt;/p&gt;

&lt;p&gt;Ξαφνικά, η Cache λήγει.&lt;/p&gt;

&lt;p&gt;Πολλοί χρήστες ζητούν ταυτόχρονα την ίδια πληροφορία.&lt;/p&gt;

&lt;p&gt;Αν κάθε request ξεκινήσει ξεχωριστά μία αναζήτηση στη βάση δεδομένων, μπορεί να δημιουργηθεί πολύ μεγάλο φορτίο.&lt;/p&gt;

&lt;p&gt;Αυτό το φαινόμενο ονομάζεται &lt;strong&gt;Cache Stampede&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Στην πράξη, πολλά ταυτόχρονα requests προσπαθούν να ανακτήσουν το ίδιο δεδομένο επειδή δεν υπάρχει πλέον στην Cache.&lt;/p&gt;

&lt;p&gt;Το FusionCache προσφέρει προστασία από αυτό το φαινόμενο, συντονίζοντας τις ταυτόχρονες προσπάθειες ανάκτησης για το ίδιο cache key.&lt;/p&gt;

&lt;p&gt;Έτσι αποφεύγουμε πολλές περιττές εκτελέσεις του ίδιου factory μέσα στην ίδια εφαρμογή.&lt;/p&gt;

&lt;p&gt;Σε περιβάλλον με πολλά application instances, για ισχυρότερο συντονισμό μεταξύ τους μπορεί να χρειαστεί πρόσθετη distributed locking λύση ή κατάλληλη επέκταση.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. Πότε πρέπει να χρησιμοποιούμε FusionCache;
&lt;/h2&gt;

&lt;p&gt;Το FusionCache είναι ιδιαίτερα χρήσιμο όταν:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Έχουμε εφαρμογές με μεγάλο αριθμό requests.&lt;/li&gt;
&lt;li&gt;Θέλουμε να μειώσουμε τα επαναλαμβανόμενα queries στη βάση δεδομένων.&lt;/li&gt;
&lt;li&gt;Ανακτούμε δεδομένα από αργά εξωτερικά APIs.&lt;/li&gt;
&lt;li&gt;Η εφαρμογή μας εκτελείται σε πολλαπλά application instances.&lt;/li&gt;
&lt;li&gt;Θέλουμε συνδυασμό τοπικής και distributed Cache.&lt;/li&gt;
&lt;li&gt;Χρειαζόμαστε μεγαλύτερη ανθεκτικότητα σε προσωρινές αποτυχίες εξωτερικών υπηρεσιών.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Δεν σημαίνει, όμως, ότι πρέπει να κάνουμε caching παντού.&lt;/p&gt;

&lt;p&gt;Για δεδομένα που αλλάζουν συνεχώς ή απαιτούν πάντα την πιο πρόσφατη τιμή, πρέπει να εξετάζουμε προσεκτικά αν το caching είναι κατάλληλο.&lt;/p&gt;

&lt;p&gt;Επίσης, πρέπει να σχεδιάζουμε σωστά τα Cache Keys, τις διάρκειες και την ακύρωση των cached values.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. Συμπέρασμα
&lt;/h2&gt;

&lt;p&gt;Ας επιστρέψουμε για τελευταία φορά στο coffee shop.&lt;/p&gt;

&lt;p&gt;Ο καλός barista δεν χρειάζεται να ρωτάει κάθε πελάτη ξανά και ξανά για την παραγγελία του.&lt;/p&gt;

&lt;p&gt;Θυμάται τις προτιμήσεις του, γνωρίζει πότε χρειάζεται να ενημερώσει τις πληροφορίες του και συνεργάζεται με τους υπόλοιπους barista ώστε να προσφέρει μία γρήγορη και συνεπή εξυπηρέτηση.&lt;/p&gt;

&lt;p&gt;Ακριβώς αυτή τη λογική προσπαθούμε να πετύχουμε και στις εφαρμογές μας.&lt;/p&gt;

&lt;p&gt;Το FusionCache μάς προσφέρει έναν ολοκληρωμένο μηχανισμό caching στο .NET, συνδυάζοντας διαφορετικά επίπεδα αποθήκευσης και δυνατότητες που βοηθούν στην απόδοση και την ανθεκτικότητα της εφαρμογής.&lt;/p&gt;

&lt;p&gt;Το σημαντικότερο είναι να κατανοήσουμε ότι το caching δεν είναι απλώς η αποθήκευση ενός αποτελέσματος στη μνήμη.&lt;/p&gt;

&lt;p&gt;Είναι μία αρχιτεκτονική επιλογή που επηρεάζει την απόδοση, τη συνέπεια των δεδομένων και τη συμπεριφορά της εφαρμογής μας.&lt;/p&gt;

&lt;p&gt;Και όπως ένας καλός barista θυμάται τι προτιμάς, έτσι και μία καλά σχεδιασμένη εφαρμογή πρέπει να γνωρίζει πότε μπορεί να επαναχρησιμοποιήσει δεδομένα και πότε χρειάζεται να αναζητήσει καινούργια.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Happy coding! ☕&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Χρήσιμες πηγές
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ZiggyCreatures/FusionCache" rel="noopener noreferrer"&gt;FusionCache – GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ZiggyCreatures/FusionCache/tree/main/docs" rel="noopener noreferrer"&gt;FusionCache – Official Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/caching" rel="noopener noreferrer"&gt;Microsoft – Caching in .NET&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Ασφαλές deploy στο Azure App Service με Deployment Slots βήμα βήμα</title>
      <dc:creator>nikosst</dc:creator>
      <pubDate>Sun, 01 Nov 2026 09:11:17 +0000</pubDate>
      <link>https://dev.to/__b63657/asphales-deploy-sto-azure-app-service-me-deployment-slots-bema-bema-322o</link>
      <guid>https://dev.to/__b63657/asphales-deploy-sto-azure-app-service-me-deployment-slots-bema-bema-322o</guid>
      <description>&lt;p&gt;Πρακτικός οδηγός για staging slots, σωστά settings, warm-up, ελέγχους, swap και rollback, με εξήγηση του γιατί σε κάθε βήμα.&lt;/p&gt;




&lt;p&gt;Ένα deployment μπορεί να ολοκληρωθεί επιτυχώς και η εφαρμογή να αποτυγχάνει στο πρώτο πραγματικό αίτημα.&lt;/p&gt;

&lt;p&gt;Το build πέρασε. Τα αρχεία ανέβηκαν. Το pipeline έγινε πράσινο. Όμως μια σύνδεση με τη βάση, ένα ξεχασμένο secret ή ένα σφάλμα κατά την εκκίνηση αρκεί για να μετατρέψει την επόμενη επίσκεψη ενός χρήστη σε HTTP 500.&lt;/p&gt;

&lt;p&gt;Τα &lt;strong&gt;Deployment Slots του Azure App Service&lt;/strong&gt; μας επιτρέπουν να εγκαταστήσουμε και να ελέγξουμε τη νέα έκδοση πριν αναλάβει την εξυπηρέτηση των χρηστών. Έτσι, το deployment και η διάθεση της έκδοσης στο κοινό γίνονται δύο διακριτά βήματα.&lt;/p&gt;

&lt;p&gt;Σε αυτόν τον οδηγό θα ακολουθήσουμε μια συγκεκριμένη διαδρομή&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build → Deploy στο staging → Έλεγχοι → Swap → Παρακολούθηση
                                        ↓
                              Rollback, αν χρειαστεί
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Θα δούμε και τον λόγο ύπαρξης κάθε βήματος. Γιατί το ζητούμενο είναι να γνωρίζουμε με ποια στοιχεία αποφασίζουμε ότι μια έκδοση είναι έτοιμη για production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Τι είναι τα Deployment Slots
&lt;/h2&gt;

&lt;p&gt;Ένα deployment slot είναι μια ενεργή εφαρμογή μέσα στο ίδιο App Service, με δικό της hostname. Συνήθως κρατάμε δύο slots&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Production&lt;/strong&gt;, που εξυπηρετεί τους χρήστες.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Staging&lt;/strong&gt;, όπου προετοιμάζουμε την επόμενη έκδοση.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Τα deployment slots διατίθενται στα tiers &lt;strong&gt;Standard, Premium και Isolated&lt;/strong&gt;. Το διαθέσιμο πλήθος εξαρτάται από το plan. &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/deploy-staging-slots" rel="noopener noreferrer"&gt;Microsoft Learn για τα deployment slots&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ας υποθέσουμε ότι έχουμε την έκδοση &lt;code&gt;v1&lt;/code&gt; σε production και θέλουμε να διαθέσουμε τη &lt;code&gt;v2&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Στάδιο&lt;/th&gt;
&lt;th&gt;Production&lt;/th&gt;
&lt;th&gt;Staging&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Πριν από την αλλαγή&lt;/td&gt;
&lt;td&gt;v1&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Μετά το deployment&lt;/td&gt;
&lt;td&gt;v1&lt;/td&gt;
&lt;td&gt;v2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Μετά τους ελέγχους&lt;/td&gt;
&lt;td&gt;v1&lt;/td&gt;
&lt;td&gt;v2, ελεγμένη&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Μετά το swap&lt;/td&gt;
&lt;td&gt;v2&lt;/td&gt;
&lt;td&gt;v1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Αν κάνουμε rollback με νέο swap&lt;/td&gt;
&lt;td&gt;v1&lt;/td&gt;
&lt;td&gt;v2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Αυτή είναι μια πρακτική εφαρμογή της λογικής &lt;strong&gt;blue-green deployment&lt;/strong&gt;: προετοιμάζουμε μια δεύτερη έκδοση και αλλάζουμε ποια εξυπηρετεί το production. Η Microsoft προτείνει deployment σε nonproduction slot, ελέγχους και προώθηση μέσω swap. &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/deploy-best-practices" rel="noopener noreferrer"&gt;Deployment best practices&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Πώς βοηθούν στην ασφάλεια του release
&lt;/h3&gt;

&lt;p&gt;Σκεφτείτε μια εφαρμογή παραγγελιών. Η νέα έκδοση λειτουργεί τοπικά, αλλά στο Azure δεν μπορεί να διαβάσει ένα απαραίτητο secret.&lt;/p&gt;

&lt;p&gt;Με έλεγχο στο staging, έχουμε τη δυνατότητα να εντοπίσουμε το πρόβλημα πριν προωθήσουμε την έκδοση. Η απόφαση για release περιμένει μέχρι να έχουμε λειτουργική εφαρμογή και επιτυχημένους ελέγχους.&lt;/p&gt;

&lt;p&gt;Αν εμφανιστεί πρόβλημα μετά την προώθηση, η προηγούμενη έκδοση παραμένει διαθέσιμη στο άλλο slot. Αυτό μας δίνει μια συγκεκριμένη επιλογή επαναφοράς, αντί να αρχίζουμε εκείνη τη στιγμή να αναζητούμε παλιό build.&lt;/p&gt;

&lt;h3&gt;
  
  
  Οι πόροι του plan παραμένουν κοινόχρηστοι
&lt;/h3&gt;

&lt;p&gt;Τα slots χρησιμοποιούν τους πόρους του ίδιου App Service plan. Ένα βαρύ load test στο staging μπορεί επομένως να επηρεάσει το production. Η δυνατότητα των slots δεν έχει ξεχωριστή χρέωση, αλλά εξακολουθούμε να πληρώνουμε το plan και τις συνδεδεμένες υπηρεσίες. &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/overview-hosting-plans" rel="noopener noreferrer"&gt;App Service plans&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Για το runbook που ακολουθεί χρησιμοποιούμε το staging για προετοιμασία release και σύντομους λειτουργικούς ελέγχους. Τα απαιτητικά performance tests χρειάζονται κατάλληλο περιβάλλον και χωρητικότητα.&lt;/p&gt;

&lt;h2&gt;
  
  
  Βήμα 1 Προετοιμάζουμε εφαρμογή και κριτήρια επιτυχίας
&lt;/h2&gt;

&lt;p&gt;Θα θεωρήσουμε ότι έχουμε ήδη:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ένα App Service που λειτουργεί, σε plan με υποστήριξη slots.&lt;/li&gt;
&lt;li&gt;Δικαιώματα δημιουργίας slot, αλλαγής ρυθμίσεων, deployment και swap.&lt;/li&gt;
&lt;li&gt;Ένα έτοιμο, ελεγμένο artifact της νέας έκδοσης, εδώ &lt;code&gt;release.zip&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Runtime στο App Service συμβατό με την εφαρμογή.&lt;/li&gt;
&lt;li&gt;Logs και μετρήσεις για να αξιολογήσουμε το release.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Το παράδειγμα αφορά &lt;strong&gt;App Service με deployment κώδικα μέσω ZIP&lt;/strong&gt;. Οι εντολές γράφονται για &lt;strong&gt;Azure Cloud Shell σε Bash&lt;/strong&gt;. Για custom containers αλλάζει το βήμα εγκατάστασης του artifact.&lt;/p&gt;

&lt;p&gt;Πριν ξεκινήσουμε, ορίζουμε τι πρέπει να αποδείξει ο έλεγχος. Για μια εφαρμογή παραγγελιών, για παράδειγμα:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Η εφαρμογή εκκινεί κανονικά.&lt;/li&gt;
&lt;li&gt;Μπορεί να επικοινωνήσει με τις απαραίτητες υπηρεσίες.&lt;/li&gt;
&lt;li&gt;Ένας χρήστης συνδέεται και βλέπει τις παραγγελίες του.&lt;/li&gt;
&lt;li&gt;Επιστρέφεται το αναμενόμενο release identifier.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Τα &lt;code&gt;/health/ready&lt;/code&gt; και &lt;code&gt;/version&lt;/code&gt; που χρησιμοποιούμε παρακάτω είναι endpoints &lt;strong&gt;που υλοποιούμε στην εφαρμογή μας&lt;/strong&gt;. Το πρώτο πρέπει να επιστρέφει &lt;code&gt;200&lt;/code&gt; όταν η εφαρμογή είναι έτοιμη και, για παράδειγμα, &lt;code&gt;503&lt;/code&gt; όταν αποτυγχάνει μια κρίσιμη εξάρτηση. Το δεύτερο επιστρέφει την έκδοση του build, όπως &lt;code&gt;{"version":"2.0.0"}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Το readiness endpoint κρατά τους ελέγχους σύντομους, χωρίς εγγραφές ή άλλες παρενέργειες. Η απόκρισή του δεν πρέπει να αποκαλύπτει secrets ή εσωτερικά διαγνωστικά στοιχεία.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Γιατί έχει σημασία:&lt;/strong&gt; με συγκεκριμένα κριτήρια, το «μου άνοιξε η αρχική σελίδα» παύει να είναι ο μοναδικός έλεγχος πριν από το release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Βήμα 2 Δημιουργούμε το staging slot
&lt;/h2&gt;

&lt;p&gt;Από το Azure Portal ανοίγουμε το App Service και επιλέγουμε &lt;strong&gt;Deployment → Deployment slots → Add&lt;/strong&gt;. Δίνουμε όνομα &lt;code&gt;staging&lt;/code&gt; και επιλέγουμε αντιγραφή configuration από το production.&lt;/p&gt;

&lt;p&gt;Η αντιγραφή configuration δεν αντιγράφει τον κώδικα της εφαρμογής. Επίσης, ένα private endpoint δεν αντιγράφεται αυτόματα στο νέο slot. &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/deploy-staging-slots#add-a-slot" rel="noopener noreferrer"&gt;Δημιουργία deployment slot&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Η αντίστοιχη διαδικασία με Azure CLI είναι:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Οι αποτυχίες σε pipelines εντολών πρέπει να είναι ορατές.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; pipefail

&lt;span class="c"&gt;# Αντικαθιστούμε τις τιμές με τα δικά μας στοιχεία.&lt;/span&gt;
az account &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--subscription&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;SUBSCRIPTION_ID&amp;gt;"&lt;/span&gt;

&lt;span class="nv"&gt;RG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"rg-myapp-prod"&lt;/span&gt;
&lt;span class="nv"&gt;APP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-existing-app"&lt;/span&gt;
&lt;span class="nv"&gt;SLOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"staging"&lt;/span&gt;

az webapp deployment slot create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--slot&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SLOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--configuration-source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; none
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Στο &lt;code&gt;--configuration-source&lt;/code&gt;, το όνομα της εφαρμογής αναφέρεται στο production. &lt;a href="https://learn.microsoft.com/en-us/cli/azure/webapp/deployment/slot" rel="noopener noreferrer"&gt;Azure CLI για deployment slots&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Γιατί έχει σημασία:&lt;/strong&gt; χρησιμοποιούμε το production configuration ως αφετηρία. Στο επόμενο βήμα προσαρμόζουμε όσα πρέπει να διαφέρουν, πριν εγκαταστήσουμε κώδικα που θα μπορούσε να χρησιμοποιήσει λάθος υπηρεσίες.&lt;/p&gt;

&lt;h2&gt;
  
  
  Βήμα 3 Ξεχωρίζουμε σωστά τις ρυθμίσεις κάθε περιβάλλοντος
&lt;/h2&gt;

&lt;p&gt;Αυτό το βήμα χρειάζεται ιδιαίτερη προσοχή.&lt;/p&gt;

&lt;p&gt;Ένας έλεγχος στο staging μπορεί να στείλει πραγματικό email ή να γράψει στην production βάση, αν η εφαρμογή χρησιμοποιεί τις αντίστοιχες συνδέσεις.&lt;/p&gt;

&lt;p&gt;Για κάθε ρύθμιση αποφασίζουμε αν πρέπει να παραμένει συνδεδεμένη με το περιβάλλον. Στο Portal, μέσα στο αντίστοιχο slot, πηγαίνουμε στις &lt;strong&gt;Settings → Environment variables&lt;/strong&gt; και ενεργοποιούμε &lt;strong&gt;Deployment slot setting&lt;/strong&gt; όπου χρειάζεται. Τα slot-specific settings παραμένουν στο slot μετά την ολοκλήρωση του swap. &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/configure-common" rel="noopener noreferrer"&gt;Ρυθμίσεις εφαρμογής&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ένα ενδεικτικό πλάνο είναι:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ρύθμιση&lt;/th&gt;
&lt;th&gt;Production&lt;/th&gt;
&lt;th&gt;Staging&lt;/th&gt;
&lt;th&gt;Deployment slot setting&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Σύνδεση βάσης&lt;/td&gt;
&lt;td&gt;Production database&lt;/td&gt;
&lt;td&gt;Test database&lt;/td&gt;
&lt;td&gt;Ναι&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payment provider&lt;/td&gt;
&lt;td&gt;Live λογαριασμός&lt;/td&gt;
&lt;td&gt;Sandbox λογαριασμός&lt;/td&gt;
&lt;td&gt;Ναι&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email provider&lt;/td&gt;
&lt;td&gt;Κανονική αποστολή&lt;/td&gt;
&lt;td&gt;Test inbox&lt;/td&gt;
&lt;td&gt;Ναι&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API εξωτερικής υπηρεσίας&lt;/td&gt;
&lt;td&gt;Production endpoint&lt;/td&gt;
&lt;td&gt;Test endpoint&lt;/td&gt;
&lt;td&gt;Ναι&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ελέγχουμε τις τιμές και τη σήμανση και στα δύο slots.&lt;/p&gt;

&lt;p&gt;Για secrets χρησιμοποιούμε, όπου ταιριάζει, &lt;strong&gt;Azure Key Vault references&lt;/strong&gt;, με διαφορετικά secrets ανά περιβάλλον και slot-specific ρυθμίσεις. Έτσι τα credentials διαχειρίζονται κεντρικά και δεν χρειάζεται να μπουν στο repository. Ελέγχουμε τόσο τα δικαιώματα ανάγνωσης όσο και τη δικτυακή πρόσβαση στο vault. &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/app-service-key-vault-references" rel="noopener noreferrer"&gt;Key Vault references&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Τα &lt;strong&gt;managed identities είναι ρυθμίσεις ανά slot&lt;/strong&gt;. Ελέγχουμε ξεχωριστά τι μπορεί να προσπελάσει κάθε identity. &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/overview-managed-identity" rel="noopener noreferrer"&gt;Managed identities στο App Service&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Γιατί έχει σημασία:&lt;/strong&gt; θέλουμε να προωθήσουμε μια έκδοση της εφαρμογής, διατηρώντας τις σωστές συνδέσεις για το περιβάλλον που θα την εκτελεί.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Οι αλλαγές στα app settings προκαλούν επανεκκίνηση της εφαρμογής. Προγραμματίζουμε την αρχική ρύθμιση του production ως αλλαγή υποδομής και την ολοκληρώνουμε πριν από τα τακτικά releases. &lt;a href="https://learn.microsoft.com/en-us/cli/azure/webapp/config" rel="noopener noreferrer"&gt;Azure CLI configuration&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Βήμα 4 Προστατεύουμε την πρόσβαση στο staging
&lt;/h2&gt;

&lt;p&gt;Το staging έχει δικό του hostname. Το ότι δεν του στέλνουμε production traffic δεν σημαίνει ότι το περιεχόμενό του είναι ιδιωτικό.&lt;/p&gt;

&lt;p&gt;Ρυθμίζουμε πρόσβαση για την ομάδα και τον deployment runner μέσω των κατάλληλων access restrictions ή δικτυακών ελέγχων. Ελέγχουμε και το &lt;strong&gt;SCM/Kudu endpoint&lt;/strong&gt;, που έχει δικούς του κανόνες πρόσβασης. &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/overview-access-restrictions" rel="noopener noreferrer"&gt;App Service access restrictions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Γιατί έχει σημασία:&lt;/strong&gt; μια ανέκδοτη λειτουργία μπορεί να είναι προσβάσιμη απευθείας από το hostname του staging. Οι κανόνες πρόσβασης καθορίζουν ποιος μπορεί να τη δει και ποιος μπορεί να εκτελέσει τους ελέγχους μας.&lt;/p&gt;

&lt;p&gt;Στο παράδειγμα, τα επόμενα HTTP requests εκτελούνται από επιτρεπόμενο δίκτυο. Αν έχουμε περιορίσει την πρόσβαση σε VNet, χρησιμοποιούμε runner μέσα σε αυτό το δίκτυο.&lt;/p&gt;

&lt;h2&gt;
  
  
  Βήμα 5 Ρυθμίζουμε ουσιαστικό warm-up
&lt;/h2&gt;

&lt;p&gt;Το warm-up δίνει στη νέα έκδοση χρόνο να προετοιμαστεί πριν αναλάβει production traffic.&lt;/p&gt;

&lt;p&gt;Ορίζουμε το endpoint που θα ελέγχεται κατά το swap και απαιτούμε HTTP &lt;code&gt;200&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Αρχική ρύθμιση production. Μπορεί να προκαλέσει restart.&lt;/span&gt;
az webapp config appsettings &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--settings&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;WEBSITE_SWAP_WARMUP_PING_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/health/ready &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;WEBSITE_SWAP_WARMUP_PING_STATUSES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;200 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; none

&lt;span class="c"&gt;# Ίδια πολιτική στο staging.&lt;/span&gt;
az webapp config appsettings &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--slot&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SLOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--settings&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;WEBSITE_SWAP_WARMUP_PING_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/health/ready &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;WEBSITE_SWAP_WARMUP_PING_STATUSES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;200 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; none
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Χωρίς ρητή ρύθμιση των αποδεκτών statuses, το swap warm-up θεωρεί έγκυρους όλους τους HTTP κωδικούς. Επομένως, ακόμη και μια απόκριση σφάλματος μπορεί να θεωρηθεί επαρκής. &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/reference-app-settings#deployment-slots" rel="noopener noreferrer"&gt;App settings για deployment slots&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Το endpoint πρέπει να υπάρχει και στην προηγούμενη και στη νέα έκδοση, ώστε να μπορεί να χρησιμοποιηθεί και κατά την επαναφορά. Ελέγχουμε επίσης ότι τα probes φτάνουν στην εφαρμογή χωρίς redirect σε σελίδα σύνδεσης.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Γιατί έχει σημασία:&lt;/strong&gt; ο έλεγχος αποδεικνύει όσα έχουμε υλοποιήσει σε αυτόν. Μια στατική απόκριση &lt;code&gt;200&lt;/code&gt; δεν ελέγχει αν λειτουργεί η σύνδεση με τη βάση.&lt;/p&gt;

&lt;h3&gt;
  
  
  Το App Service Health check λειτουργεί συμπληρωματικά
&lt;/h3&gt;

&lt;p&gt;Στο Portal, από &lt;strong&gt;Monitoring → Health check&lt;/strong&gt;, μπορούμε να ορίσουμε το ίδιο readiness path για συνεχή παρακολούθηση των instances. Το Health check βοηθά στη δρομολόγηση αιτημάτων μακριά από unhealthy instances και είναι διαφορετικός μηχανισμός από το swap warm-up.&lt;/p&gt;

&lt;p&gt;Κρατάμε συνεπή ρύθμιση στα δύο slots: το Health check configuration ανταλλάσσεται στο swap. Για να αξιοποιείται πλήρως η δυνατότητα χρειάζονται τουλάχιστον δύο ενεργά replicas της εφαρμογής και επαρκής χωρητικότητα. &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/monitor-instances-health-check" rel="noopener noreferrer"&gt;App Service Health check&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Βήμα 6 Κάνουμε deploy τη νέα έκδοση στο staging
&lt;/h2&gt;

&lt;p&gt;Το &lt;code&gt;release.zip&lt;/code&gt; περιέχει την έτοιμη εφαρμογή στη ρίζα του, χωρίς έναν επιπλέον εξωτερικό φάκελο. Για .NET, για παράδειγμα, πακετάρουμε τα περιεχόμενα του αποτελέσματος του &lt;code&gt;dotnet publish&lt;/code&gt;. &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/deploy-zip" rel="noopener noreferrer"&gt;Δημιουργία ZIP package&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Από τον φάκελο όπου βρίσκεται το artifact εκτελούμε:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az webapp deploy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--slot&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SLOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--src-path&lt;/span&gt; ./release.zip &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--type&lt;/span&gt; zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το &lt;code&gt;--slot "$SLOT"&lt;/code&gt; καθορίζει τον προορισμό του deployment. &lt;a href="https://learn.microsoft.com/en-us/cli/azure/webapp#az-webapp-deploy" rel="noopener noreferrer"&gt;Azure CLI webapp deploy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Γιατί έχει σημασία:&lt;/strong&gt; εγκαθιστούμε τη νέα έκδοση στον χώρο προετοιμασίας. Η απόφαση να την προωθήσουμε στο production ακολουθεί τους ελέγχους.&lt;/p&gt;

&lt;p&gt;Στο pipeline κρατάμε το artifact συνδεδεμένο με συγκεκριμένο commit ή release identifier. Η έκδοση που ελέγχουμε είναι αυτή που θα προωθήσουμε, χωρίς ενδιάμεσο rebuild.&lt;/p&gt;

&lt;h2&gt;
  
  
  Βήμα 7 Ελέγχουμε την εφαρμογή στο staging
&lt;/h2&gt;

&lt;p&gt;Ανακτούμε τα πραγματικά hostnames από το Azure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;STAGING_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az webapp show &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--slot&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SLOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; defaultHostName &lt;span class="nt"&gt;--output&lt;/span&gt; tsv&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;PROD_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az webapp show &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; defaultHostName &lt;span class="nt"&gt;--output&lt;/span&gt; tsv&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Έπειτα ελέγχουμε readiness και έκδοση:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--fail-with-body&lt;/span&gt; &lt;span class="nt"&gt;--silent&lt;/span&gt; &lt;span class="nt"&gt;--show-error&lt;/span&gt; &lt;span class="nt"&gt;--max-time&lt;/span&gt; 20 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--write-out&lt;/span&gt; &lt;span class="s1"&gt;'\nHTTP %{http_code}\n'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="nv"&gt;$STAGING_HOST&lt;/span&gt;&lt;span class="s2"&gt;/health/ready"&lt;/span&gt;

curl &lt;span class="nt"&gt;--fail-with-body&lt;/span&gt; &lt;span class="nt"&gt;--silent&lt;/span&gt; &lt;span class="nt"&gt;--show-error&lt;/span&gt; &lt;span class="nt"&gt;--max-time&lt;/span&gt; 20 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="nv"&gt;$STAGING_HOST&lt;/span&gt;&lt;span class="s2"&gt;/version"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'.version == "2.0.0"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Για το readiness περιμένουμε ακριβώς &lt;code&gt;HTTP 200&lt;/code&gt;. Στον δεύτερο έλεγχο αντικαθιστούμε το &lt;code&gt;2.0.0&lt;/code&gt; με την αναμενόμενη έκδοση του artifact. Αν τα endpoints απαιτούν application authentication, τα requests χρειάζονται τα κατάλληλα credentials.&lt;/p&gt;

&lt;p&gt;Ακολουθούν τα &lt;strong&gt;smoke tests&lt;/strong&gt;: σύντομοι έλεγχοι βασικών λειτουργιών. Στο παράδειγμά μας δοκιμάζουμε σύνδεση χρήστη, ανάγνωση παραγγελιών και μια ελεγχόμενη δοκιμαστική παραγγελία στο test περιβάλλον.&lt;/p&gt;

&lt;p&gt;Ελέγχουμε παράλληλα logs και exceptions. Ένας λειτουργικός έλεγχος μπορεί να φαίνεται επιτυχημένος, ενώ μια δευτερεύουσα διεργασία αποτυγχάνει στο παρασκήνιο.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Γιατί έχει σημασία:&lt;/strong&gt; ελέγχουμε συμπεριφορά, πρόσβαση στις εξαρτήσεις και ταυτότητα της έκδοσης. Ένα επιτυχημένο upload αποδεικνύει ότι μεταφέρθηκε το artifact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Βήμα 8 Κάνουμε τελικό έλεγχο και swap
&lt;/h2&gt;

&lt;p&gt;Πριν προχωρήσουμε, σταματάμε παράλληλα deployments ή άλλες αλλαγές στο ίδιο staging slot. Θέλουμε να προωθήσουμε ακριβώς την έκδοση που μόλις ελέγξαμε.&lt;/p&gt;

&lt;h3&gt;
  
  
  Προαιρετικός έλεγχος με swap with preview
&lt;/h3&gt;

&lt;p&gt;Το preview εφαρμόζει τις σχετικές ρυθμίσεις του target στο source slot και παύει τη διαδικασία πριν από την αλλαγή δρομολόγησης. Έτσι μπορούμε να ελέγξουμε τη νέα έκδοση με τις ρυθμίσεις του production. &lt;strong&gt;Δεν υποστηρίζεται όταν είναι ενεργό το App Service Authentication σε οποιοδήποτε από τα δύο slots.&lt;/strong&gt; &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/deploy-staging-slots#swap-with-preview-multiple-phase-swap" rel="noopener noreferrer"&gt;Swap with preview&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az webapp deployment slot swap &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--slot&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SLOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target-slot&lt;/span&gt; production &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--action&lt;/span&gt; preview
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Εδώ το staging μπορεί να χρησιμοποιεί &lt;strong&gt;production connections&lt;/strong&gt;. Περιορίζουμε τους ελέγχους σε ελεγχόμενες ενέργειες χωρίς παρενέργειες και έχουμε ήδη αντιμετωπίσει migrations κατά την εκκίνηση και background workers.&lt;/p&gt;

&lt;p&gt;Η identity και η VNet integration παραμένουν του source slot. Συνεπώς ελέγχουμε την πρόσβασή του στις production εξαρτήσεις κατά την προετοιμασία, ακόμη και για swap χωρίς preview. &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/deploy-staging-slots#which-settings-are-swapped" rel="noopener noreferrer"&gt;Ρυθμίσεις που δεν ανταλλάσσονται&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Αν αποφασίσουμε να ακυρώσουμε το εκκρεμές preview:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az webapp deployment slot swap &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--slot&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SLOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target-slot&lt;/span&gt; production &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--action&lt;/span&gt; reset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Προώθηση στο production
&lt;/h3&gt;

&lt;p&gt;Όταν οι έλεγχοι έχουν ολοκληρωθεί, εκτελούμε:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az webapp deployment slot swap &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--slot&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SLOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target-slot&lt;/span&gt; production &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--action&lt;/span&gt; swap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η εντολή εκτελεί κανονικό swap ή ολοκληρώνει ένα επιτυχημένο preview. Επιλέγουμε &lt;strong&gt;source το staging και target το production&lt;/strong&gt;. &lt;a href="https://learn.microsoft.com/en-us/cli/azure/webapp/deployment/slot#az-webapp-deployment-slot-swap" rel="noopener noreferrer"&gt;Azure CLI slot swap&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Το App Service προετοιμάζει τη νέα έκδοση πριν αλλάξει τη δρομολόγηση. Αυτό περιορίζει τη διακοπή που προκαλεί ένα release. Η συνολική διαθεσιμότητα εξακολουθεί να εξαρτάται από την εφαρμογή, τις εξαρτήσεις και τον χειρισμό επανεκκινήσεων. &lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/service-guides/app-service-web-apps" rel="noopener noreferrer"&gt;Αρχιτεκτονικές πρακτικές για το App Service&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/azure/reliability/reliability-app-service" rel="noopener noreferrer"&gt;οδηγός αξιοπιστίας&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Γιατί έχει σημασία:&lt;/strong&gt; η προώθηση γίνεται ως συγκεκριμένη αλλαγή, αφού έχουμε συγκεντρώσει στοιχεία ότι η έκδοση είναι έτοιμη.&lt;/p&gt;

&lt;h2&gt;
  
  
  Βήμα 9 Επαληθεύουμε το production και παρακολουθούμε
&lt;/h2&gt;

&lt;p&gt;Μετά το swap επαναλαμβάνουμε τους ελέγχους στο production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--fail-with-body&lt;/span&gt; &lt;span class="nt"&gt;--silent&lt;/span&gt; &lt;span class="nt"&gt;--show-error&lt;/span&gt; &lt;span class="nt"&gt;--max-time&lt;/span&gt; 20 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--write-out&lt;/span&gt; &lt;span class="s1"&gt;'\nHTTP %{http_code}\n'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="nv"&gt;$PROD_HOST&lt;/span&gt;&lt;span class="s2"&gt;/health/ready"&lt;/span&gt;

curl &lt;span class="nt"&gt;--fail-with-body&lt;/span&gt; &lt;span class="nt"&gt;--silent&lt;/span&gt; &lt;span class="nt"&gt;--show-error&lt;/span&gt; &lt;span class="nt"&gt;--max-time&lt;/span&gt; 20 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="nv"&gt;$PROD_HOST&lt;/span&gt;&lt;span class="s2"&gt;/version"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'.version == "2.0.0"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αν η εφαρμογή εξυπηρετείται μέσω custom domain, Front Door ή άλλου proxy, ελέγχουμε και το πραγματικό δημόσιο URL. Εκεί επαληθεύουμε ολόκληρη τη διαδρομή που ακολουθούν οι χρήστες.&lt;/p&gt;

&lt;p&gt;Στο εργαλείο παρακολούθησης, όπως το Application Insights, συγκρίνουμε με την προηγούμενη κατάσταση:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Αποτυχημένα requests και νέες exceptions.&lt;/li&gt;
&lt;li&gt;Χρόνους απόκρισης, ιδιαίτερα p95.&lt;/li&gt;
&lt;li&gt;Αποτυχίες επικοινωνίας με εξαρτήσεις.&lt;/li&gt;
&lt;li&gt;CPU, μνήμη και επανεκκινήσεις.&lt;/li&gt;
&lt;li&gt;Επιτυχία βασικών επιχειρησιακών ενεργειών, όπως η ολοκλήρωση παραγγελίας.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Πριν από το release έχουμε ορίσει ποια επιδείνωση απαιτεί επαναφορά και ποιος παίρνει την απόφαση. Το διάστημα παρακολούθησης πρέπει να καλύπτει αντιπροσωπευτική κίνηση και τις σημαντικές διεργασίες της εφαρμογής. &lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/operational-excellence/safe-deployments" rel="noopener noreferrer"&gt;Safe deployment practices&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Γιατί έχει σημασία:&lt;/strong&gt; μια αστοχία μπορεί να εμφανίζεται μόνο σε συγκεκριμένους χρήστες ή δεδομένα. Η παρακολούθηση συνδέει το release με την πραγματική λειτουργία της υπηρεσίας.&lt;/p&gt;

&lt;h2&gt;
  
  
  Βήμα 10 Κάνουμε rollback όταν χρειάζεται
&lt;/h2&gt;

&lt;p&gt;Αν η προηγούμενη έκδοση παραμένει στο staging και είναι συμβατή με την τρέχουσα κατάσταση των δεδομένων, μπορούμε να ανταλλάξουμε ξανά τα ίδια slots:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az webapp deployment slot swap &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--slot&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SLOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target-slot&lt;/span&gt; production &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--action&lt;/span&gt; swap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ακόμη και για rollback, κρατάμε το production ως target. Η ανταλλαγή επαναφέρει την προηγούμενη έκδοση στο production. &lt;a href="https://learn.microsoft.com/en-us/cli/azure/webapp/deployment/slot#az-webapp-deployment-slot-swap" rel="noopener noreferrer"&gt;Azure CLI slot swap&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ελέγχουμε ξανά readiness, την προηγούμενη έκδοση στο &lt;code&gt;/version&lt;/code&gt; και τις βασικές λειτουργίες. Μέχρι να επιβεβαιωθεί το release, αποφεύγουμε νέο deployment στο staging που θα αντικαταστήσει την έκδοση επαναφοράς. &lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/service-guides/app-service-web-apps" rel="noopener noreferrer"&gt;Διατήρηση προηγούμενης έκδοσης για επαναφορά&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Γιατί έχει σημασία:&lt;/strong&gt; η δυνατότητα rollback είναι χρήσιμη όταν ξέρουμε τι επαναφέρουμε, πότε θα το κάνουμε και πώς θα επιβεβαιώσουμε το αποτέλεσμα.&lt;/p&gt;

&lt;h2&gt;
  
  
  Η βάση δεδομένων χρειάζεται δικό της σχέδιο
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Το slot swap δεν αναιρεί database migrations ή αλλαγές στα δεδομένα.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ας υποθέσουμε ότι η &lt;code&gt;v2&lt;/code&gt; καταργεί τη στήλη &lt;code&gt;FullName&lt;/code&gt; και χρησιμοποιεί τις &lt;code&gt;FirstName&lt;/code&gt; και &lt;code&gt;LastName&lt;/code&gt;. Αν επαναφέρουμε τη &lt;code&gt;v1&lt;/code&gt;, εκείνη θα συνεχίσει να ζητά τη στήλη που έχει ήδη διαγραφεί.&lt;/p&gt;

&lt;p&gt;Για τέτοιες αλλαγές σχεδιάζουμε σταδιακή μετάβαση, γνωστή ως &lt;strong&gt;expand and contract&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Προσθέτουμε τα νέα πεδία, διατηρώντας το παλιό schema λειτουργικό.&lt;/li&gt;
&lt;li&gt;Διαθέτουμε έκδοση που υποστηρίζει τη μεταβατική κατάσταση και κρατά τα δεδομένα συμβατά με την προηγούμενη έκδοση για το διάστημα rollback.&lt;/li&gt;
&lt;li&gt;Μεταφέρουμε τα υπάρχοντα δεδομένα και ελέγχουμε τη συνέπειά τους.&lt;/li&gt;
&lt;li&gt;Αφαιρούμε το παλιό πεδίο σε επόμενο release, όταν δεν απαιτείται πλέον η παλιά έκδοση.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Τα migrations εκτελούνται ως ελεγχόμενο βήμα, με δικό τους σχέδιο αποτυχίας. Αποφεύγουμε ανεξέλεγκτες καταστροφικές αλλαγές κατά την εκκίνηση, ιδιαίτερα όταν το preview μπορεί να συνδέσει τη νέα έκδοση με production δεδομένα.&lt;/p&gt;

&lt;p&gt;Η επαναφορά αλλαγών σε schema και άλλα stateful στοιχεία απαιτεί ιδιαίτερη πρόβλεψη. &lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/operational-excellence/safe-deployments" rel="noopener noreferrer"&gt;Well-Architected guidance για ασφαλή deployments&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background jobs και κατάσταση εφαρμογής
&lt;/h2&gt;

&lt;p&gt;Δύο ενεργές εκδόσεις μπορούν να εκτελούν δύο φορές μια εργασία, αν δεν έχουμε σχεδιάσει τον συντονισμό τους. Για queue consumers, schedulers και αποστολές email προβλέπουμε απομονωμένες δοκιμαστικές ουρές, συντονισμό εκτέλεσης και idempotency, δηλαδή ασφαλή χειρισμό επαναλαμβανόμενης επεξεργασίας.&lt;/p&gt;

&lt;p&gt;Για WebJobs υπάρχουν ειδικές ρυθμίσεις απενεργοποίησης στο staging. Ελέγχουμε όμως ολόκληρη τη συμπεριφορά κατά το swap και το preview: η προσωρινή εφαρμογή production settings χρειάζεται πρόβλεψη και για τυχόν flags που ενεργοποιούν εργασίες. &lt;a href="https://learn.microsoft.com/en-ca/azure/app-service/webjobs-create" rel="noopener noreferrer"&gt;WebJobs στο App Service&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Εξετάζουμε επίσης τι χρειάζεται να επιβιώσει από ένα restart: sessions, αρχεία χρηστών και εργασίες μεγάλης διάρκειας. Τα αποθηκεύουμε και τα διαχειριζόμαστε με τρόπο που επιτρέπει τη μετάβαση μεταξύ εκδόσεων.&lt;/p&gt;

&lt;h2&gt;
  
  
  Πώς μεταφέρουμε τη διαδικασία σε pipeline
&lt;/h2&gt;

&lt;p&gt;Αφού κατανοήσουμε τη ροή, την αυτοματοποιούμε:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build και tests
    ↓
Αποθήκευση artifact με συγκεκριμένη έκδοση
    ↓
Deploy στο staging
    ↓
Readiness και smoke tests
    ↓
Έγκριση ή αυτόματα κριτήρια προώθησης
    ↓
Swap στο production
    ↓
Επαλήθευση και παρακολούθηση
    ↓
Ολοκλήρωση release ή ελεγχόμενο rollback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Κάθε αποτυχία πρέπει να σταματά την προώθηση. Επίσης, επιτρέπουμε ένα release κάθε φορά ανά εφαρμογή, ώστε ένα δεύτερο pipeline να μη μεταβάλει το slot που ελέγχει το πρώτο.&lt;/p&gt;

&lt;p&gt;Η αυτοματοποίηση αποκτά αξία όταν επαναλαμβάνει ελέγχους με σαφή κριτήρια. Καταγράφουμε commit, artifact, αποτελέσματα ελέγχων και χρόνο swap, ώστε να γνωρίζουμε τι εκτελείται και γιατί προωθήθηκε.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist πριν από το release
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Το staging εκτελεί το αναμενόμενο artifact.&lt;/li&gt;
&lt;li&gt;[ ] Οι συνδέσεις, τα secrets και τα slot-specific settings είναι σωστά.&lt;/li&gt;
&lt;li&gt;[ ] Έχουν ελεγχθεί δικαιώματα identity και δικτυακή πρόσβαση.&lt;/li&gt;
&lt;li&gt;[ ] Το readiness endpoint ελέγχει κρίσιμες εξαρτήσεις και το warm-up απαιτεί &lt;code&gt;200&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] Τα smoke tests έχουν περάσει.&lt;/li&gt;
&lt;li&gt;[ ] Έχουμε αντιμετωπίσει migrations και background jobs.&lt;/li&gt;
&lt;li&gt;[ ] Δεν εκτελείται άλλο deployment στο ίδιο slot.&lt;/li&gt;
&lt;li&gt;[ ] Έχουμε ορίσει κριτήρια rollback και κρατάμε την προηγούμενη έκδοση διαθέσιμη.&lt;/li&gt;
&lt;li&gt;[ ] Μετά το swap θα ελέγξουμε το πραγματικό δημόσιο URL και τη συμπεριφορά των χρηστών.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Τα deployment slots μάς δίνουν χρόνο για έλεγχο πριν από την προώθηση και μια πρακτική διαδρομή επιστροφής μετά από αυτήν. Η σημασία τους φαίνεται τη στιγμή που ένα release παρουσιάζει πρόβλημα: έχουμε ήδη προετοιμάσει τις επόμενες κινήσεις μας.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>devops</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Decorator pattern σε CQRS handlers με authorization και telemetry στο .NET</title>
      <dc:creator>nikosst</dc:creator>
      <pubDate>Sun, 01 Nov 2026 08:48:21 +0000</pubDate>
      <link>https://dev.to/__b63657/decorator-pattern-se-cqrs-handlers-me-authorization-kai-telemetry-sto-net-7do</link>
      <guid>https://dev.to/__b63657/decorator-pattern-se-cqrs-handlers-me-authorization-kai-telemetry-sto-net-7do</guid>
      <description>&lt;p&gt;Πρόσθεσε authorization και telemetry γύρω από CQRS handlers στο .NET, με Dependency Injection, OpenTelemetry και tests, χωρίς MediatR.&lt;/p&gt;




&lt;p&gt;Ένας handler εκτελεί ένα use case: διαβάζει δεδομένα, υπολογίζει ένα αποτέλεσμα ή καταχωρίζει μια αλλαγή. Συχνά όμως θέλουμε να ελέγξουμε δικαιώματα πριν από την εκτέλεσή του και να μετρήσουμε τη διάρκεια ολόκληρης της κλήσης.&lt;/p&gt;

&lt;p&gt;Με το Decorator pattern μπορούμε να προσθέσουμε αυτές τις συμπεριφορές γύρω από τον handler, κρατώντας το ίδιο interface. Κάθε decorator κρατά έναν εσωτερικό handler και αποφασίζει τι θα κάνει πριν, μετά ή γύρω από την κλήση του.&lt;/p&gt;

&lt;p&gt;Σε αυτό το άρθρο θα γράψουμε authorization και telemetry decorators για CQRS handlers στο .NET, θα τους συνδέσουμε με Dependency Injection και θα ελέγξουμε ότι η αλυσίδα λειτουργεί. Θα καλέσουμε τους handlers απευθείας, χωρίς MediatR.&lt;/p&gt;

&lt;p&gt;Για να ακολουθήσεις, χρειάζεται βασική εξοικείωση με interfaces, generics, &lt;code&gt;async/await&lt;/code&gt; και το DI του ASP.NET Core. Τα snippets προορίζονται για ASP.NET Core project με .NET 10 και ενεργοποιημένα implicit usings. Βάλε τους τύπους σε ξεχωριστά αρχεία· οι προσθήκες στο &lt;code&gt;Program.cs&lt;/code&gt; επισημαίνονται στα αντίστοιχα σημεία.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ένα use case με authorization και telemetry
&lt;/h2&gt;

&lt;p&gt;Ας φανταστούμε ένα εσωτερικό εργαλείο μιας πιτσαρίας. Ένας υπάλληλος ζητά την τιμή μιας πίτσας με συγκεκριμένα υλικά.&lt;/p&gt;

&lt;p&gt;Η κύρια δουλειά είναι να υπολογίσουμε την τιμή. Θέλουμε επίσης:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Να ελέγξουμε αν ο υπάλληλος έχει δικαίωμα να χρησιμοποιήσει τη λειτουργία.&lt;/li&gt;
&lt;li&gt;Να μετρήσουμε τη διάρκεια και να καταγράψουμε αν η εκτέλεση ολοκληρώθηκε ή απέτυχε.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Μπορούμε να βάλουμε όλον αυτόν τον κώδικα στην ίδια μέθοδο. Αν όμως οι ίδιοι έλεγχοι και οι ίδιες μετρήσεις χρειάζονται σε πολλές λειτουργίες, θα αρχίσουμε να τους επαναλαμβάνουμε.&lt;/p&gt;

&lt;p&gt;Με decorators μπορούμε να χωρίσουμε τη δουλειά:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telemetry decorator
└── Authorization decorator
    └── Handler που υπολογίζει την τιμή
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Κάθε επίπεδο κρατά το επόμενο, προσφέρει το ίδιο συμβόλαιο κλήσης και προσθέτει τη δική του συμπεριφορά. Ο κώδικας που καλεί τον handler δεν χρειάζεται να γνωρίζει πόσα επίπεδα υπάρχουν.&lt;/p&gt;

&lt;h2&gt;
  
  
  Οι απαραίτητοι όροι
&lt;/h2&gt;

&lt;p&gt;Ένα query είναι ένα αίτημα για ανάγνωση ή υπολογισμό δεδομένων, χωρίς να αλλάζουμε την αποθηκευμένη κατάσταση της εφαρμογής. Για παράδειγμα: «υπολόγισε την τιμή αυτής της πίτσας».&lt;/p&gt;

&lt;p&gt;Ένα command είναι ένα αίτημα που ζητά αλλαγή. Για παράδειγμα: «καταχώρισε αυτή την παραγγελία».&lt;/p&gt;

&lt;p&gt;Ένας handler είναι η κλάση που εκτελεί το αντίστοιχο αίτημα.&lt;/p&gt;

&lt;p&gt;Ο διαχωρισμός των λειτουργιών ανάγνωσης από τις λειτουργίες αλλαγής είναι η βασική ιδέα του CQRS. Μπορείς να ξεκινήσεις με διαφορετικά requests και handlers στην ίδια εφαρμογή και στην ίδια βάση δεδομένων.&lt;/p&gt;

&lt;p&gt;Το CQRS δεν απαιτεί MediatR. Στο παράδειγμα θα καλέσουμε τους handlers απευθείας, μέσα από δικά μας interfaces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Το κοινό συμβόλαιο των handlers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IRequest&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IQuery&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IRequest&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ICommand&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IRequest&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IRequestHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;TRequest&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IRequest&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;TRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Τα &lt;code&gt;TRequest&lt;/code&gt; και &lt;code&gt;TResult&lt;/code&gt; είναι θέσεις για τύπους που θα ορίσουμε όταν χρησιμοποιήσουμε το interface.&lt;/p&gt;

&lt;p&gt;Για παράδειγμα, το &lt;code&gt;IRequestHandler&amp;lt;GetPizzaPriceQuery, PizzaPriceDto&amp;gt;&lt;/code&gt; σημαίνει: «handler που δέχεται ένα &lt;code&gt;GetPizzaPriceQuery&lt;/code&gt; και επιστρέφει ένα &lt;code&gt;PizzaPriceDto&lt;/code&gt; μέσα σε &lt;code&gt;Task&lt;/code&gt;».&lt;/p&gt;

&lt;p&gt;Η γραμμή &lt;code&gt;where TRequest : IRequest&amp;lt;TResult&amp;gt;&lt;/code&gt; περιορίζει ποιοι τύποι μπορούν να χρησιμοποιηθούν ως requests.&lt;/p&gt;

&lt;p&gt;Commands και queries έχουν διαφορετικά interfaces για να δηλώνουν την πρόθεσή τους, αλλά οι handlers τους μοιράζονται ένα κοινό συμβόλαιο. Έτσι, ο ίδιος generic decorator μπορεί να χρησιμοποιηθεί και για τα δύο.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;IRequestHandler&amp;lt;TRequest, TResult&amp;gt;&lt;/code&gt; είναι το κοινό interface που θα υλοποιούν τόσο ο πραγματικός handler όσο και οι decorators του.&lt;/p&gt;

&lt;h2&gt;
  
  
  Δηλώνουμε ποιο δικαίωμα απαιτείται
&lt;/h2&gt;

&lt;p&gt;Θα χρησιμοποιήσουμε ένα μικρό attribute, δηλαδή μια δήλωση που προσθέτει πληροφορία σε μια κλάση:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;AttributeUsage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AttributeTargets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Inherited&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RequiresPermissionAttribute&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Attribute&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Permission&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;RequiresPermissionAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"A permission is required."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;Permission&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το attribute δηλώνει την απαίτηση. Δεν εκτελεί μόνο του κανέναν έλεγχο. Ο authorization decorator θα διαβάσει αυτή την πληροφορία και θα την εφαρμόσει.&lt;/p&gt;

&lt;h2&gt;
  
  
  Το query και ο handler υπολογισμού της τιμής
&lt;/h2&gt;

&lt;p&gt;Το query περιγράφει τις επιλογές του υπαλλήλου. Το DTO είναι ένα απλό αντικείμενο με τα δεδομένα που θέλουμε να επιστρέψουμε.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;RequiresPermission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pizza.prices.read"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;ExtraCheese&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;Mushrooms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;Olives&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IQuery&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PizzaPriceDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;PizzaPriceDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ο handler παρακάτω υπολογίζει την τιμή στη μνήμη. Η υλοποίηση παραμένει απλή, ώστε να επικεντρωθούμε στους decorators γύρω από την εκτέλεσή του.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetPizzaPriceHandler&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IRequestHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PizzaPriceDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PizzaPriceDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;GetPizzaPriceQuery&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThrowIfCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Πίτσα με σάλτσα ντομάτας"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;6.00m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExtraCheese&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;", έξτρα τυρί"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="m"&gt;1.50m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mushrooms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;", μανιτάρια"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="m"&gt;1.00m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Olives&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;", ελιές"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="m"&gt;0.50m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PizzaPriceDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ο υπολογισμός γίνεται στη μνήμη, γι’ αυτό χρησιμοποιούμε &lt;code&gt;Task.FromResult&lt;/code&gt;. Διατηρούμε το ασύγχρονο συμβόλαιο ώστε ένας άλλος handler να μπορεί, για παράδειγμα, να κάνει &lt;code&gt;await&lt;/code&gt; σε μια κλήση προς τη βάση δεδομένων.&lt;/p&gt;

&lt;p&gt;Ο συγκεκριμένος handler ασχολείται με τις επιλογές και την τιμή. Δεν ελέγχει τον χρήστη και δεν περιέχει κώδικα μετρήσεων. Σε μια μεγαλύτερη εφαρμογή, οι κανόνες τιμολόγησης μπορούν να βρίσκονται σε ξεχωριστό μοντέλο του domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authorization πριν εκτελεστεί ο handler
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Οι πληροφορίες που χρειαζόμαστε για τον χρήστη
&lt;/h3&gt;

&lt;p&gt;Το authorization απαντά στην ερώτηση «επιτρέπεται αυτός ο χρήστης να εκτελέσει αυτή τη λειτουργία;».&lt;/p&gt;

&lt;p&gt;Ο decorator χρειάζεται να γνωρίζει αν ο χρήστης έχει συνδεθεί και αν έχει το απαραίτητο δικαίωμα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ICurrentUser&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;IsAuthenticated&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;HasPermission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το interface μάς επιτρέπει να πάρουμε αυτές τις πληροφορίες χωρίς να εξαρτάται ο decorator από το &lt;code&gt;HttpContext&lt;/code&gt; του ASP.NET Core.&lt;/p&gt;

&lt;p&gt;Θα χρησιμοποιήσουμε επίσης δύο δικά μας exceptions, ώστε να ξεχωρίζουμε τις διαφορετικές απορρίψεις:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthenticationRequiredException&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AuthenticationRequiredException&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authentication is required."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PermissionDeniedException&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;PermissionDeniedException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Missing permission: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ο authorization decorator
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Reflection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthorizationHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IRequestHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;TRequest&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IRequest&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;RequiresPermissionAttribute&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Requirement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TRequest&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;GetCustomAttribute&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RequiresPermissionAttribute&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IRequestHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_inner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ICurrentUser&lt;/span&gt; &lt;span class="n"&gt;_currentUser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AuthorizationHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;IRequestHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ICurrentUser&lt;/span&gt; &lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_inner&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_currentUser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;TRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThrowIfCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Requirement&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;$"Request &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TRequest&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; must declare a permission."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;_currentUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsAuthenticated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AuthenticationRequiredException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;_currentUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HasPermission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Requirement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permission&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PermissionDeniedException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Requirement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permission&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_inner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το &lt;code&gt;_inner&lt;/code&gt; είναι ο handler που βρίσκεται μέσα στον decorator. Επειδή υλοποιεί το ίδιο interface, μπορεί να είναι είτε ο πραγματικός handler είτε ένας άλλος decorator.&lt;/p&gt;

&lt;p&gt;Ο decorator πρώτα ελέγχει τις προϋποθέσεις. Αν όλα είναι εντάξει, καλεί τον εσωτερικό handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_inner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αν ο χρήστης δεν έχει δικαίωμα, πετά exception και ο εσωτερικός handler δεν εκτελείται. Ένας decorator μπορεί λοιπόν να σταματήσει σκόπιμα την κλήση, όταν αυτό είναι μέρος της ευθύνης του.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;GetCustomAttribute&lt;/code&gt; διαβάζει το attribute από τον τύπο του request. Το αποτέλεσμα αποθηκεύεται στο &lt;code&gt;static readonly&lt;/code&gt; πεδίο, οπότε δεν επαναλαμβάνουμε αυτή την αναζήτηση σε κάθε κλήση για τον ίδιο συνδυασμό generic τύπων.&lt;/p&gt;

&lt;p&gt;Στο παράδειγμά μας, κάθε request που περνά από αυτόν τον decorator πρέπει να δηλώνει &lt;code&gt;permission&lt;/code&gt;. Αν το ξεχάσουμε, η εκτέλεση σταματά με σφάλμα ρύθμισης. Για δημόσια requests θα χρειαζόταν μια ρητή πολιτική που να επιτρέπει την ανώνυμη πρόσβαση.&lt;/p&gt;

&lt;h3&gt;
  
  
  Από τον χρήστη του API στο ICurrentUser
&lt;/h3&gt;

&lt;p&gt;Σε ένα ASP.NET Core API μπορούμε να διαβάσουμε τα permissions από τα claims του χρήστη:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HttpCurrentUser&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICurrentUser&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IHttpContextAccessor&lt;/span&gt; &lt;span class="n"&gt;_httpContextAccessor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;HttpCurrentUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IHttpContextAccessor&lt;/span&gt; &lt;span class="n"&gt;httpContextAccessor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_httpContextAccessor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpContextAccessor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;IsAuthenticated&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;_httpContextAccessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Identity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;IsAuthenticated&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;HasPermission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;IsAuthenticated&lt;/span&gt;
            &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;_httpContextAccessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HasClaim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"permission"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Εδώ υποθέτουμε ότι το authentication της εφαρμογής έχει ήδη ρυθμιστεί και δημιουργεί ένα claim τύπου &lt;code&gt;permission&lt;/code&gt; για κάθε δικαίωμα. Αν η εφαρμογή σου χρησιμοποιεί άλλο μοντέλο δικαιωμάτων, αλλάζει αυτή η υλοποίηση του &lt;code&gt;ICurrentUser&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Το API πρέπει επίσης να μεταφράζει τα exceptions στις κατάλληλες HTTP απαντήσεις:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Exception&lt;/th&gt;
&lt;th&gt;HTTP απάντηση&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AuthenticationRequiredException&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;401 Unauthorized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PermissionDeniedException&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;403 Forbidden&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Αυτή η αντιστοίχιση γίνεται στο κεντρικό error handling του API. Τα exceptions από μόνα τους δεν αλλάζουν τον HTTP status code. Το ASP.NET Core παρέχει το &lt;code&gt;IExceptionHandler&lt;/code&gt; γι’ αυτή τη δουλειά, σε συνδυασμό με το exception handling middleware. Δες την &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?view=aspnetcore-10.0#iexceptionhandler" rel="noopener noreferrer"&gt;τεκμηρίωση για το error handling&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Οι συγκεκριμένοι έλεγχοι αφορούν ένα γενικό &lt;code&gt;permission&lt;/code&gt;. Μια λειτουργία που ανοίγει συγκεκριμένη παραγγελία μπορεί να χρειάζεται επιπλέον έλεγχο ιδιοκτησίας ή πρόσβασης στα δεδομένα της.&lt;/p&gt;

&lt;h2&gt;
  
  
  Telemetry γύρω από την εκτέλεση
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Τι θέλουμε να μετρήσουμε
&lt;/h3&gt;

&lt;p&gt;Με τον όρο telemetry εννοούμε εδώ τις πληροφορίες που συλλέγουμε για την εκτέλεση μιας λειτουργίας:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Πόσο χρόνο χρειάστηκε.&lt;/li&gt;
&lt;li&gt;Αν ολοκληρώθηκε, απέτυχε ή ακυρώθηκε.&lt;/li&gt;
&lt;li&gt;Πού βρίσκεται μέσα στη συνολική διαδρομή ενός request.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Θα χρησιμοποιήσουμε δύο APIs του .NET:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Το &lt;code&gt;ActivitySource&lt;/code&gt;, για να δημιουργούμε ένα span, δηλαδή μια καταγραφή της συγκεκριμένης εκτέλεσης μέσα σε ένα trace.&lt;/li&gt;
&lt;li&gt;Το &lt;code&gt;Meter&lt;/code&gt;, για να καταγράφουμε μετρήσεις διάρκειας. Το histogram είναι ο τύπος μέτρησης που χρησιμοποιούμε για τις διάρκειες.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Οι πηγές των μετρήσεων
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Diagnostics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Diagnostics.Metrics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CqrsDiagnostics&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;SourceName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"PizzaApp.Cqrs"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ActivitySource&lt;/span&gt; &lt;span class="n"&gt;Activities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SourceName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Meter&lt;/span&gt; &lt;span class="n"&gt;Meter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SourceName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Histogram&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;Meter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateHistogram&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"pizzaapp.cqrs.duration"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"ms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Duration of CQRS requests."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Τα κρατάμε για όλη τη διάρκεια της εφαρμογής, αντί να δημιουργούμε νέες πηγές σε κάθε request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ο telemetry decorator
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Diagnostics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TelemetryHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IRequestHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;TRequest&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IRequest&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IRequestHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_inner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;TelemetryHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IRequestHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_inner&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;TRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;requestName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TRequest&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;CqrsDiagnostics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Activities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stopwatch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Stopwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartNew&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_inner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OperationCanceledException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"cancelled"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"failure"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;SetStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ActivityStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;finally&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;SetTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cqrs.outcome"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;CqrsDiagnostics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;stopwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Elapsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalMilliseconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;KeyValuePair&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"cqrs.request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requestName&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;KeyValuePair&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"cqrs.outcome"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ας διαβάσουμε τη ροή με απλά λόγια:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ξεκινά η καταγραφή και το χρονόμετρο.&lt;/li&gt;
&lt;li&gt;Καλείται ο εσωτερικός handler.&lt;/li&gt;
&lt;li&gt;Το &lt;code&gt;await&lt;/code&gt; περιμένει να ολοκληρωθεί η εργασία του.&lt;/li&gt;
&lt;li&gt;Αν υπάρξει σφάλμα, καταγράφουμε την αποτυχία και ξαναπετάμε το exception.&lt;/li&gt;
&lt;li&gt;Το &lt;code&gt;finally&lt;/code&gt; καταγράφει τη διάρκεια σε κάθε περίπτωση.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Το &lt;code&gt;throw;&lt;/code&gt; επιτρέπει στον κώδικα που έκανε την κλήση να λάβει το αρχικό exception. Το telemetry δεν μετατρέπει μια αποτυχία σε επιτυχημένο αποτέλεσμα.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;using var&lt;/code&gt; κλείνει το activity όταν τελειώσει η μέθοδος. Το &lt;code&gt;activity?.&lt;/code&gt; σημαίνει «κάνε αυτή την κλήση μόνο αν υπάρχει activity», επειδή η &lt;code&gt;StartActivity()&lt;/code&gt; μπορεί να επιστρέψει &lt;code&gt;null&lt;/code&gt; όταν δεν υπάρχει ενδιαφερόμενος listener. Δες την &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.activitysource.startactivity?view=net-10.0" rel="noopener noreferrer"&gt;τεκμηρίωση του &lt;code&gt;ActivitySource&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Στα tags του histogram χρησιμοποιούμε το όνομα του request και λίγες σταθερές τιμές αποτελέσματος. IDs παραγγελιών και email χρηστών θα δημιουργούσαν πάρα πολλούς διαφορετικούς συνδυασμούς μετρήσεων.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;cancelled&lt;/code&gt; αφορά ακύρωση που ζητήθηκε μέσω του token της κλήσης. Άλλο &lt;code&gt;OperationCanceledException&lt;/code&gt; καταγράφεται εδώ ως αποτυχία. Σε μεγαλύτερη εφαρμογή μπορείς να ορίσεις πιο αναλυτική ταξινόμηση των αποτελεσμάτων.&lt;/p&gt;

&lt;h3&gt;
  
  
  Πώς βλέπουμε το telemetry με OpenTelemetry
&lt;/h3&gt;

&lt;p&gt;Ο decorator παράγει τα δεδομένα. Χρειαζόμαστε επίσης ένα σύστημα που τα συλλέγει και έναν exporter, δηλαδή ένα εξάρτημα που τα στέλνει στον επιλεγμένο προορισμό.&lt;/p&gt;

&lt;p&gt;Για ένα τοπικό παράδειγμα σε ASP.NET Core μπορούμε να χρησιμοποιήσουμε τον console exporter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.Console
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Στο &lt;code&gt;Program.cs&lt;/code&gt;, πριν από το &lt;code&gt;builder.Build()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;OpenTelemetry.Metrics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;OpenTelemetry.Trace&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenTelemetry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithTracing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tracing&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tracing&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAspNetCoreInstrumentation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CqrsDiagnostics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SourceName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddConsoleExporter&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithMetrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMeter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CqrsDiagnostics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SourceName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddConsoleExporter&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το &lt;code&gt;AddSource&lt;/code&gt; συνδέει τα activities μας με τη συλλογή traces. Το &lt;code&gt;AddMeter&lt;/code&gt; συνδέει το histogram με τη συλλογή metrics.&lt;/p&gt;

&lt;p&gt;Ο console exporter βοηθά στην τοπική ανάπτυξη. Για αποστολή σε ένα σύστημα παρακολούθησης μπορείς να χρησιμοποιήσεις έναν OTLP exporter και την αντίστοιχη ρύθμιση προορισμού. Δες την &lt;a href="https://opentelemetry.io/docs/languages/dotnet/exporters/" rel="noopener noreferrer"&gt;τεκμηρίωση των OpenTelemetry exporters&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Συνδέουμε τους decorators με Dependency Injection
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Πρώτα η σύνθεση με new
&lt;/h3&gt;

&lt;p&gt;Το Dependency Injection, ή DI, είναι ο μηχανισμός με τον οποίο η εφαρμογή παρέχει σε μια κλάση τις εξαρτήσεις που χρειάζεται.&lt;/p&gt;

&lt;p&gt;Πριν το χρησιμοποιήσουμε, ας δούμε τι θέλουμε να κατασκευάσει. Αν έχουμε ήδη ένα &lt;code&gt;ICurrentUser&lt;/code&gt; με το όνομα currentUser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;IRequestHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PizzaPriceDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GetPizzaPriceHandler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AuthorizationHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PizzaPriceDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TelemetryHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PizzaPriceDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;PizzaPriceDto&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ExtraCheese&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Mushrooms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Olives&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αν ο χρήστης έχει το απαιτούμενο δικαίωμα, η τιμή που επιστρέφεται είναι 8,50 €.&lt;/p&gt;

&lt;p&gt;Ξεκινάμε με την κανονική υλοποίηση και προσθέτουμε ένα αντικείμενο γύρω της κάθε φορά. Κάθε ανάθεση κρατά ως εσωτερικό handler την αλυσίδα που έχουμε ήδη κατασκευάσει.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ένα ρητό registration χωρίς επιπλέον βιβλιοθήκη
&lt;/h3&gt;

&lt;p&gt;Στο ASP.NET Core μπορούμε να δηλώσουμε την ίδια κατασκευή μέσα σε ένα factory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PizzaPriceRegistration&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="nf"&gt;AddPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHttpContextAccessor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ICurrentUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HttpCurrentUser&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetPizzaPriceHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;
            &lt;span class="n"&gt;IRequestHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PizzaPriceDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;IRequestHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PizzaPriceDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
                &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetPizzaPriceHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

            &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AuthorizationHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;
                &lt;span class="n"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PizzaPriceDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ICurrentUser&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;());&lt;/span&gt;

            &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TelemetryHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;
                &lt;span class="n"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PizzaPriceDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Και στο &lt;code&gt;Program.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το factory είναι απλώς ο κώδικας που λέει στο DI πώς να κατασκευάσει την αλυσίδα. Όταν κάποια κλάση ζητήσει &lt;code&gt;IRequestHandler&amp;lt;GetPizzaPriceQuery, PizzaPriceDto&amp;gt;&lt;/code&gt;, θα πάρει τον εξωτερικό &lt;code&gt;TelemetryHandler&lt;/code&gt;, ο οποίος κρατά τους υπόλοιπους μέσα του.&lt;/p&gt;

&lt;p&gt;Χρησιμοποιούμε &lt;code&gt;Scoped&lt;/code&gt; lifetime: τα συγκεκριμένα services συνδέονται με το ενεργό DI scope, που στο ASP.NET Core συνήθως αντιστοιχεί σε ένα HTTP request.&lt;/p&gt;

&lt;p&gt;Καταχωρούμε και τον πραγματικό handler ως service. Έτσι το container διαχειρίζεται τον κύκλο ζωής του. Αυτό έχει σημασία αν αργότερα ο handler αποκτήσει πόρους που χρειάζονται &lt;code&gt;Dispose&lt;/code&gt;. Οι decorators του παραδείγματος δεν κατέχουν τέτοιους πόρους. Δες τις &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection/guidelines" rel="noopener noreferrer"&gt;οδηγίες του .NET για το DI και το disposal&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Για πολλούς handlers μπορεί να αξίζει μια δοκιμασμένη λύση αυτόματου decoration. Το ρητό registration εδώ κρατά ορατό τον τρόπο κατασκευής, χωρίς reflection πάνω στα registrations ή πρόσθετο μηχανισμό υποδομής.&lt;/p&gt;

&lt;h2&gt;
  
  
  Η σειρά αλλάζει τι παρατηρούμε
&lt;/h2&gt;

&lt;p&gt;Στήσαμε αυτή την αλυσίδα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Κλήση HandleAsync
└── TelemetryHandler
    └── AuthorizationHandler
        └── GetPizzaPriceHandler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η εκτέλεση ξεκινά από τον εξωτερικό decorator:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Το telemetry ξεκινά τη μέτρηση.&lt;/li&gt;
&lt;li&gt;Το authorization ελέγχει τα δικαιώματα.&lt;/li&gt;
&lt;li&gt;Ο handler υπολογίζει την τιμή, αν επιτραπεί η εκτέλεση.&lt;/li&gt;
&lt;li&gt;Το αποτέλεσμα ή το exception επιστρέφει προς τα έξω.&lt;/li&gt;
&lt;li&gt;Το telemetry καταγράφει το αποτέλεσμα και τη διάρκεια.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Επειδή το telemetry είναι εξωτερικό, βλέπει και τις απορρίψεις του authorization. Αν αντιστρέφαμε τη σειρά, μια απόρριψη θα συνέβαινε πριν φτάσουμε στον telemetry decorator.&lt;/p&gt;

&lt;p&gt;Η σειρά των decorators μπορεί να αλλάξει ουσιαστικά τη συμπεριφορά της εφαρμογής. Για παράδειγμα, ένας έλεγχος πρόσβασης πρέπει να γίνεται πριν επιστρέψουμε προστατευμένα δεδομένα από cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ο κώδικας που καλεί τον handler
&lt;/h2&gt;

&lt;p&gt;Σε ένα API με ρυθμισμένο authentication, authorization και error handling, το endpoint μπορεί να ζητήσει το κοινό interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/pizza/price"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;extraCheese&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;mushrooms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;olives&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IRequestHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PizzaPriceDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extraCheese&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mushrooms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;olives&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RequireAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το endpoint γνωρίζει το request και το αποτέλεσμα. Η κατασκευή των decorators βρίσκεται στο registration.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;RequireAuthorization()&lt;/code&gt; εφαρμόζει την πολιτική του HTTP endpoint. Ο decorator επιβάλλει τον έλεγχο του use case όταν αυτό καλείται μέσω της διακοσμημένης αλυσίδας, ακόμη και από άλλο σημείο της εφαρμογής.&lt;/p&gt;

&lt;p&gt;Αν ένα background job χρειάζεται να καλέσει το use case, πρέπει να του παρέχουμε κατάλληλη ταυτότητα και υλοποίηση του &lt;code&gt;ICurrentUser&lt;/code&gt;. Η &lt;code&gt;HttpCurrentUser&lt;/code&gt; απαιτεί ενεργό HTTP context για να αναγνωρίσει χρήστη.&lt;/p&gt;

&lt;h2&gt;
  
  
  Έλεγχοι ότι η αλυσίδα λειτουργεί
&lt;/h2&gt;

&lt;p&gt;Μπορούμε να ελέγξουμε ξεχωριστά τον υπολογισμό, τον decorator και τη σύνδεσή τους. Τα παρακάτω tests χρησιμοποιούν xUnit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ο handler υπολογίζει την τιμή
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Xunit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PizzaPriceHandlerTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Returns_price_for_the_selected_ingredients&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GetPizzaPriceHandler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8.50m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Πίτσα με σάλτσα ντομάτας, έξτρα τυρί, μανιτάρια"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Δεν χρειάζεται να δημιουργήσουμε χρήστη ή telemetry για να ελέγξουμε τον υπολογισμό.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ο authorization decorator σταματά την κλήση
&lt;/h3&gt;

&lt;p&gt;Για να ελέγξουμε τον decorator, χρειαζόμαστε έναν ψεύτικο χρήστη και έναν handler που μας λέει αν κλήθηκε:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FakeCurrentUser&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICurrentUser&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;_permissions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;IsAuthenticated&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;FakeCurrentUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;params&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;IsAuthenticated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_permissions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;HasPermission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SpyPizzaPriceHandler&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IRequestHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PizzaPriceDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;WasCalled&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PizzaPriceDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;GetPizzaPriceQuery&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;WasCalled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PizzaPriceDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Test pizza"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;6.00m&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το test ελέγχει και το exception και το ότι ο εσωτερικός handler δεν εκτελέστηκε:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Xunit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthorizationHandlerTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Does_not_call_inner_when_permission_is_missing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;inner&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SpyPizzaPriceHandler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FakeCurrentUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AuthorizationHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;
            &lt;span class="n"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PizzaPriceDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ThrowsAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PermissionDeniedException&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;False&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WasCalled&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Calls_inner_when_permission_is_granted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;inner&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SpyPizzaPriceHandler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FakeCurrentUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"pizza.prices.read"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AuthorizationHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;
            &lt;span class="n"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PizzaPriceDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;True&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WasCalled&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Το DI εφαρμόζει πράγματι τον έλεγχο
&lt;/h3&gt;

&lt;p&gt;Τα προηγούμενα tests κατασκευάζουν τον decorator απευθείας. Χρειαζόμαστε και έναν έλεγχο ότι το registration επιστρέφει τη σωστή αλυσίδα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Xunit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PizzaPriceRegistrationTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Resolved_handler_enforces_permissions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ServiceCollection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Στο test αντικαθιστούμε τον χρήστη του HTTP request.&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ICurrentUser&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FakeCurrentUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BuildServiceProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ServiceProviderOptions&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;ValidateScopes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;ValidateOnBuild&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateScope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;
            &lt;span class="n"&gt;IRequestHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PizzaPriceDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;
            &lt;span class="n"&gt;TelemetryHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PizzaPriceDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ThrowsAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PermissionDeniedException&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GetPizzaPriceQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αυτό το test θα αποτύχει αν το registration παρακάμψει το authorization, ακόμη κι αν ο εξωτερικός τύπος παραμείνει &lt;code&gt;TelemetryHandler&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Για τον telemetry decorator, χρήσιμοι έλεγχοι είναι ότι καταγράφει span και διάρκεια, διατηρεί τα exceptions και ξεχωρίζει τη ζητημένη ακύρωση. Αυτά μπορούν να ελεγχθούν με &lt;code&gt;ActivityListener&lt;/code&gt; και &lt;code&gt;MeterListener&lt;/code&gt;, χωρίς εξωτερικό σύστημα παρακολούθησης.&lt;/p&gt;

&lt;h2&gt;
  
  
  Πού ανήκει κάθε κομμάτι σε Clean Architecture
&lt;/h2&gt;

&lt;p&gt;Σε μια εφαρμογή που ακολουθεί Clean Architecture, μια λογική οργάνωση για το παράδειγμα είναι:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Περιοχή&lt;/th&gt;
&lt;th&gt;Περιεχόμενο&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Domain&lt;/td&gt;
&lt;td&gt;Το μοντέλο της πίτσας και οι κανόνες τιμολόγησης&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application&lt;/td&gt;
&lt;td&gt;Requests, handlers, &lt;code&gt;ICurrentUser&lt;/code&gt;, &lt;code&gt;permission&lt;/code&gt; attribute, authorization decorator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure&lt;/td&gt;
&lt;td&gt;Telemetry decorator και τεχνικές συνδέσεις&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;HttpCurrentUser&lt;/code&gt;, endpoints, HTTP error handling και σύνθεση του DI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Το Application ορίζει τι πληροφορίες χρειάζεται από τον χρήστη μέσω του &lt;code&gt;ICurrentUser&lt;/code&gt;. Το API παρέχει την υλοποίηση που διαβάζει τον χρήστη του HTTP request.&lt;/p&gt;

&lt;p&gt;Η δομή των φακέλων μπορεί να διαφέρει ανά project. Το ουσιαστικό είναι οι κανόνες της εφαρμογής να μη χρειάζονται το HTTP ή έναν συγκεκριμένο προμηθευτή telemetry για να εκτελεστούν.&lt;/p&gt;

&lt;h2&gt;
  
  
  Παγίδες που αξίζει να προσέξεις
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Να ξεχάσεις την κλήση του εσωτερικού αντικειμένου
&lt;/h3&gt;

&lt;p&gt;Αν ένας decorator κάνει μόνο τη δική του δουλειά και δεν καλέσει το &lt;code&gt;_inner&lt;/code&gt;, η κανονική λειτουργία δεν θα εκτελεστεί. Αυτό πρέπει να συμβαίνει μόνο όταν είναι σκόπιμο, όπως στην απόρριψη πρόσβασης ή σε ένα έγκυρο cache hit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Να ζητήσεις το ίδιο interface μέσα από το factory
&lt;/h3&gt;

&lt;p&gt;Μέσα στο factory του &lt;code&gt;IRequestHandler&amp;lt;GetPizzaPriceQuery, PizzaPriceDto&amp;gt;&lt;/code&gt;, μην ζητήσεις από το DI να σου δώσει ξανά αυτό το ίδιο service για να το χρησιμοποιήσεις ως &lt;code&gt;_inner&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Το DI θα προσπαθεί να κατασκευάσει το ίδιο service από την αρχή και θα δημιουργηθεί κυκλική εξάρτηση. Γι’ αυτό στο παράδειγμα ζητάμε τον συγκεκριμένο &lt;code&gt;GetPizzaPriceHandler&lt;/code&gt; και χτίζουμε την αλυσίδα γύρω του.&lt;/p&gt;

&lt;h3&gt;
  
  
  Να παρακάμψεις τους decorators
&lt;/h3&gt;

&lt;p&gt;Αν ένα endpoint δημιουργήσει μόνο του &lt;code&gt;new GetPizzaPriceHandler()&lt;/code&gt;, δεν περνά από το authorization ή το telemetry.&lt;/p&gt;

&lt;p&gt;Το pattern δεν επιβάλλει από μόνο του τη χρήση του. Ο κώδικας της εφαρμογής πρέπει να ζητά το διακοσμημένο interface, και τα tests της σύνθεσης να επιβεβαιώνουν ότι το DI το κατασκευάζει σωστά.&lt;/p&gt;

&lt;h3&gt;
  
  
  Να ξεχάσεις το await ή το CancellationToken
&lt;/h3&gt;

&lt;p&gt;Ένας telemetry decorator πρέπει να περιμένει την ασύγχρονη εργασία πριν ολοκληρώσει τη μέτρηση. Διαφορετικά μπορεί να μετρήσει μόνο τον χρόνο δημιουργίας του &lt;code&gt;Task&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Κάθε decorator πρέπει επίσης να προωθεί το &lt;code&gt;CancellationToken&lt;/code&gt; στον επόμενο handler. Έτσι η δυνατότητα ακύρωσης δεν χάνεται στη μέση της αλυσίδας.&lt;/p&gt;

&lt;h3&gt;
  
  
  Να καταπιείς το exception
&lt;/h3&gt;

&lt;p&gt;Αν καταγράψεις ένα σφάλμα και επιστρέψεις ένα «κανονικό» αποτέλεσμα, ο καλών μπορεί να νομίσει ότι η εργασία ολοκληρώθηκε. Σε έναν decorator παρακολούθησης, συνήθως καταγράφεις και κάνεις &lt;code&gt;throw;&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Να συνδυάσεις ασύμβατα lifetimes
&lt;/h3&gt;

&lt;p&gt;Ένας singleton δεν πρέπει να κρατά ένα service που ανήκει σε request scope. Εδώ κρατάμε την αλυσίδα και το &lt;code&gt;ICurrentUser&lt;/code&gt; ως scoped services και ενεργοποιούμε το scope validation στα tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Να κρύψεις ειδικούς κανόνες ενός use case σε έναν γενικό decorator
&lt;/h3&gt;

&lt;p&gt;Ένας γενικός decorator μπορεί να εφαρμόζει μια κοινή πολιτική δικαιωμάτων ή μετρήσεων. Ένας ειδικός κανόνας όπως «αν η παραγγελία ξεπερνά ένα ποσό, χρειάζεται έγκριση υπευθύνου» πρέπει να φαίνεται στη λογική του αντίστοιχου use case ή του domain.&lt;/p&gt;

&lt;p&gt;Το Decorator μπορεί να χρησιμοποιηθεί και στο domain, όταν εκφράζει καθαρά τη συμπεριφορά του μοντέλου. Το πρόβλημα εδώ είναι να κρύβουμε έναν ειδικό επιχειρησιακό κανόνα σε έναν γενικό decorator που εφαρμόζεται σε πολλά διαφορετικά requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Πότε αξίζει να χρησιμοποιήσεις Decorator
&lt;/h2&gt;

&lt;p&gt;Το pattern είναι χρήσιμο όταν θέλεις να συνδυάζεις ανεξάρτητες προσθήκες γύρω από ένα αντικείμενο, διατηρώντας το ίδιο συμβόλαιο κλήσης.&lt;/p&gt;

&lt;p&gt;Συνηθισμένες εφαρμογές είναι το logging, οι μετρήσεις, οι έλεγχοι πρόσβασης και το caching. Ένας decorator μπορεί επίσης να εφαρμόζει retries, αρκεί η επανάληψη της συγκεκριμένης πράξης να είναι ασφαλής. Μια επανάληψη σε καταχώριση παραγγελίας, για παράδειγμα, χρειάζεται πρόβλεψη ώστε να μη δημιουργήσει δεύτερη παραγγελία.&lt;/p&gt;

&lt;p&gt;Δεν χρειάζεται να το προσθέτεις σε κάθε κλάση. Αν μια μικρή λειτουργία εκφράζεται καθαρά με λίγες γραμμές, τα επιπλέον αντικείμενα μπορεί να κάνουν τον κώδικα δυσκολότερο στην παρακολούθηση. Η χρησιμότητα του pattern κρίνεται από το αν διευκολύνει την επέκταση και την κατανόηση της συγκεκριμένης εφαρμογής.&lt;/p&gt;

&lt;h2&gt;
  
  
  Η διαφορά από άλλα wrappers
&lt;/h2&gt;

&lt;p&gt;Η λέξη wrapper περιγράφει γενικά ένα αντικείμενο που κρατά και χρησιμοποιεί ένα άλλο. Ο σκοπός του καθορίζει ποιο pattern εφαρμόζουμε.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Έννοια&lt;/th&gt;
&lt;th&gt;Κύριος σκοπός&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Decorator&lt;/td&gt;
&lt;td&gt;Προσθέτει συμπεριφορά γύρω από ένα αντικείμενο, διατηρώντας το κοινό συμβόλαιο.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adapter&lt;/td&gt;
&lt;td&gt;Προσαρμόζει μια υπάρχουσα υλοποίηση στο interface που χρειάζεται ο κώδικας που την καλεί.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proxy&lt;/td&gt;
&lt;td&gt;Παρεμβάλλεται στην πρόσβαση σε ένα αντικείμενο, για παράδειγμα για απομακρυσμένη πρόσβαση ή καθυστερημένη δημιουργία.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ένας authorization wrapper μπορεί να περιγραφεί και ως protection proxy. Οι ονομασίες βοηθούν να εξηγήσουμε την πρόθεση του κώδικα, αλλά δεν έχουν πάντα απόλυτα όρια.&lt;/p&gt;

&lt;p&gt;Το ASP.NET Core middleware και τα MediatR pipeline behaviors επιτρέπουν επίσης την εκτέλεση κώδικα πριν ή μετά από την επόμενη λειτουργία. Έχουν όμως δικά τους συμβόλαια και μηχανισμούς σύνθεσης. Το middleware λειτουργεί στην αλυσίδα HTTP, ενώ οι decorators που γράψαμε τυλίγουν το δικό μας interface handler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Τι να κρατήσεις από το παράδειγμα
&lt;/h2&gt;

&lt;p&gt;Ο πραγματικός handler υπολογίζει την τιμή. Ο authorization decorator ελέγχει αν επιτρέπεται η εκτέλεση. Ο telemetry decorator παρακολουθεί ολόκληρη την κλήση και καταγράφει τη διάρκεια και το αποτέλεσμά της.&lt;/p&gt;

&lt;p&gt;Η σύνθεση στο DI ορίζει ποιος decorator βρίσκεται εξωτερικά και ποιος καλείται στη συνέχεια. Στο παράδειγμά μας, το telemetry περιβάλλει και το authorization, οπότε βλέπει και τις απορρίψεις πρόσβασης.&lt;/p&gt;

&lt;p&gt;Αν δεις έναν decorator στον κώδικα, αναζήτησε το κοινό interface, το εσωτερικό αντικείμενο και την επιπλέον δουλειά γύρω από την κλήση του. Για να ελέγξεις τη χρήση του στην εφαρμογή, ακολούθησε και το registration: εκεί φαίνεται ποια αλυσίδα λαμβάνει τελικά ο κώδικας που ζητά τον handler.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://dev.to/__b63657/decorator-pattern-sto-net-apo-te-theoria-se-clean-architecture-me-cqrs-khoris-mediatr-517i"&gt;Decorator pattern στην C# βήμα βήμα με μια πίτσα&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="mailto:nikosstit@gmail.com"&gt;nikosstit@gmail.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>architecture</category>
      <category>cqrs</category>
    </item>
    <item>
      <title>Decorator pattern στην C# βήμα βήμα με μια πίτσα</title>
      <dc:creator>nikosst</dc:creator>
      <pubDate>Thu, 29 Oct 2026 07:36:03 +0000</pubDate>
      <link>https://dev.to/__b63657/decorator-pattern-sto-net-apo-te-theoria-se-clean-architecture-me-cqrs-khoris-mediatr-517i</link>
      <guid>https://dev.to/__b63657/decorator-pattern-sto-net-apo-te-theoria-se-clean-architecture-me-cqrs-khoris-mediatr-517i</guid>
      <description>&lt;p&gt;Κατανόησε το Decorator pattern στην C# με ένα απλό παράδειγμα πίτσας και συνδύασε προσθήκες χωρίς κλάση για κάθε πιθανό συνδυασμό.&lt;/p&gt;




&lt;p&gt;Έχεις μια βασική πίτσα. Ο πελάτης μπορεί να προσθέσει έξτρα τυρί, μανιτάρια, ελιές ή όποιον συνδυασμό θέλει.&lt;/p&gt;

&lt;p&gt;Πώς θα το έγραφες στον κώδικα;&lt;/p&gt;

&lt;p&gt;Θα έφτιαχνες μια διαφορετική κλάση για κάθε πιθανό συνδυασμό; Θα άλλαζες συνέχεια την κλάση της βασικής πίτσας;&lt;/p&gt;

&lt;p&gt;Το Decorator pattern προσφέρει μια άλλη επιλογή: ξεκινάς με ένα αντικείμενο και προσθέτεις δυνατότητες γύρω του, μία κάθε φορά.&lt;/p&gt;

&lt;p&gt;Σε αυτό το άρθρο θα το δούμε με την πίτσα, βήμα βήμα: από το κοινό interface μέχρι τον συνδυασμό των υλικών κατά την εκτέλεση.&lt;/p&gt;

&lt;p&gt;Για να ακολουθήσεις το παράδειγμα, αρκεί να γνωρίζεις κλάσεις, constructors και interfaces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Το πρόβλημα με τους συνδυασμούς
&lt;/h2&gt;

&lt;p&gt;Ας υποθέσουμε ότι η πιτσαρία έχει αυτές τις τιμές:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Επιλογή&lt;/th&gt;
&lt;th&gt;Τιμή&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Βασική πίτσα με σάλτσα ντομάτας&lt;/td&gt;
&lt;td&gt;6,00 €&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Έξτρα τυρί&lt;/td&gt;
&lt;td&gt;+1,50 €&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Μανιτάρια&lt;/td&gt;
&lt;td&gt;+1,00 €&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ελιές&lt;/td&gt;
&lt;td&gt;+0,50 €&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ο ένας πελάτης θέλει μόνο τυρί. Ο άλλος τυρί και μανιτάρια. Ο τρίτος μανιτάρια και ελιές.&lt;/p&gt;

&lt;p&gt;Αν έφτιαχνες μια κλάση για κάθε συνδυασμό, θα κατέληγες με ονόματα όπως:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PlainPizza
PizzaWithExtraCheese
PizzaWithMushrooms
PizzaWithOlives
PizzaWithExtraCheeseAndMushrooms
PizzaWithExtraCheeseAndOlives
PizzaWithMushroomsAndOlives
PizzaWithExtraCheeseAndMushroomsAndOlives
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Με κάθε νέο υλικό αυξάνονται οι πιθανοί συνδυασμοί. Δεν θέλουμε να γράφουμε μια νέα κλάση για κάθε παραγγελία που μπορεί να σκεφτεί ο πελάτης.&lt;/p&gt;

&lt;p&gt;Θέλουμε να γράψουμε κάθε προσθήκη μία φορά και να μπορούμε να τη συνδυάζουμε με τις υπόλοιπες.&lt;/p&gt;

&lt;h2&gt;
  
  
  Η ιδέα σε μία πρόταση
&lt;/h2&gt;

&lt;p&gt;Ένας decorator κρατά μέσα του ένα άλλο αντικείμενο, προσφέρει τις ίδιες μεθόδους και προσθέτει τη δική του δουλειά όταν τις καλούμε.&lt;/p&gt;

&lt;p&gt;Στο παράδειγμά μας, η πίτσα με έξτρα τυρί θα κρατά μέσα της μια πίτσα. Όταν ζητήσουμε την τιμή της, θα πάρει την τιμή της εσωτερικής πίτσας και θα προσθέσει την τιμή του τυριού.&lt;/p&gt;

&lt;p&gt;Ας το γράψουμε.&lt;/p&gt;

&lt;h2&gt;
  
  
  Βήμα 1 Ορίζουμε τι μπορεί να κάνει μια πίτσα
&lt;/h2&gt;

&lt;p&gt;Χρειαζόμαστε δύο πληροφορίες: την περιγραφή και την τιμή.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IPizza&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetDescription&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;GetPrice&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το interface είναι ένα κοινό συμβόλαιο. Οποιαδήποτε κλάση υλοποιεί το &lt;code&gt;IPizza&lt;/code&gt; πρέπει να παρέχει αυτές τις δύο μεθόδους.&lt;/p&gt;

&lt;p&gt;Άρα, αν έχουμε μια μεταβλητή τύπου &lt;code&gt;IPizza&lt;/code&gt;, μπορούμε να ζητήσουμε περιγραφή και τιμή χωρίς να γνωρίζουμε ποια συγκεκριμένη κλάση βρίσκεται από πίσω.&lt;/p&gt;

&lt;h2&gt;
  
  
  Βήμα 2 Φτιάχνουμε τη βασική πίτσα
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlainPizza&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IPizza&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetDescription&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Πίτσα με σάλτσα ντομάτας"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;GetPrice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;6.00m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Το &lt;code&gt;m&lt;/code&gt; στο &lt;code&gt;6.00m&lt;/code&gt; δηλώνει αριθμό τύπου &lt;code&gt;decimal&lt;/code&gt;, τον οποίο χρησιμοποιούμε εδώ για τις χρηματικές τιμές.&lt;/p&gt;

&lt;p&gt;Προς το παρόν έχουμε μόνο μια απλή πίτσα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;IPizza&lt;/span&gt; &lt;span class="n"&gt;pizza&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PlainPizza&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDescription&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPrice&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η περιγραφή είναι «Πίτσα με σάλτσα ντομάτας» και η τιμή 6,00 €.&lt;/p&gt;

&lt;h2&gt;
  
  
  Βήμα 3 Προσθέτουμε έξτρα τυρί με έναν decorator
&lt;/h2&gt;

&lt;p&gt;Θέλουμε να προσθέσουμε έξτρα τυρί χωρίς να αλλάξουμε την &lt;code&gt;PlainPizza&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExtraCheeseDecorator&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IPizza&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IPizza&lt;/span&gt; &lt;span class="n"&gt;_pizza&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ExtraCheeseDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IPizza&lt;/span&gt; &lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_pizza&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetDescription&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_pizza&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDescription&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;", έξτρα τυρί"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;GetPrice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_pizza&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPrice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1.50m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αυτή η κλάση είναι ο πρώτος μας decorator. Ας δούμε τι κάνει κάθε κομμάτι της.&lt;/p&gt;

&lt;p&gt;Το &lt;code&gt;: IPizza&lt;/code&gt; σημαίνει ότι προσφέρει τις ίδιες μεθόδους με τη βασική πίτσα: &lt;code&gt;GetDescription()&lt;/code&gt; και &lt;code&gt;GetPrice()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Το πεδίο &lt;code&gt;_pizza&lt;/code&gt; κρατά την πίτσα στην οποία προσθέτουμε τυρί. Η πίτσα αυτή περνά στον constructor.&lt;/p&gt;

&lt;p&gt;Η &lt;code&gt;GetPrice()&lt;/code&gt; ζητά πρώτα την τιμή από την εσωτερική πίτσα και προσθέτει 1,50 €. Η &lt;code&gt;GetDescription()&lt;/code&gt; ζητά την περιγραφή της και προσθέτει το κείμενο «έξτρα τυρί».&lt;/p&gt;

&lt;p&gt;Η χρήση του είναι:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;IPizza&lt;/span&gt; &lt;span class="n"&gt;plainPizza&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PlainPizza&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;IPizza&lt;/span&gt; &lt;span class="n"&gt;pizzaWithCheese&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ExtraCheeseDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plainPizza&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Έχουμε πλέον δύο αντικείμενα. Το ένα είναι η βασική πίτσα και το άλλο ο decorator που την κρατά μέσα του:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ExtraCheeseDecorator
└── PlainPizza
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Όταν γράφουμε:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pizzaWithCheese&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPrice&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;συμβαίνουν τα εξής:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Καλείται η &lt;code&gt;GetPrice()&lt;/code&gt; του &lt;code&gt;ExtraCheeseDecorator&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ο decorator καλεί τη &lt;code&gt;GetPrice()&lt;/code&gt; της &lt;code&gt;PlainPizza&lt;/code&gt; και παίρνει &lt;code&gt;6.00m&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Προσθέτει &lt;code&gt;1.50m&lt;/code&gt; και επιστρέφει &lt;code&gt;7.50m&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Η &lt;code&gt;PlainPizza&lt;/code&gt; εξακολουθεί να κοστίζει 6,00 €. Το νέο αντικείμενο &lt;code&gt;pizzaWithCheese&lt;/code&gt; επιστρέφει 7,50 €.&lt;/p&gt;

&lt;p&gt;Αυτό εννοούμε όταν λέμε ότι ο decorator προσθέτει συμπεριφορά χωρίς να αλλάζει το αρχικό αντικείμενο.&lt;/p&gt;

&lt;h2&gt;
  
  
  Βήμα 4 Φτιάχνουμε έναν decorator για κάθε επιπλέον υλικό
&lt;/h2&gt;

&lt;p&gt;Τα μανιτάρια ακολουθούν την ίδια λογική:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MushroomsDecorator&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IPizza&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IPizza&lt;/span&gt; &lt;span class="n"&gt;_pizza&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MushroomsDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IPizza&lt;/span&gt; &lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_pizza&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetDescription&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_pizza&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDescription&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;", μανιτάρια"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;GetPrice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_pizza&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPrice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1.00m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Και οι ελιές:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OlivesDecorator&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IPizza&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IPizza&lt;/span&gt; &lt;span class="n"&gt;_pizza&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;OlivesDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IPizza&lt;/span&gt; &lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_pizza&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetDescription&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_pizza&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDescription&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;", ελιές"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;GetPrice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_pizza&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPrice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;0.50m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Κάθε decorator γνωρίζει μόνο τη δική του προσθήκη. Ο decorator για τις ελιές δεν χρειάζεται να γνωρίζει αν η πίτσα έχει ήδη τυρί ή μανιτάρια.&lt;/p&gt;

&lt;h2&gt;
  
  
  Βήμα 5 Προσθέτουμε υλικά ένα ένα
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;IPizza&lt;/span&gt; &lt;span class="n"&gt;pizza&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PlainPizza&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Βασική πίτσα: 6,00 €&lt;/span&gt;

&lt;span class="n"&gt;pizza&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ExtraCheeseDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Προσθέσαμε τυρί: 7,50 €&lt;/span&gt;

&lt;span class="n"&gt;pizza&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MushroomsDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Προσθέσαμε μανιτάρια: 8,50 €&lt;/span&gt;

&lt;span class="n"&gt;pizza&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OlivesDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Προσθέσαμε ελιές: 9,00 €&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDescription&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPrice&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η τελική περιγραφή είναι:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Πίτσα με σάλτσα ντομάτας, έξτρα τυρί, μανιτάρια, ελιές
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η τελική τιμή είναι 9,00 €.&lt;/p&gt;

&lt;p&gt;Πρόσεξε αυτή τη γραμμή:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;pizza&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MushroomsDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Στα δεξιά, δίνουμε στον νέο decorator ό,τι περιείχε μέχρι εκείνη τη στιγμή η μεταβλητή &lt;code&gt;pizza&lt;/code&gt;: την πίτσα με έξτρα τυρί.&lt;/p&gt;

&lt;p&gt;Στα αριστερά, κάνουμε τη μεταβλητή να δείχνει στο νέο αντικείμενο, το οποίο προσθέτει μανιτάρια.&lt;/p&gt;

&lt;p&gt;Δεν σβήνεται η προηγούμενη πίτσα. Ο νέος decorator κρατά αναφορά σε αυτήν. Έτσι σχηματίζεται μια αλυσίδα:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OlivesDecorator
└── MushroomsDecorator
    └── ExtraCheeseDecorator
        └── PlainPizza
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Η κλήση της &lt;code&gt;GetPrice()&lt;/code&gt; ξεκινά από τις ελιές και προχωρά προς τη βασική πίτσα. Έπειτα τα αποτελέσματα επιστρέφουν προς τα έξω:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PlainPizza                 → 6,00 €
ExtraCheeseDecorator       → 6,00 + 1,50 = 7,50 €
MushroomsDecorator         → 7,50 + 1,00 = 8,50 €
OlivesDecorator            → 8,50 + 0,50 = 9,00 €
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Βήμα 6 Αφήνουμε τον πελάτη να επιλέξει
&lt;/h2&gt;

&lt;p&gt;Δεν χρειάζεται κάθε πίτσα να έχει όλα τα υλικά. Ας υποθέσουμε ότι οι παρακάτω τιμές προέρχονται από τις επιλογές του πελάτη:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;wantsExtraCheese&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;wantsMushrooms&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;wantsOlives&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;IPizza&lt;/span&gt; &lt;span class="n"&gt;pizza&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PlainPizza&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wantsExtraCheese&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pizza&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ExtraCheeseDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wantsMushrooms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pizza&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MushroomsDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wantsOlives&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pizza&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OlivesDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDescription&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPrice&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Αυτή η παραγγελία έχει έξτρα τυρί και ελιές, άρα κοστίζει 8,00 €.&lt;/p&gt;

&lt;p&gt;Αν ο πελάτης επιλέξει μόνο μανιτάρια, εφαρμόζεται μόνο ο &lt;code&gt;MushroomsDecorator&lt;/code&gt;. Αν δεν επιλέξει τίποτα, παραμένει η &lt;code&gt;PlainPizza&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Αυτό σημαίνει ότι συνδυάζουμε τις προσθήκες κατά την εκτέλεση του προγράμματος, ανάλογα με τις επιλογές που λαμβάνουμε.&lt;/p&gt;

&lt;p&gt;Για να δοκιμάσεις την πίτσα σε ένα console project, βάλε στο &lt;code&gt;Program.cs&lt;/code&gt; πρώτα ένα από τα παραδείγματα χρήσης και από κάτω το &lt;code&gt;IPizza&lt;/code&gt; και τις τέσσερις κλάσεις. Τα παραδείγματα χρήσης είναι εναλλακτικά, οπότε δεν χρειάζεται να τα αντιγράψεις όλα μαζί.&lt;/p&gt;

&lt;h2&gt;
  
  
  Τι κάνει τελικά μια κλάση decorator
&lt;/h2&gt;

&lt;p&gt;Ο &lt;code&gt;ExtraCheeseDecorator&lt;/code&gt; έχει δύο βασικά χαρακτηριστικά:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Είναι &lt;code&gt;IPizza&lt;/code&gt;, οπότε μπορούμε να τον χρησιμοποιήσουμε όπου περιμένουμε μια πίτσα.&lt;/li&gt;
&lt;li&gt;Κρατά ένα &lt;code&gt;IPizza&lt;/code&gt;, οπότε μπορεί να περιέχει είτε τη βασική πίτσα είτε άλλον decorator.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ο constructor του δέχεται &lt;code&gt;IPizza&lt;/code&gt;, όχι μόνο &lt;code&gt;PlainPizza&lt;/code&gt;. Γι’ αυτό μπορεί να δεχτεί και μια πίτσα που έχει ήδη μανιτάρια ή ελιές.&lt;/p&gt;

&lt;p&gt;Ο υπόλοιπος κώδικας συνεχίζει να καλεί τις ίδιες δύο μεθόδους:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDescription&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pizza&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPrice&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Δεν χρειάζεται να εξετάσει πόσα αντικείμενα υπάρχουν στην αλυσίδα ή ποια υλικά έχει επιλέξει ο πελάτης.&lt;/p&gt;

&lt;h2&gt;
  
  
  Τι κερδίσαμε
&lt;/h2&gt;

&lt;p&gt;Η βασική πίτσα έμεινε απλή. Κάθε υλικό γράφτηκε μία φορά και μπορεί να συνδυαστεί με τα υπόλοιπα.&lt;/p&gt;

&lt;p&gt;Για να προσθέσουμε πιπεριά, μπορούμε να γράψουμε έναν &lt;code&gt;PeppersDecorator&lt;/code&gt;. Δεν χρειάζεται να αλλάξουμε τις υπάρχουσες κλάσεις της πίτσας. Θα προσθέσουμε, βέβαια, την καινούργια επιλογή στο σημείο όπου διαβάζουμε τις επιλογές του πελάτη.&lt;/p&gt;

&lt;p&gt;Αυτό συνδέεται με δύο αρχές του SOLID:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single Responsibility: κάθε κλάση έχει μια συγκεκριμένη ευθύνη.&lt;/li&gt;
&lt;li&gt;Open/Closed: επεκτείνουμε τη λειτουργικότητα με νέα κλάση, διατηρώντας τις υπάρχουσες υλοποιήσεις.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Το κοινό interface βοηθά επίσης τον κώδικα να εξαρτάται από το &lt;code&gt;IPizza&lt;/code&gt;, αντί να γνωρίζει κάθε συγκεκριμένη κλάση.&lt;/p&gt;

&lt;h2&gt;
  
  
  Χρειάζεται κάθε πιτσαρία Decorator
&lt;/h2&gt;

&lt;p&gt;Αν το μόνο που χρειάζεσαι είναι μια λίστα υλικών και το άθροισμα των τιμών τους, μια συλλογή από &lt;code&gt;Topping&lt;/code&gt; αντικείμενα μπορεί να είναι απλούστερη λύση.&lt;/p&gt;

&lt;p&gt;Εδώ χρησιμοποιούμε την πίτσα για να γίνει ορατός ο μηχανισμός του pattern: κάθε αντικείμενο ζητά το αποτέλεσμα του προηγούμενου και προσθέτει κάτι δικό του.&lt;/p&gt;

&lt;p&gt;Η ίδια δομή γίνεται ιδιαίτερα χρήσιμη όταν η προσθήκη είναι μια ενέργεια γύρω από μια άλλη ενέργεια. Για παράδειγμα: «έλεγξε δικαιώματα πριν εκτελέσεις μια λειτουργία» ή «μέτρησε πόσο χρόνο χρειάστηκε».&lt;/p&gt;

&lt;h2&gt;
  
  
  Τι να κρατήσεις από το παράδειγμα
&lt;/h2&gt;

&lt;p&gt;Ξεκινήσαμε με μια πίτσα των 6,00 €. Κάθε decorator πρόσθεσε την περιγραφή και την τιμή ενός υλικού, χρησιμοποιώντας την πίτσα που είχε ήδη δημιουργηθεί.&lt;/p&gt;

&lt;p&gt;Ο μηχανισμός στηρίζεται σε τρία κομμάτια:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ένα κοινό interface, το &lt;code&gt;IPizza&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ένα εσωτερικό αντικείμενο που υλοποιεί αυτό το interface.&lt;/li&gt;
&lt;li&gt;Τη συμπεριφορά που προσθέτει ο decorator γύρω από την κλήση του.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Επειδή ένας decorator είναι και ο ίδιος &lt;code&gt;IPizza&lt;/code&gt;, μπορεί να τυλιχτεί από έναν ακόμη decorator. Έτσι συνδυάζουμε προσθήκες κατά την εκτέλεση, χωρίς να χρειαζόμαστε μια διαφορετική κλάση για κάθε πιθανό συνδυασμό.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://dev.to/__b63657/decorator-pattern-se-cqrs-handlers-me-authorization-kai-telemetry-sto-net-7do"&gt;Decorator pattern σε CQRS handlers με authorization και telemetry στο .NET&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="mailto:nikosstit@gmail.com"&gt;nikosstit@gmail.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>csharp</category>
      <category>designpatterns</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
