<?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: Julia Shevchenko</title>
    <description>The latest articles on DEV Community by Julia Shevchenko (@juliashevchenko).</description>
    <link>https://dev.to/juliashevchenko</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2052249%2Fe5349476-0bab-4113-9063-6d02e10c95a9.jpg</url>
      <title>DEV Community: Julia Shevchenko</title>
      <link>https://dev.to/juliashevchenko</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/juliashevchenko"/>
    <language>en</language>
    <item>
      <title>How Ubiquitous Language Can Solve Your Communication Issues</title>
      <dc:creator>Julia Shevchenko</dc:creator>
      <pubDate>Wed, 01 Apr 2026 07:35:18 +0000</pubDate>
      <link>https://dev.to/juliashevchenko/how-ubiquitous-language-can-solve-your-miscommunication-issues-38fo</link>
      <guid>https://dev.to/juliashevchenko/how-ubiquitous-language-can-solve-your-miscommunication-issues-38fo</guid>
      <description>&lt;p&gt;When someone asks me what Domain-driven design is for, I always tell a story that happened to me once. &lt;/p&gt;

&lt;p&gt;I joined a new project and had long onboarding meetings where I was told what was already in place and what should be done. The language of communication was English. I am not a native English speaker, but by that time, I hadn't had any issues in communication. I was trying hard to understand the current system infrastructure, architecture, and future requirements. There was no documentation, and the only source of truth was verbal discussion. Soon, I've noticed something unclear.&lt;/p&gt;

&lt;p&gt;First, I've discovered a &lt;strong&gt;tenant&lt;/strong&gt; as a part of a &lt;a href="https://en.wikipedia.org/wiki/Multitenancy" rel="noopener noreferrer"&gt;multitenancy&lt;/a&gt; setup - a customer who owns a dedicated instance of software. Then another meaning of "&lt;strong&gt;tenant&lt;/strong&gt;" emerged: a person or company who rents an apartment. Both, also, could occasionally be called users, which confused me even more. When I finally understood what was happening and pointed out that we have this ambiguous naming, the person I was speaking to told me that in his native language, they have different words for these things, but English happens to be less descriptive. As a result, we agreed to use different words and be specific to what we're talking about. It simplified my life a lot while onboarding new members on this project some time later.&lt;/p&gt;

&lt;p&gt;And similar situations happened to me many more times. And I see that it is continuing to happen among many other teams. &lt;/p&gt;

&lt;p&gt;Domain-driven design does not start with the code. &lt;strong&gt;It starts&lt;/strong&gt; with team cooperation in discussing and agreeing to use a common project language, widely known as the ubiquitous language. &lt;/p&gt;

&lt;h2&gt;
  
  
  How should I start?
&lt;/h2&gt;

&lt;p&gt;Follow this (not so easy as it looks) two-step plan to start with Domain-driven design: &lt;br&gt;
&lt;u&gt;Step 1&lt;/u&gt; - Within the project team, agree on all terms you have and &lt;strong&gt;start&lt;/strong&gt; using them correctly. Team members could encourage and support each other when adopting new terminology. &lt;br&gt;
&lt;u&gt;Step 2&lt;/u&gt; - Continue using it! The project will grow and evolve, and the language must keep up with it. &lt;/p&gt;

&lt;p&gt;If you wonder if following any DDD principles is worth your time in the era of LLM-agents writing code for you, check my article to see &lt;a href="https://dev.to/juliashevchenko/is-ddd-still-relevant-in-the-vibe-coding-era-5cam"&gt;how you can benefit from DDD even more&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, DDD could also benefit from adopting cutting-edge technologies. Let's see how it can help us.&lt;/p&gt;

&lt;h2&gt;
  
  
  The source of truth
&lt;/h2&gt;

&lt;p&gt;Having a source of truth is a solid helper in any discussion. Set up a glossary with all the terminology that is domain-specific or project-specific. Start small - put the most used words and phrases. I would also recommend putting the abbreviation there if there are any, it will help a lot! It should not be fancy, it could even be a text file but shared with the whole team inside some cloud. Each team member could add new things, but editing already existing ones should require the team's approval, so everyone is aware that something has changed. &lt;/p&gt;

&lt;p&gt;Nowadays, you can start by asking an LLM to create a first version of a glossary and propose the meaning of each term, so the team doesn't need to start from scratch. It can even group and format it nicely and prepare the format for any tool you'd like to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The source of truth becomes unreliable
&lt;/h2&gt;

&lt;p&gt;Here comes the hardest part. &lt;/p&gt;

&lt;p&gt;Project evolves, new functionality appears, new customers start using their own terminology, new team members bring new abbreviations, and the source of truth becomes unreliable, because it was updated 2 years ago by the person who is not even part of the team anymore. &lt;/p&gt;

&lt;p&gt;Just as the project development process is a continuous process, the maintenance of the ubiquitous language should also be continuous. It is the only way to benefit from it, from the time you've already dedicated to its development and adoption.&lt;/p&gt;

&lt;p&gt;And here, modern technologies could also help. You can create a recurring task to review the glossary against the codebase, tickets, documentation, and other relevant sources to see if anything new has appeared or been changed. Then, it could be a quick 5-minute discussion within the team if everyone agrees with that. It could also be done in async mode by some vote tool. &lt;/p&gt;




&lt;p&gt;Just imagine how much easier onboarding a project would be with a documented glossary.&lt;/p&gt;

&lt;p&gt;Came after a 2-week vacation and can't understand what the new abbreviation means? Check the glossary! &lt;/p&gt;

&lt;p&gt;Have noticed some ambiguity in the code - check the glossary to know how it should be refactored. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Domain-driven design is not just for software developers, it is for the entire team to be able to communicate efficiently.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;The article cover is Aleksandra Ekster's &lt;a href="https://dev.tourl"&gt;artwork&lt;/a&gt; "Venice".&lt;/p&gt;

</description>
      <category>ddd</category>
      <category>architecture</category>
      <category>software</category>
      <category>programming</category>
    </item>
    <item>
      <title>Is DDD still relevant in the vibe-coding era?</title>
      <dc:creator>Julia Shevchenko</dc:creator>
      <pubDate>Wed, 25 Mar 2026 19:05:48 +0000</pubDate>
      <link>https://dev.to/juliashevchenko/is-ddd-still-relevant-in-the-vibe-coding-era-5cam</link>
      <guid>https://dev.to/juliashevchenko/is-ddd-still-relevant-in-the-vibe-coding-era-5cam</guid>
      <description>&lt;p&gt;It is not possible to deny that a massive shift has happened. It's hard to say the exact percentage of code that is now generated by LLMs. But what is obvious - this number rapidly grows. A lot of people are questioning the role of a software engineer and whether such a profession will exist in a couple of years. &lt;/p&gt;

&lt;p&gt;However, LLMs are not working autonomously. They need someone to orchestrate them, providing a specification and validating an outcome. &lt;strong&gt;The more precise the specification is, the better the result you can expect.&lt;/strong&gt; Basically, this rule applies to any development process. &lt;/p&gt;

&lt;p&gt;Moreover, an LLM produces much more suitable code when given an example. The example could be a short code snippet, but it would be much better if it followed the project's codebase style and approaches. &lt;/p&gt;

&lt;p&gt;Finally, the generated code should be reviewed and tested carefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evolution of Communication
&lt;/h2&gt;

&lt;p&gt;Language is a way to communicate with other people. And sometimes it is not an easy task. It happens to be quite difficult to express thoughts or to understand other people's ideas. Language allows one to express something in an endless number of different ways. Speaking is not deterministic, but humans handle that complexity in most cases. &lt;/p&gt;

&lt;p&gt;Computers, on the other hand, understand only 0 and 1. The problem is - people don't. &lt;/p&gt;

&lt;p&gt;Programming languages were designed to fill this communication gap. They have strict rules for expressing what needs to be done so it can be clearly translated into what the computer understands. The program will do exactly what you tell it to do via programming language. &lt;/p&gt;

&lt;p&gt;But what happened recently is that we can now communicate with coding agents in any language, and the agent translates it to the programming language of our choice. And here comes a problem - &lt;strong&gt;speaking is too ambiguous&lt;/strong&gt;.  And if the code base is also too ambiguous, no agent will be able to generate proper and reliable code that fits your needs, especially business logic.&lt;/p&gt;

&lt;p&gt;You tell it to make a change to tenant management, but there are places in code where it is called &lt;em&gt;client&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;You tell it to add some functionality for a buyer, but it is also called a consumer, purchaser, and, again, &lt;em&gt;client&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;You tell it to add a new entity - &lt;em&gt;client&lt;/em&gt;, because this is how it was called in meetings, but there are already plenty of things named as &lt;em&gt;client&lt;/em&gt; in the code. &lt;/p&gt;

&lt;p&gt;And each time you have to explain what is what, and you may still miss some tiny detail. &lt;/p&gt;

&lt;p&gt;Of course, the good-to-go approach is to tell the agent to collect knowledge about the domain. But it is hard to aggregate the domain knowledge if the domain is not clearly defined. &lt;/p&gt;

&lt;h2&gt;
  
  
  Meet your new colleague
&lt;/h2&gt;

&lt;p&gt;In addition to overcoming difficulties in communication between teams, you now need to overcome difficulties in communication with AI Agents. To reduce the number of hallucinations, you need to provide it with clear instructions and a summary of project knowledge so it can rely on them instead of repeating the same research every time. You need to be precise with terminology and in describing the results you want to achieve. It is very similar to onboarding a new colleague to the project. &lt;/p&gt;

&lt;h2&gt;
  
  
  What is Domain-Driven Design?
&lt;/h2&gt;

&lt;p&gt;Domain-Driven Design - is a concept of having code base that reflects the business logic. It unites two worlds - technical and product commands, so they can understand each other better and work together more efficiently. I often see that Domain-Driven Design is defined as a set of patterns. However, patterns are only tools to achieve the final goal, and it is not mandatory to follow all of them.&lt;/p&gt;

&lt;p&gt;From an engineering perspective, use cases reflect what's going on in real life, and the code becomes more than just a set of random get/set calls. Why is it a good thing? Any request for a change to business logic can be easily translated into an existing code base. The question, "How does this feature work?", does not require a week of investigation. Of course, real life is much more complicated, and such a state cannot be reached from the decision to apply DDD to the project from day one. It is the process of evolution to become business-centric, not framework-centric or infrastructure-centric. It requires hard work, with both engineering and product teams involved, to define and shape the domain. &lt;/p&gt;

&lt;h2&gt;
  
  
  Is it still relevant?
&lt;/h2&gt;

&lt;p&gt;So, what advantages does Domain-driven design provide for AI-first development? &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Writing in DDD-style makes code &lt;strong&gt;more readable&lt;/strong&gt; -&amp;gt; review process will become easier and faster;&lt;/li&gt;
&lt;li&gt;The domain layer literally repeats domain logic -&amp;gt; easy to notice if some use case is missing or doesn't do what is expected because &lt;strong&gt;the logic isn't scattered throughout the project&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;Domain-centric code is easy to cover with unit tests that check domain logic, not just getters and setters -&amp;gt; &lt;strong&gt;code becomes easier to maintain and extend&lt;/strong&gt;; easy to notice that something is broken;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ubiquitous language makes the communication easier&lt;/strong&gt; not only with humans, but also with LLMs -&amp;gt; less confusion means less hallucinations;&lt;/li&gt;
&lt;li&gt;Bounded contexts could force making smaller changes -&amp;gt; &lt;strong&gt;change will be applied to a context, not affecting the whole project&lt;/strong&gt; with unpredictable consequences;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Domain-driven Design solves the same problem as before - decreases ambiguity and brings clarity to the development process&lt;/strong&gt;, but now with the new party involved. Product and engineering teams, along with AI-agents, share the &lt;u&gt;same&lt;/u&gt; knowledge.&lt;/p&gt;

&lt;p&gt;Would I still apply DDD to a project? Definitely. &lt;/p&gt;

&lt;p&gt;It may seem like such concepts are now outdated. Why bother with such things when an AI agent can write code for us, fix bugs, and answer questions about the product? But all of these rely on an input context. When the input becomes messy, the quality of the outcome drops drastically. Same as with simple old manual engineering - you must keep the bar high to maintain high-quality results. &lt;/p&gt;




&lt;p&gt;The article cover is a fragment of Oleksandr Bohomazov's &lt;a href="https://www.google.com/search?sa=X&amp;amp;sca_esv=5c685c3490d49ca4&amp;amp;sxsrf=ANbL-n5PT90tpj0viWQNKzSAHTp_DcbouQ:1774465596605&amp;amp;q=Sawyers&amp;amp;stick=H4sIAAAAAAAAAONgFuLUz9U3sMjNMchW4tVP1zc0TDYuSS80MMjTUspOttIvyywuTcyJTywqQWJmFpdYlecXZRcvYmUPTiyvTC0qBgBw3JwCSgAAAA&amp;amp;ved=2ahUKEwiF4rS837uTAxV-JRAIHV33CHQQtq8DegQIKhAF&amp;amp;biw=1685&amp;amp;bih=819&amp;amp;dpr=1.14#&amp;amp;tbs=kac:1,kac_so:0" rel="noopener noreferrer"&gt;artwork&lt;/a&gt; "Sawyers". &lt;/p&gt;

</description>
      <category>architecture</category>
      <category>ai</category>
      <category>vibecoding</category>
      <category>ddd</category>
    </item>
    <item>
      <title>How to check if AutoMapper misses any mapping</title>
      <dc:creator>Julia Shevchenko</dc:creator>
      <pubDate>Thu, 29 Jan 2026 15:30:00 +0000</pubDate>
      <link>https://dev.to/juliashevchenko/an-easy-way-to-check-if-automapper-misses-any-mapping-38p</link>
      <guid>https://dev.to/juliashevchenko/an-easy-way-to-check-if-automapper-misses-any-mapping-38p</guid>
      <description>&lt;p&gt;I am a hater of AutoMapper. I don't think it is really beneficial to delegate mapping to it, because over time, it becomes a mapping hell where you are trying to identify which properties haven't been mapped and where and why. You would never be in such a situation if you just manually map one type to another. Yes, it is boring. But it is stable. After all, you can speed it up with Copilot or any other tool. &lt;/p&gt;

&lt;p&gt;But if you got trapped in AutoMapper frustration, I'm here to cover your back.&lt;br&gt;
There is a very fast and easy way to check if any type maps are missing. Add this to your startup script, and it will tell you during startup if there are any unmapped properties. This will throw an exception if there are any issues, preventing the application from running.&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;app&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="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="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="n"&gt;app&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;CreateScope&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;mapper&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;IMapper&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="n"&gt;mapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConfigurationProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AssertConfigurationIsValid&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;h4&gt;
  
  
  Example output
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unhandled exception. 

AutoMapper.AutoMapperConfigurationException: Unmapped members were found.

Review the types and members below. Add a custom mapping expression,
ignore, add a custom resolver, or modify the source/destination type 

For no matching constructor, add a no-arg ctor, add optional arguments, 
or map all of the constructor parameters 

=============================================
CustomerDto -&amp;gt; CreateCustomerCommand (Destination member list) 

SampleApp.Application.Dtos.CustomerDto -&amp;gt; 

SampleApp.Application.Commands.Customers.CreateCustomerCommand (Destination member list)

Unmapped properties: 
OrganizationId 
=============================================

CustomerDto -&amp;gt; UpdateCustomerCommand (Destination member list) 

SampleApp.Application.Dtos.CustomerDto -&amp;gt; 

SampleApp.Application.Commands.Customers.UpdateCustomerCommand (Destination member list)

Unmapped properties: 
OrganizationId 
CustomerId 
=============================================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🗺️ Enjoy your coding!&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>testing</category>
      <category>codequality</category>
    </item>
    <item>
      <title>How to Inject Service from DI Container to Marten Async Projection</title>
      <dc:creator>Julia Shevchenko</dc:creator>
      <pubDate>Thu, 09 Oct 2025 15:18:51 +0000</pubDate>
      <link>https://dev.to/juliashevchenko/how-to-inject-service-from-di-container-to-marten-async-projection-4f77</link>
      <guid>https://dev.to/juliashevchenko/how-to-inject-service-from-di-container-to-marten-async-projection-4f77</guid>
      <description>&lt;p&gt;Recently, I had a need to &lt;strong&gt;inject a service directly from the DI Container to the &lt;a href="https://martendb.io/" rel="noopener noreferrer"&gt;Marten&lt;/a&gt; Async projection&lt;/strong&gt;. The issue is that Marten async projections are run in separate scopes, so they cannot reach services from other scopes.&lt;br&gt;
It appeared to be tricky, and I couldn't find any information in the documentation on how to overcome it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Marten async projections VS DI
&lt;/h2&gt;

&lt;p&gt;Marten runs async projections in the &lt;a href="https://martendb.io/events/projections/async-daemon.html#async-projections-daemon" rel="noopener noreferrer"&gt;background daemon&lt;/a&gt;.&lt;br&gt;
These &lt;a href="https://martendb.io/events/projections/ioc.html#projections-and-ioc-services" rel="noopener noreferrer"&gt;projections are run out of any HTTP request or message handler scope&lt;/a&gt;, so it's not possible to inject scoped services directly. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;ILogger&lt;/code&gt;, for example, is usually registered as a singleton, so Marten allows it to be injected into async projections.&lt;/p&gt;
&lt;h2&gt;
  
  
  Typical Configuration
&lt;/h2&gt;

&lt;p&gt;This is how async projection configuration usually looks:&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;AddMarten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storeOptions&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="n"&gt;AddProjectionWithServices&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyProjection&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
      &lt;span class="n"&gt;ProjectionLifecycle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Async&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;ServiceLifetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scoped&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, it could be like this:&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;AddMarten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storeOptions&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;storeOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Projections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TripProjectionWithCustomName&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;ProjectionLifecycle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Async&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;This lambda runs at container build time, not inside a request scope, so there is no direct access to the DI container. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Workaround
&lt;/h2&gt;

&lt;p&gt;The possible way to solve it is to inject the &lt;code&gt;ServiceProvider&lt;/code&gt; to use it as a Service Locator and get the service directly from it. &lt;em&gt;It's not good practice, though.&lt;/em&gt;&lt;br&gt;
I was lucky to find this &lt;a href="https://github.com/JasperFx/marten/discussions/2329" rel="noopener noreferrer"&gt;GitHub Issue&lt;/a&gt;, which gave me a clue how to proceed with it. &lt;br&gt;
There is an overload of &lt;code&gt;AddMarten&lt;/code&gt; that injects the &lt;code&gt;serviceProvider&lt;/code&gt;.&lt;br&gt;
Here is what you need: create an instance of your projection, pass &lt;code&gt;IServiceProvider&lt;/code&gt; or any other service as a parameter, and add this instance to the Marten with &lt;code&gt;Projections.Add&lt;/code&gt;. To be able to get the &lt;code&gt;ServiceProvider&lt;/code&gt; - return the &lt;code&gt;StoreOptions&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;AddMarten&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;=&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;options&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;StoreOptions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="n"&gt;MyProjection&lt;/span&gt; &lt;span class="n"&gt;mytProjection&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;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;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MytProjection&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;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;IServiceProvider&lt;/span&gt;&lt;span class="p"&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="n"&gt;Projections&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;mytProjection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ProjectionLifecycle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Async&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;options&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;When you return options, you’re handing your fully configured &lt;code&gt;StoreOptions&lt;/code&gt; back to Marten so it can build the document store. Marten calls this lambda after the service container is created, so the &lt;code&gt;serviceProvider&lt;/code&gt; argument is already a fully built root container.&lt;/p&gt;




&lt;p&gt;Hope this article was helpful and saved you some time!&lt;/p&gt;

&lt;p&gt;Have you ever worked with Event Sourcing and the Marten framework in particular? How do you like this experience?&lt;/p&gt;

</description>
      <category>marten</category>
      <category>eventsourcing</category>
      <category>dotnet</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Auto-generated IDs VS Manually created: which approach to chose?</title>
      <dc:creator>Julia Shevchenko</dc:creator>
      <pubDate>Mon, 22 Sep 2025 07:19:21 +0000</pubDate>
      <link>https://dev.to/juliashevchenko/auto-generated-ids-vs-manually-created-which-approach-to-chose-5b0g</link>
      <guid>https://dev.to/juliashevchenko/auto-generated-ids-vs-manually-created-which-approach-to-chose-5b0g</guid>
      <description>&lt;p&gt;Creating incremented auto-generated integer IDs on a database level was a default approach for a long time. It was (&lt;em&gt;is?&lt;/em&gt;) very popular for a reason.&lt;/p&gt;

&lt;p&gt;Advantages of generated IDs includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No need to manage IDs manually in code + database guarantees uniqueness; &lt;/li&gt;
&lt;li&gt;Integers perform fast DB-level for joins, comparisons, indexing, and take less storage size;&lt;/li&gt;
&lt;li&gt;Natural Ordering - Sequential IDs help clustered indexes, reduce fragmentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, with modern software architecture evolving, this approach may not be the best option. &lt;/p&gt;

&lt;p&gt;Let's compare this approach and the approach with manually creating IDs by example. It is simplified but reflects a common business case without diving into details. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article shows examples in C# and EF Core, but the same approach can be used for any language and ORM that implements the Unit of Work pattern.&lt;/em&gt;  &lt;/p&gt;

&lt;h2&gt;
  
  
  Example case description
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv36r9z2v6vrqxjtyzoby.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv36r9z2v6vrqxjtyzoby.png" alt="OrderService, PaymentService and ShippingService" width="583" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's imagine there are three independent microservices or modules: &lt;code&gt;OrderService&lt;/code&gt;, &lt;code&gt;PaymentService&lt;/code&gt;, and &lt;code&gt;ShippingService&lt;/code&gt;. Each module has its own database. &lt;/p&gt;

&lt;p&gt;Order service saves an &lt;code&gt;Order&lt;/code&gt; with all its information, including OrderItems. Payment service is not interested in Order Details, it tracks if the &lt;code&gt;Order&lt;/code&gt; was paid or not. Shipping service wants to have its own Order, but it should only contain shipment information. &lt;/p&gt;

&lt;p&gt;Orders should be easily synced between services, so that the most recent information about the order is available in whichever service and it is easy to get information about the order from other services if needed. Payment Service has &lt;code&gt;OrderId&lt;/code&gt; and it should be the same for &lt;code&gt;Orders&lt;/code&gt; in all services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using auto-generated IDs
&lt;/h2&gt;

&lt;p&gt;The following code shows what it looks like with the IDs generated on the DB level. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: I'm advocating for initializing domain entities through constructors, but for the articles, it is more readable to do it with object initializers.&lt;/em&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;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;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateOrderCommand&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;ct&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;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;TotalPrice&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;TotalPrice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;OrderItems&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;Items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&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="n"&gt;OrderItem&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="n"&gt;i&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="n"&gt;Quantity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&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="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;OrderedAt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&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;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;_dbContext&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;ct&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;paymentEvent&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;OrderCreatedForPayment&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;TotalPrice&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;TotalPrice&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;shippingEvent&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;OrderCreatedForShipping&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;_messageBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PublishAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paymentEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&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;_messageBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PublishAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shippingEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&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;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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example looks not that bad, however, it has potential issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Order cannot be saved in the same transaction with events to apply Outbox pattern because Order Id will be available only after &lt;code&gt;SaveChangesAsync()&lt;/code&gt; is called;&lt;/li&gt;
&lt;li&gt;There is no guarantee that Order in other services, Shipping Service in particular, will be created with the same ID. And this point is crucial;&lt;/li&gt;
&lt;li&gt;Entities rely on the infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same object should be identified the same way across different services or modules. Of course, it is possible to solve this by adding a separate column OrderId along with the database ID. But it adds complexity, makes it harder to maintain and may introduce errors if identifiers are misused.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using manually created IDs (Guid)
&lt;/h2&gt;

&lt;p&gt;Let's see how we can improve this case by manually creating IDs. &lt;br&gt;
In C# the common type for IDs will be &lt;code&gt;Guid&lt;/code&gt;. In SQL, it will be translated into type &lt;a href="https://en.wikipedia.org/wiki/Universally_unique_identifier" rel="noopener noreferrer"&gt;UUID&lt;/a&gt; v4. &lt;u&gt;Important&lt;/u&gt;: to generate a new random &lt;code&gt;Giud&lt;/code&gt;, you need to call a static &lt;code&gt;Guid.NewGuid()&lt;/code&gt;, not create a class 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="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;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateOrderCommand&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;ct&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;orderId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&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;Id&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;TotalPrice&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;TotalPrice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;OrderItems&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;Items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&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="n"&gt;OrderItem&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="n"&gt;i&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="n"&gt;Quantity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&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="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;OrderedAt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&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;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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;paymentEvent&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;OrderCreatedForPayment&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;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;TotalPrice&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;TotalPrice&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;shippingEvent&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;OrderCreatedForShipping&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;orderId&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;OutboxMessages&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;OutboxMessage&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;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;Type&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;OrderCreatedForPayment&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Payload&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paymentEvent&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;OccurredOnUtc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&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;OutboxMessages&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;OutboxMessage&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;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;Type&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;OrderCreatedForShipping&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Payload&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shippingEvent&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;OccurredOnUtc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&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;_dbContext&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;ct&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;orderId&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;Advantages of this approach are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Order is created in the same transaction as corresponding events ensuring data integrity;&lt;/li&gt;
&lt;li&gt;Only single DB connection was opened for this piece of code;&lt;/li&gt;
&lt;li&gt;Entity is untied from the Infrastructure;&lt;/li&gt;
&lt;li&gt;Order will be created with the same identifier across all microservices, modules or, even, systems. This means data can be easily synced or merged, if needed.&lt;/li&gt;
&lt;li&gt;We are fully in charge of how the identifier is generated. We could use any of the UUIDs or even compose it with some additional information. For example, if we want natural ordering and timestamp-based identifier, we may use &lt;a href="https://uuid7.com/" rel="noopener noreferrer"&gt;UUIDv7&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;It is easier to write tests, because there is no need to save the entity to access its ID. This may sound not important, however it's crusial that the code is easily testable which leads to better tests quality and therefore less problems in the future.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Note: for this implementation, you will also need background service to read OutboxMessages and publish them to the message broker.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  EF Core Configuration
&lt;/h2&gt;

&lt;p&gt;By default, &lt;a href="https://learn.microsoft.com/en-us/ef/core/modeling/keys?tabs=data-annotations#value-generation" rel="noopener noreferrer"&gt;EF Core generates values&lt;/a&gt; for the defined identifiers depending on its type - integers or GUID. &lt;br&gt;
Here is how to configure EF Core to not generate IDs using Fluent API. This should be a part of DbContext:&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;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnModelCreating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&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="n"&gt;modelBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Order&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;Property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&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="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="nf"&gt;ValueGeneratedNever&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;h2&gt;
  
  
  What about sequences?
&lt;/h2&gt;

&lt;p&gt;There is a workaround on how to get next value for autogenerated IDs. You can say ORM, in our case EF Core, to use sequence as the default ID generator for an entity.&lt;/p&gt;

&lt;p&gt;A sequence is a database object that generates a sequence of numbers (1, 2, 3, …) independent of any table. When you call it to get the next value, it reserves the value so it could not be used in another transaction. &lt;br&gt;
The next value in code can be with a raw SQL call (which is not very elegant).&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;nextId&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;context&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="n"&gt;SqlQueryRaw&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT NEXT VALUE FOR MySequence"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Could it solve all our concerns? Not really.&lt;/p&gt;

&lt;p&gt;Imagine that there is a need to create new Service that also produces Order, for example, Subscription service. Each service has its own database. It means that there is a very high chance to have Orders with same IDs in different services. &lt;/p&gt;

&lt;p&gt;Yes, this may never happen, but business requirements change rapidly and are not always predictable.&lt;/p&gt;

&lt;p&gt;Remember, &lt;strong&gt;Entity should be unique across the whole system&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Shifting responsibility to the database to manage ID values leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No ability to provide data consistency between services or modules;&lt;/li&gt;
&lt;li&gt;Concurrency issues when two or more threads try to create a row with the same id;&lt;/li&gt;
&lt;li&gt;Difficulties with synchronizing database data;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are crucial for microservice or event-driven architectures and could be solved by utilizing manual IDs creation.&lt;/p&gt;

&lt;p&gt;It is worth mentioning that integer IDs are claimed to be faster and take less disc space. However, nowadays databases are stored in the cloud and have significantly more performance capabilities than before. For massive tables, smaller indexes may help, but for normal-sized cloud apps, the difference is often negligible. Additionally, there are UUIDs which are timestamp based, so could cover natural ordering needs. &lt;/p&gt;

&lt;p&gt;Of course, every decision is a trade-off. The manual creation of IDs is not the silver bullet. There is a chance of collision (which is nearly zero), they are less readable and take more space than integer values. &lt;br&gt;
Every project is special and there is no single right way to build it. &lt;/p&gt;

&lt;p&gt;However, for most modern applications, especially those with an emphasis on domain logic, it is a great approach, which will save you a lot of time and prevent unexpected errors. &lt;/p&gt;

&lt;p&gt;Thank you for reading!&lt;/p&gt;




&lt;p&gt;Feel free to share in the comments, which approach do you prefer and why?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>dotnet</category>
      <category>tutorial</category>
      <category>microservices</category>
    </item>
    <item>
      <title>TODO or not TODO / Stop spamming the code with useless comments</title>
      <dc:creator>Julia Shevchenko</dc:creator>
      <pubDate>Mon, 25 Aug 2025 16:06:50 +0000</pubDate>
      <link>https://dev.to/juliashevchenko/todo-or-not-todo-stop-spamming-the-code-with-useless-comments-4lo4</link>
      <guid>https://dev.to/juliashevchenko/todo-or-not-todo-stop-spamming-the-code-with-useless-comments-4lo4</guid>
      <description>&lt;p&gt;It has been proven that the best way to solve a complex task is to divide it into smaller tasks. Some people create a to-do list in a notebook, some draw a diagram, others prefer to keep the plan in their minds. And, of course, many of the engineers use TODO comments in their code, not to forget something.&lt;/p&gt;

&lt;p&gt;What may go wrong? One &lt;code&gt;//TODO:&lt;/code&gt; here and one &lt;code&gt;//TODO:&lt;/code&gt; there. But as a result - no one ever takes care of those comments. Everyone is afraid of touching them: what if it is something important? There is always an endless list of tasks in the task manager, and there is no task to treat the abandoned TODOs. If you haven't observed something like that, are you sure? (Lucky you!)&lt;/p&gt;

&lt;p&gt;"&lt;em&gt;What is she talking about?&lt;/em&gt;" - you may think, as these types of comments are extremely popular and an absolutely normal thing.&lt;/p&gt;

&lt;p&gt;I will share some examples with you.&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="c1"&gt;// TODO: log result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will take 15 seconds to write a log.&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="c1"&gt;// TODO: add test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will take 5-10 minutes to write a test.&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="c1"&gt;// TODO: verify result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is your responsibility to verify if it is working, and it also does not take that much time. Do it now, not later (which means never).&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="c1"&gt;//TODO: fix&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You have found a bug and just left the comment, so no one has an idea that something is broken? Please, go and create a ticket with the problem description. If the problem is not on the board, it doesn't exist.&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="c1"&gt;//TODO:  check later&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check what? When? By whom? &lt;strong&gt;Why?&lt;/strong&gt; Why not handle it now when you're in the context? &lt;/p&gt;

&lt;p&gt;By leaving such comments, you &lt;strong&gt;shift responsibility&lt;/strong&gt; from you to another person who will find this comment. Besides, you provide absolutely no context. &lt;/p&gt;

&lt;h3&gt;
  
  
  How to use TODOs properly?
&lt;/h3&gt;

&lt;p&gt;If you want to remind yourself about something you don't want to forget, or you are waiting for some details &lt;strong&gt;while working on the task&lt;/strong&gt;, it is completely fine to use the TODO comments. It's even a good idea, because human memory is not very reliable. Just make sure to &lt;strong&gt;take care of these comments before you merge&lt;/strong&gt;. It should be living within the scope of the current piece of work.&lt;/p&gt;

&lt;p&gt;If it's a 5-minutes-job, just do it. If it's a bug, create a ticket. Need some additional information, ask for it. But &lt;strong&gt;do not postpone a problem for the future&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A hack from me: write preconized todos, like todo-julia, and create a filter for the IDE to show your specific todos. It helps to filter your comments from others. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Another scenario is when you want to warn your teammates or your future self about something &lt;strong&gt;critical and unobvious&lt;/strong&gt; that needs to be implemented on the next iteration of the task, and cannot be implemented now because of some circumstances. And you feel the need to have it not only in the ticket, but also in the code to be more specific about something tricky. If the comment is meaningful and concise, it is fine. But it should not last forever. The purpose of the comment should be to help, to guide, not to confuse. &lt;/p&gt;

&lt;p&gt;TODO or not TODO - is your personal choice. A perfect code base does not exist, but you can contribute to making it cleaner. &lt;/p&gt;

</description>
      <category>programming</category>
      <category>cleancode</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Health Check setup for .NET application</title>
      <dc:creator>Julia Shevchenko</dc:creator>
      <pubDate>Fri, 20 Jun 2025 11:42:38 +0000</pubDate>
      <link>https://dev.to/juliashevchenko/health-check-setup-for-net-application-14h7</link>
      <guid>https://dev.to/juliashevchenko/health-check-setup-for-net-application-14h7</guid>
      <description>&lt;h2&gt;
  
  
  What is a Health Check?
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Health Check&lt;/strong&gt; is an indicator of service availability. For web applications, it's an endpoint that returns the current state of the application - usually, it could be &lt;code&gt;Healthy&lt;/code&gt;, &lt;code&gt;Degraded&lt;/code&gt;, or &lt;code&gt;Unhealthy&lt;/code&gt;. Health checks can be configured for any type of service, such as a database or a message broker, or can be set to track any crucial part of the application.&lt;br&gt;
It is frequently used in CD pipelines to observe if the service has any issues and restart it if needed. &lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Basic Health Check configuration
&lt;/h2&gt;

&lt;p&gt;To add a basic health check, you need to do two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Register a health check in the DI: &lt;code&gt;builder.Services.AddHealthChecks();&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add a middleware and set a path to the health check endpoint: &lt;code&gt;app.UseHealthChecks("/health")&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it - if your application is able to start, then &lt;code&gt;/health&lt;/code&gt; endpoint will return 200 OK and a plain text response with the string "Healthy". Everything else is considered Unhealthy. &lt;br&gt;
However, with this basic configuration, &lt;em&gt;it won't track any major problems&lt;/em&gt;. For example, if the database becomes unavailable, the health check will still indicate a Healthy state. &lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Tuning Health Checks
&lt;/h2&gt;

&lt;p&gt;To define custom logic for health check - just implement &lt;code&gt;IHealthCheck&lt;/code&gt; interface and then register the implemented health check like this:&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;AddHealthChecks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="err"&gt;   &lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddCheck&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CustomeHealthCheck&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sample"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, there are plenty of ready-to-use &lt;a href="https://www.nuget.org/packages?q=AspNetCore.HealthChecks&amp;amp;frameworks=net&amp;amp;includeComputedFrameworks=true&amp;amp;frameworkFilterMode=all&amp;amp;prerel=true&amp;amp;sortby=relevance" rel="noopener noreferrer"&gt;extensions&lt;/a&gt; to cover most of your needs - database management systems, message brokers, check for system info like disk storage, network status, and even a ui tool to observe all this information. &lt;/p&gt;

&lt;p&gt;The example usage:&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;AddHealthChecks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSqlServer&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="s"&gt;"your-connection-string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;healthQuery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"SELECT 1;"&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;"sqlserver"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;failureStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HealthStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unhealthy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;h2&gt;
  
  
  Practical usage
&lt;/h2&gt;

&lt;p&gt;Having a health check but not tracking its status doesn't make sense.&lt;br&gt;
The possible options for it are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI dashboard, which will be accessible by some url inside your application&lt;/li&gt;
&lt;li&gt;Kubernetes / Docker Health Probes, which allow to configure automatic recovery&lt;/li&gt;
&lt;li&gt;Add as a metric to Application Insights / Grafana / other tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adding a health check will help keep your service available and prevent you from missing any issues.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Thanks for reading, &lt;br&gt;
Wishing you successful deployments and enjoy your coding!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>webdev</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Parallel Queries in EF Core with PostgreSQL: Why It Fails and How to Fix It</title>
      <dc:creator>Julia Shevchenko</dc:creator>
      <pubDate>Tue, 15 Apr 2025 17:19:43 +0000</pubDate>
      <link>https://dev.to/juliashevchenko/parallel-queries-in-ef-core-with-postgresql-why-it-fails-and-how-to-fix-it-4648</link>
      <guid>https://dev.to/juliashevchenko/parallel-queries-in-ef-core-with-postgresql-why-it-fails-and-how-to-fix-it-4648</guid>
      <description>&lt;p&gt;You may have faced an interesting error trying to run queries in parallel with &lt;strong&gt;EF Core&lt;/strong&gt; and &lt;strong&gt;Postgres&lt;/strong&gt;: "&lt;em&gt;A command is already in progress&lt;/em&gt;" which isn't very specific but refers to parallel execution failure. &lt;/p&gt;

&lt;p&gt;This piece of code represents an intention of parallel query execution:&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;city&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;Cities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteDeleteAsync&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;forecasts&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;WeatherForecasts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteDeleteAsync&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;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhenAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;forecasts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seems that it should work, so &lt;strong&gt;what's wrong?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's get a little bit into the details of what's going on here. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;ExecuteDeleteAsync()&lt;/code&gt; runs the SQL query in DB immediately, so there is no need to call &lt;code&gt;SaveChangesAsync()&lt;/code&gt;. Also, EF Core doesn't track any changes as entities are not loaded into memory.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Task.WhenAll()&lt;/code&gt; allows tasks to be awaited concurrently. When dealing with I/O-bound operations, such as database interactions, they are expected to be parallelized, as in our case. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Please note that city and forecast entities are not connected with a foreign key.&lt;/em&gt; It looks like they should be, however, for testing purposes, I intentionally avoid that. If they are connected with a foreign key and have a CASCADE restriction, then this call will work because forecasts will be deleted along with the cities. &lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  What have we missed?
&lt;/h3&gt;

&lt;p&gt;It turned out that &lt;strong&gt;Postgres doesn't support parallel query execution&lt;/strong&gt; within the same connection, &lt;strong&gt;unlike the SQL Server&lt;/strong&gt;. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡&lt;em&gt;Tip:&lt;/em&gt; The support of parallel query execution in the SQL Server &lt;strong&gt;is turned off by default&lt;/strong&gt;, but it's possible to enable it with &lt;code&gt;MultipleActiveResultSets=True&lt;/code&gt;.&lt;br&gt;
But be careful and consider the potential risks. Ask yourself, are you sure your project needs it? &lt;br&gt;
With such configuration, the code above will work. &lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Is there anything you could do?
&lt;/h3&gt;

&lt;p&gt;Yes, you can run queries with separate db contexts, meaning queries will run on different connections. However, a more general approach is to run queries sequentially.&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteDeleteAsync&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;WeatherForecasts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteDeleteAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;h3&gt;
  
  
  Should you get upset because of it?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Definitely not.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;❌ Parallel query execution has underlying concurrency limitations, which could lead to deadlocks or timeouts&lt;br&gt;
✔️ Queries from separate connections will be executed in parallel at the I/O level, and the DB runs them in parallel, too.&lt;/p&gt;

</description>
      <category>efcore</category>
      <category>dotnet</category>
      <category>postgres</category>
      <category>sqlserver</category>
    </item>
    <item>
      <title>How to Parse Environment Variables into DTO in .NET: AWS S3 Bucket Example</title>
      <dc:creator>Julia Shevchenko</dc:creator>
      <pubDate>Wed, 02 Apr 2025 09:23:26 +0000</pubDate>
      <link>https://dev.to/juliashevchenko/how-to-parse-environment-variables-into-dto-in-net-2m8a</link>
      <guid>https://dev.to/juliashevchenko/how-to-parse-environment-variables-into-dto-in-net-2m8a</guid>
      <description>&lt;p&gt;Working with objects is always better than with "magical" strings. Having multiple configuration strings for some services can be uncomfortable to work with. Also, it increases the number of parameters to pass to a service.  If you add one more parameter to the configuration section, you will need to manually retrieve it from the configuration and pass it to the service, where it will be used. &lt;/p&gt;

&lt;p&gt;Treating configuration settings as a DTO class will make your code more readable. &lt;/p&gt;

&lt;h2&gt;
  
  
  Parse config into DTO class
&lt;/h2&gt;

&lt;p&gt;As an example, I have a configuration for an &lt;strong&gt;AWS S3 bucket&lt;/strong&gt; in &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="nl"&gt;"AwsS3"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"AccessKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-access-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"SecretKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-secret-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"BucketName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-bucket-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's create a DTO class for it:&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;AwsS3Settings&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;AccessKey&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;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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&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;SecretKey&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;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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&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;BucketName&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;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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&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;Region&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;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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&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;Now, it's time to configure the dependency injection container to store our configuration. This code reads the configuration section, maps it to the &lt;code&gt;AwsS3Settings&lt;/code&gt; class, wraps it with &lt;code&gt;IOptions&amp;lt;T&amp;gt;&lt;/code&gt;, and adds it to the container as an &lt;code&gt;IOptions&amp;lt;AwsS3Settings&amp;gt;&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="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nf"&gt;AwsS3Settings&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;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AwsS3"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It means now it is possible to inject it in some service like this:&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;AwsS3Saver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AwsS3Settings&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to inject your configuration DTO directly, without &lt;code&gt;IOptions&lt;/code&gt; cover, you can register it as a Singleton in the container.&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;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sp&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;IOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AwsS3Settings&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;().&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, you will be able to inject the DTO directly to any service:&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;AwsS3Saver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AwsS3Settings&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Parse config into Record
&lt;/h2&gt;

&lt;p&gt;Usually, such data is treated using a record, not a plain class. However, the record cannot be used to map configurations into it in the same way. If you try, you will get an exception: &lt;code&gt;System.MissingMethodException: Cannot dynamically create an instance of type 'EnvToDtoDemo.Settings.AwsS3SettingsRecord'. Reason: No parameterless constructor defined.&lt;/code&gt;&lt;br&gt;
The reason behind that is in &lt;code&gt;IOptions&amp;lt;T&amp;gt;&lt;/code&gt;, which requires parameterless constructor and &lt;code&gt;set;&lt;/code&gt; for a public properties. &lt;br&gt;
Records use constructors with parameters and use them to initialize properties, so they can be set only during initialization, which makes them immutable. &lt;/p&gt;

&lt;p&gt;However, you still can use records for parsing environment variables, but with a slightly different approach. Use &lt;code&gt;Get&lt;/code&gt; extension method to map configuration to a strongly typed object, which is our &lt;code&gt;AwsS3SettingsRecord&lt;/code&gt;. After that, add it to the container.&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;record&lt;/span&gt; &lt;span class="nc"&gt;AwsS3SettingsRecord&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;AccessKey&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;SecretKey&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;BucketName&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;Region&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;awsS3SettingsRecord&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;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AwsS3"&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;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AwsS3SettingsRecord&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;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;"AwsS3 settings are missing!"&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;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;awsS3SettingsRecord&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Now you can inject the record with configuration where needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🖖 Enjoy your coding!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>cleancode</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to make an API call in the middle of an OpenAI conversation</title>
      <dc:creator>Julia Shevchenko</dc:creator>
      <pubDate>Thu, 27 Feb 2025 14:47:45 +0000</pubDate>
      <link>https://dev.to/juliashevchenko/how-to-make-an-api-call-in-the-middle-of-an-openai-conversation-12ab</link>
      <guid>https://dev.to/juliashevchenko/how-to-make-an-api-call-in-the-middle-of-an-openai-conversation-12ab</guid>
      <description>&lt;p&gt;While AI integrations become more and more desirable features, the need to perform additional functionality, for example an API call, in the middle of the conversations is the biggest concern for developers. Even though it is possible to provide AI assistants with clear instructions, they sometimes struggle to follow them. The most problematic are if/else conditions. &lt;/p&gt;

&lt;p&gt;For demonstration purposes, I've created a console application that will simulate a chat with OpenAI via &lt;a href="https://platform.openai.com/docs/guides/text-generation#text-generation-models" rel="noopener noreferrer"&gt;completion API&lt;/a&gt;. So, everything is put inside the background Worker class. We will use &lt;a href="https://www.nuget.org/packages/Azure.AI.OpenAI" rel="noopener noreferrer"&gt;Azure.AI.OpenAI&lt;/a&gt; package to interact with the API. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 0. The basics
&lt;/h2&gt;

&lt;p&gt;Completions API doesn't store the state, so we need to pass the chat history with every call. Chat history should consist of &lt;code&gt;ChatMessage&lt;/code&gt; type. &lt;br&gt;
There are several chat &lt;code&gt;ChatMessage&lt;/code&gt; types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SystemChatMessage&lt;/code&gt; (system) - use to provide system instructions that will be considered by OpenAI but won't be visible to the user;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UserChatMessage&lt;/code&gt; (user) - contains messages from the user;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AssistantChatMessage&lt;/code&gt; (assistant) - contains messages from OpenAI;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ToolChatMessage&lt;/code&gt; (tool) - use to provide result of the tool (in our case function) call.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;ChatTool&lt;/em&gt; - is a concept of additional chat functionality, in our case, a function. So, every function is a ChatTool.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1.  Starting a conversation
&lt;/h2&gt;

&lt;p&gt;First of all, we need to have system instructions to describe OpenAI's role in this conversation. Here, we can also tell OpenAI that we want to execute some function after specific user actions.&lt;/p&gt;

&lt;p&gt;In this example, I want it to be an assistant in a bookstore, and when the user picks up a book - execute a function that will call our API to get book information, check if there are any deals, and tell the user if there are any. &lt;/p&gt;

&lt;p&gt;So, when the user says, &lt;em&gt;"I want to buy this book"&lt;/em&gt;, we will make a call to our bookstore API, check if there are any, and send &lt;code&gt;SystemChatMessage&lt;/code&gt; with new instructions. &lt;strong&gt;Doing the deal check on our backend will also help to avoid OpenAI misbehavior with if/else conditions.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inside such function calls, we can do whatever we need: call an API, do some calculations, send emails, etc.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the diagram, I show the part of the simplified flow from the user message that indicates a desire to buy a book to the new instruction for OpenAI. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm66ro11vbtg66bwm4j5i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm66ro11vbtg66bwm4j5i.png" alt="Simplified flow diagram" width="638" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's now move on to the implementation.&lt;/p&gt;

&lt;p&gt;We will store message history in the &lt;code&gt;messagesHistory&lt;/code&gt; property of type &lt;code&gt;List&amp;lt;ChatMessage&amp;gt;&lt;/code&gt;. &lt;br&gt;
ExecuteAsync is an entry point for our background worker. So, we are waiting for the user to type anything, then save the message and continue our conversation.&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;protected&lt;/span&gt; &lt;span class="k"&gt;override&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;ExecuteAsync&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;stoppingToken&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;systemInstructions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
        &lt;span class="s"&gt;$"""
&lt;/span&gt;            &lt;span class="n"&gt;You&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="n"&gt;an&lt;/span&gt; &lt;span class="n"&gt;assistant&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;bookstore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Ask&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="n"&gt;about&lt;/span&gt; &lt;span class="n"&gt;their&lt;/span&gt; &lt;span class="n"&gt;interests&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;genres&lt;/span&gt; &lt;span class="n"&gt;they&lt;/span&gt; &lt;span class="n"&gt;like&lt;/span&gt; 
            &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="n"&gt;what&lt;/span&gt; &lt;span class="n"&gt;they&lt;/span&gt; &lt;span class="n"&gt;would&lt;/span&gt; &lt;span class="n"&gt;like&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;read&lt;/span&gt; &lt;span class="n"&gt;about&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;based&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="n"&gt;suggest&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="n"&gt;After&lt;/span&gt; &lt;span class="n"&gt;suggestion&lt;/span&gt; &lt;span class="n"&gt;ask&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="n"&gt;wants&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;buy&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;If&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="n"&gt;wants&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;buy&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;FindBookFunctionName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
        &lt;span class="s"&gt;""";
&lt;/span&gt;
    &lt;span class="n"&gt;_messagesHistory&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="nf"&gt;SystemChatMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;systemInstructions&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;"Assistant: Hello! How can I help?"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;stoppingToken&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;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;question&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;ReadLine&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="n"&gt;_messagesHistory&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="nf"&gt;UserChatMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ContinueConversation&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;Calling a function without any context would be useless. We need to have information about the book that user has chosen: name and author. For this, we need to define which fields we are expecting to receive from OpenAI. It can be done as the following:&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;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ChatTool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetFunctionsDefinitions&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="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;ChatTool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateFunctionTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FindBookFunctionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;functionDescription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Returns a list of books suggested to a user."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;functionParameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BinaryData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&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;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;properties&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;name&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;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"string"&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;"Book name"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="n"&gt;author&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;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"string"&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;"Book author"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="n"&gt;additionalProperties&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;required&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="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"author"&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;h2&gt;
  
  
  Step 2. Continue Conversation
&lt;/h2&gt;

&lt;p&gt;To continue conversation, we will always send message history to the OpenAI API including functions definition if there are any.&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;private&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;ChatCompletion&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;SendMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;messages&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;chatClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openAiClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetChatClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"gpt-4o-mini"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;ChatCompletionOptions&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="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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;GetFunctionsDefinitions&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="n"&gt;Tools&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;tool&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;chatClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CompleteChatAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response will always be a &lt;code&gt;ChatCompletion&lt;/code&gt; object with &lt;code&gt;FinishReason&lt;/code&gt; which could be one of the following: &lt;code&gt;Stop&lt;/code&gt;, &lt;code&gt;ToolCalls&lt;/code&gt;, &lt;code&gt;Length&lt;/code&gt;, &lt;code&gt;ContentFilter&lt;/code&gt; and &lt;code&gt;FunctionCall&lt;/code&gt;.&lt;br&gt;
We are interested in the first two. For simplification, others are omitted in this example.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Stop&lt;/code&gt; indicates that there is an ordinary response from OpenAI with some text. We need to store it as an &lt;code&gt;AssistantChatMessage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ToolCalls&lt;/code&gt; means that OpenAI identified that some Tool is required to be executed. &lt;strong&gt;It is our responsibility to execute it&lt;/strong&gt;. The text from the response should be also stored in &lt;code&gt;AssistantChatMessage&lt;/code&gt;. And, in addition, each tool call result should be saved as &lt;code&gt;ToolChatMessage&lt;/code&gt;. After &lt;code&gt;ToolChatMessage&lt;/code&gt; and &lt;code&gt;SystemChatMessage&lt;/code&gt; are sent, we need to &lt;code&gt;ContinueConversation&lt;/code&gt;, so OpenAI can react to new prompts. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this example, we will execute tool calls in &lt;code&gt;ExecuteToolCall&lt;/code&gt; method.&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;private&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;ContinueConversation&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;chatCompletion&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;SendMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_messagesHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatCompletion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FinishReason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;ChatFinishReason&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;_messagesHistory&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="nf"&gt;AssistantChatMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatCompletion&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="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Text&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;chatCompletion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Role&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;chatCompletion&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="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Text&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;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;ChatFinishReason&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolCalls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_messagesHistory&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="nf"&gt;AssistantChatMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatCompletion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolCalls&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;toolCall&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chatCompletion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolCalls&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;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ExecuteToolCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toolCall&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;_messagesHistory&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="nf"&gt;ToolChatMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toolCall&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;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&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="k"&gt;if&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="n"&gt;SystemMessage&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&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="n"&gt;_messagesHistory&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="nf"&gt;SystemChatMessage&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="n"&gt;SystemMessage&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="nf"&gt;ContinueConversation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentOutOfRangeException&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;h2&gt;
  
  
  Step 3. Execute Tool Call
&lt;/h2&gt;

&lt;p&gt;In ExecuteToolCall we will define how to execute each function that we may receive. In this case, it's only one, but could be any amounts.&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;private&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;FunctionResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ExecuteToolCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChatToolCall&lt;/span&gt; &lt;span class="n"&gt;toolCall&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;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;toolCall&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FunctionName&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;FindBookFunctionName&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;GetBookDetails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toolCall&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="c1"&gt;//other functions calls&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;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NotImplementedException&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;response&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;Our goal here is to call our bookstore backend API to get information about the book that user wants to buy: is it available and does it have any deals that user should be notified about. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4. Make an API call
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;GetBookDetails&lt;/code&gt; is the method where we can perform anything we need to extend conversation logic. It is the actual implementation of the &lt;code&gt;FindBookFunctionName&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Since &lt;code&gt;toolCall.FunctionArguments&lt;/code&gt; is binary, we can convert it into string and deserialize it to the &lt;code&gt;BookRecommendation&lt;/code&gt; DTO. &lt;br&gt;
Then, it's time to call bookstore API to get book information. After that, depending on the &lt;code&gt;Deal&lt;/code&gt; type, add a system message.&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;private&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;FunctionResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetBookDetails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChatToolCall&lt;/span&gt; &lt;span class="n"&gt;toolCall&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;bookRecommendation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BookRecommendation&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;toolCall&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FunctionArguments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&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;bookRecommendation&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;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Cannot parse the book recommendation."&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;bookDetail&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;bookApiClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bookRecommendation&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="n"&gt;systemMessage&lt;/span&gt; &lt;span class="p"&gt;=&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bookDetail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deals&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;Deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnePlusOne&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;systemMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"If you bye one more book, you get another one for free!"&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;bookDetail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deals&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;Deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ThirtyPercentOff&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;systemMessage&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;"Lucky time! This book is 30% off! To apply the discount, use the code: LUCKY30"&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;bookDetail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deals&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;Deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FreeShippingIfOverFifty&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;systemMessage&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;"If you buy books for more than 50$, you get free shipping!"&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="nf"&gt;FunctionResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bookDetail&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;systemMessage&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;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;record&lt;/span&gt; &lt;span class="nc"&gt;BookRecommendation&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;Author&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;record&lt;/span&gt; &lt;span class="nc"&gt;BookDetail&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;Author&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;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Deal&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Deals&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;enum&lt;/span&gt; &lt;span class="n"&gt;Deal&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;OnePlusOne&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ThirtyPercentOff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FreeShippingIfOverFifty&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;record&lt;/span&gt; &lt;span class="nc"&gt;FunctionResponse&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;Content&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="n"&gt;SystemMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see in the second step, the system message will be sent to the OpenAI to be considered.&lt;/p&gt;

&lt;p&gt;Let's see how the conversation will look like.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fndpiqj22ahdz5025zy3v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fndpiqj22ahdz5025zy3v.png" alt="OpenAI Conversation" width="800" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the selected book, the deal is 1+1=2, meaning buy one more book - get another one for free. OpenAI understands the user's requests and, according to the system instructions, kindly informs the user about this possibility and even asks if he wants to use it. &lt;/p&gt;

&lt;p&gt;I think that's a very accurate example of how businesses can benefit from OpenAI integration, especially when it is enhanced by custom functions calls. &lt;/p&gt;

&lt;p&gt;Have you already tried OpenAI or other integrations on your projects? Do you like how it performs? &lt;/p&gt;

</description>
      <category>openai</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
    </item>
    <item>
      <title>Adding another database provider (for testing) is not working in EF Core 9</title>
      <dc:creator>Julia Shevchenko</dc:creator>
      <pubDate>Tue, 18 Feb 2025 14:38:50 +0000</pubDate>
      <link>https://dev.to/juliashevchenko/adding-another-database-provider-for-testing-is-not-working-in-ef-core-9-4gn8</link>
      <guid>https://dev.to/juliashevchenko/adding-another-database-provider-for-testing-is-not-working-in-ef-core-9-4gn8</guid>
      <description>&lt;p&gt;For writing integration tests, a common approach is to change the database provider, for example, to the InMemory database. But with Ef Core 9 this approach will give you an exception: "&lt;em&gt;Only a single database provider can be registered in a service provider&lt;/em&gt;". So, what to do if there is a need to change the database provider and even manually removing another provider doesn't help?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7q8sjivgj8a3ibmvhv7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7q8sjivgj8a3ibmvhv7.jpg" alt=" " width="780" height="107"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Searching for possible solutions, I've found this &lt;a href="https://github.com/dotnet/efcore/issues/35126" rel="noopener noreferrer"&gt;issue&lt;/a&gt; on the ef core repository. &lt;br&gt;
It turned out that a new &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.infrastructure.idbcontextoptionsconfiguration-1?view=efcore-9.0&amp;amp;viewFallbackFrom=efcore-6.0" rel="noopener noreferrer"&gt;IDbContextOptionsConfiguration&lt;/a&gt; interface was introduced, which, according to the documentation, configures the options to be used by a DbContext. And if it tracks multiple database providers, the exception will be thrown.&lt;/p&gt;

&lt;p&gt;A workaround that I found in this &lt;a href="https://www.devgem.io/posts/resolving-multiple-ef-core-database-providers-in-asp-net-core-integration-testing" rel="noopener noreferrer"&gt;article&lt;/a&gt; is to remove the configuration from the service provider. This solution works like a charm but feels a little bit hacky. Should we manually change the configuration? However, this may still save hours for those who faced it after updating the ef core. &lt;/p&gt;

&lt;p&gt;The current version of the documentation &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-9.0#customize-webapplicationfactory" rel="noopener noreferrer"&gt;here&lt;/a&gt; is not updated yet. However, the &lt;a href="https://github.com/dotnet/AspNetCore.Docs/issues/34584" rel="noopener noreferrer"&gt;issue&lt;/a&gt; is in progress, and I'm curious what the suggested solution will be. &lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>efcore</category>
      <category>csharp</category>
      <category>microsoft</category>
    </item>
  </channel>
</rss>
