<?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: Paulo Pozeti</title>
    <description>The latest articles on DEV Community by Paulo Pozeti (@paulopozeti).</description>
    <link>https://dev.to/paulopozeti</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%2F3810722%2F477954d5-988f-41f7-8798-7ad3fea3b4a0.png</url>
      <title>DEV Community: Paulo Pozeti</title>
      <link>https://dev.to/paulopozeti</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/paulopozeti"/>
    <language>en</language>
    <item>
      <title>Why you should abstract EF's DBContext</title>
      <dc:creator>Paulo Pozeti</dc:creator>
      <pubDate>Tue, 07 Apr 2026 09:49:19 +0000</pubDate>
      <link>https://dev.to/paulopozeti/why-you-should-abstract-efs-dbcontext-38e</link>
      <guid>https://dev.to/paulopozeti/why-you-should-abstract-efs-dbcontext-38e</guid>
      <description>&lt;p&gt;Every article, old or new, about Entity Framework tells you the same thing: DbContext is already a Unit of Work, DbSet is already a Repository, and if you abstract over them you're just creating abstraction over abstraction. I think they're answering the wrong question.&lt;/p&gt;

&lt;p&gt;Others say you should only do if the app is big or logic is complex. I think it doesn't matter the size of your organization or product! You should always abstract infrastructure.&lt;/p&gt;

&lt;p&gt;Let's see if I can convince you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current misconception
&lt;/h2&gt;

&lt;p&gt;If you search right now about EF DBContext Repository Pattern, you are likely to find the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://gunnarpeipman.com/ef-core-repository-unit-of-work/" rel="noopener noreferrer"&gt;Argues that DbContext is already a Unit of Work and DbSet is already a Repository. Adding another layer is redundant abstraction over abstraction.&lt;/a&gt; — Gunnar Peipman&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.thereformedprogrammer.net/is-the-repository-pattern-useful-with-entity-framework-core/" rel="noopener noreferrer"&gt;A thorough analysis concluding that the traditional repository pattern adds complexity without benefit&lt;/a&gt; — Jon P Smith (The Reformed Programmer)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://emonarafat.github.io/2025/04/21/the-repository-pattern-is-dead-ef-core-killed-it.html" rel="noopener noreferrer"&gt;The Repository Pattern is Dead. EF Core Killed It.&lt;/a&gt; — Yaseer Arafat&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://juliocasal.com/blog/the-repository-pattern-trap" rel="noopener noreferrer"&gt;Discusses how developers fall into the trap of wrapping DbContext, ending up with more code, less flexibility, and no
real benefit.&lt;/a&gt; — Julio Casal&lt;/li&gt;
&lt;li&gt;And the list goes on...&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When we summarize the arguments from most of those posts, most of them can fall into the following categories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;DbContext is the Unit of Work; DbSet is the Repository. Wrapping them is abstraction over abstraction.&lt;/li&gt;
&lt;li&gt;You lose EF IQueryable composition, eager/lazy loading, change tracking, raw SQL, bulk operations all get hidden or crippled.&lt;/li&gt;
&lt;li&gt;"Swap out the ORM" never happens. Too risky to swap ORMs in real projects.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I will talk about why those things aren't entirely true.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fallacy #1: Abstraction over abstraction
&lt;/h2&gt;

&lt;p&gt;The people making this argument are answering the wrong question. They're asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Should I abstract Entity Framework?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When the real question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Should my domain/application layer know how or where data is stored?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The Repository pattern was never about wrapping an ORM!
&lt;/h3&gt;

&lt;p&gt;The original &lt;a href="https://deviq.com/design-patterns/repository-pattern/" rel="noopener noreferrer"&gt;https://deviq.com/design-patterns/repository-pattern/&lt;/a&gt; is defined as a collection-like abstraction that hides persistence completely, lives in the domain layer as an interface, and speaks the language of the business. It predates ORMs and EF!&lt;/p&gt;

&lt;p&gt;Saying "DbContext is already a repository" combines two different things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A data access mechanism (what DbContext is)&lt;/li&gt;
&lt;li&gt;A domain boundary contract (what a Repository is supposed to be)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A DbContext speaks the language of SQL, change tracking, Include(), IQueryable, and migrations. A proper repository speaks the language of your domain: &lt;code&gt;GetActiveCustomersByRegion()&lt;/code&gt;, &lt;code&gt;FindOverdueInvoices()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  DbContext is Microsoft's abstraction, not yours
&lt;/h3&gt;

&lt;p&gt;Being too reliant on &lt;code&gt;DbContext&lt;/code&gt; and &lt;code&gt;DbSet&amp;lt;T&amp;gt;&lt;/code&gt; is giving too much power to the ORM library. They are owned by Microsoft, not your application!&lt;br&gt;
You don't control them. You don't own their interface. They can change across versions.&lt;/p&gt;

&lt;p&gt;Your domain contract (&lt;code&gt;IOrderRepository&lt;/code&gt;) is yours. You define it. You control what it exposes. You decide when it changes. This is the difference between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Coupling to a third-party API surface you don't control&lt;/li&gt;
&lt;li&gt;Depending on a contract you own that happens to be implemented by EF today&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So even though Microsoft tries their best in keeping contracts backwards compatible, we've seen breaking changes between EF versions, like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.x/breaking-changes" rel="noopener noreferrer"&gt;EF Core 3.0 breaking changes&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FromSql renamed to FromSqlRaw / FromSqlInterpolated&lt;/li&gt;
&lt;li&gt;DbQuery removed — replaced by keyless entity types&lt;/li&gt;
&lt;li&gt;Default string length changed in the backing model&lt;/li&gt;
&lt;li&gt;DbContext.Entry behavior changed for detached entities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-6.0/breaking-changes" rel="noopener noreferrer"&gt;EF Core 6.0 breaking changes&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DbSet no longer implements IAsyncEnumerable — code casting DbSet to IAsyncEnumerable broke&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do you still want to have to change your whole application whenever a contract changes?&lt;/p&gt;

&lt;h2&gt;
  
  
  Fallacy #2: Lose EF specific features
&lt;/h2&gt;

&lt;p&gt;This argument fundamentally misunderstands what abstraction means. Abstraction doesn't mean you stop using EF features. It means you stop exposing them.&lt;/p&gt;

&lt;p&gt;The repository implementation still uses every EF Core feature available. Include(), ThenInclude(), AsSplitQuery(), change tracking, ExecuteUpdate(), raw SQL, everything. The abstraction is the interface, not the implementation.&lt;/p&gt;

&lt;p&gt;The biggest "feature" people claim you lose is leaking IQueryable to consumers. But as &lt;a href="https://ardalis.com/avoid-dbcontext-iqueryable-proliferation/" rel="noopener noreferrer"&gt;https://ardalis.com/avoid-dbcontext-iqueryable-proliferation/&lt;/a&gt; mentioned, letting &lt;code&gt;IQueryable&lt;/code&gt; proliferate through your app is actively harmful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Any consumer can compose any arbitrary query — there's no control over what SQL hits your database&lt;/li&gt;
&lt;li&gt;Query logic gets scattered across controllers, services, and middleware instead of being centralized&lt;/li&gt;
&lt;li&gt;You can't optimize, cache, or audit queries you don't know exist&lt;/li&gt;
&lt;li&gt;You can't swap the data source for specific queries (cache, Dapper, API) because consumers are writing LINQ everywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Encapsulating IQueryable inside the repository means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All query logic is in one place: easier to optimize, profile, and review&lt;/li&gt;
&lt;li&gt;The domain gets exactly the data it needs: no over-fetching, no surprise N+1s&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't lose EF features by abstracting. You stop accidentally exposing EF's entire surface area as your application's data access contract. The implementation uses everything EF offers. The interface exposes only what the domain actually needs.&lt;br&gt;
That's not losing features. That's engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fallacy #3: You will never replace EF
&lt;/h2&gt;

&lt;p&gt;This is the argument I hear most, and it misses the point entirely.&lt;/p&gt;

&lt;p&gt;Even though I agree it is hard for business to move from, say MongoDB to SQL Server, it's not impossible.&lt;br&gt;
What is more likely to happen is moving from EF6 to EF Core. &lt;/p&gt;

&lt;p&gt;That happened and if you were too entangled with EF6 in your services, controllers, you probably struggled way more than necessary!&lt;/p&gt;

&lt;p&gt;"You will never replace EF" is an argument about probability. But abstraction isn't insurance against a single catastrophic event. It's a daily engineering benefit that makes your code testable, traceable, flexible query-by-query, and resilient to the breaking changes Microsoft ships with every major version.&lt;/p&gt;

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

&lt;p&gt;The debate was never about using Entity Framework's DBContext. It was about whether your business logic should know where its data comes from.&lt;/p&gt;

&lt;p&gt;A repository interface is a few lines of code. Behind it, you can use every feature EF offers, and when EF ships its next round of breaking changes, you fix the implementation, not the entire application.&lt;/p&gt;

&lt;p&gt;If you're working in a codebase where DbContext is threaded through controllers and services, you don't have to rewrite everything tomorrow. Start with one aggregate. Define the interface. Move the queries behind it. See how it feels. The cost is small, and once you've done it, you won't want to go back.&lt;/p&gt;

&lt;p&gt;I hope I've convinced you, but if I haven't, tell me why in the comments!&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>beginners</category>
      <category>entityframework</category>
      <category>designpatterns</category>
    </item>
    <item>
      <title>Testing is the most important piece in software engineering</title>
      <dc:creator>Paulo Pozeti</dc:creator>
      <pubDate>Sun, 29 Mar 2026 11:59:51 +0000</pubDate>
      <link>https://dev.to/paulopozeti/testing-is-the-most-important-piece-in-software-engineering-3jkm</link>
      <guid>https://dev.to/paulopozeti/testing-is-the-most-important-piece-in-software-engineering-3jkm</guid>
      <description>&lt;p&gt;You might think I'm crazy with such statement but if I had to summarize what my experience is, that is the only thing I can think about.&lt;/p&gt;

&lt;p&gt;Let's go through some story of my life, shall we?&lt;/p&gt;

&lt;h2&gt;
  
  
  The past
&lt;/h2&gt;

&lt;p&gt;I started my career on a pretty big product. At first, I couldn't understand much but after a few months it became apparent what the software development flow was: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Customer/product came up with a Change Request (a word document containing one or more product features).&lt;/li&gt;
&lt;li&gt;Product Architects from different areas would analyze the CR document and find out if their areas could be impacted. Interestingly they would do that on a meeting where each could validate their assumptions.&lt;/li&gt;
&lt;li&gt;PA would then write a High-Level Architecture Document, which would link to the CR document, and describe what is the effect of the requirement against a particular part of the system.&lt;/li&gt;
&lt;li&gt;The HLD would then move to tech leads + seniors for review, clarify anything doubts and get a feel for the code involved. The outcome of this was a Detailed Design document. This could have a few pseudo-code ideas and even the exact change to be made.&lt;/li&gt;
&lt;li&gt;Then a developer would pick it up, implement, design unit tests, test and mark task as delivered.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ok, so, classic waterfall process but apart from the developer testing it, how come was that different than most businesses do today?&lt;/p&gt;

&lt;p&gt;They had a separate QA team. But not a simple QA team, they knew the system from top to bottom. I didn't mention, on purpose, that they were also part of the PA meeting and also played a critical in creating tests that were cross areas.&lt;/p&gt;

&lt;p&gt;It was always expected for developers to validate their changes in a level that QA's do today. You should indeed check for all the permutations of the modified code and take your time testing them. In doubt? Two things: simplify your solution or test the hell of it.&lt;/p&gt;

&lt;p&gt;The QA team would have their own test process, they didn't work close to developers because they weren't worried about the &lt;em&gt;ifs&lt;/em&gt; or &lt;em&gt;elses&lt;/em&gt;. They always had a holistic view of the system. They knew that if devs are touch the customer registration flow, they would test that with everything they knew!&lt;/p&gt;

&lt;h2&gt;
  
  
  Back to now
&lt;/h2&gt;

&lt;p&gt;What is different now? Why does it feel that systems nowadays are flaky and with every new version there's always something broken?&lt;/p&gt;

&lt;p&gt;I don't have the-all-seeing-eye, but in my past experiences it is clear that we have less and less testing involved as part of the Software Development Life Cycle and another critical point: software companies are leaner than ever!&lt;/p&gt;

&lt;p&gt;We now have tools to automate everything. We can easily create tests in all levels, in a matter of seconds! Thanks Claude Code. But why are we still lacking confidence to deploy to production when we make a change?&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem(s)
&lt;/h2&gt;

&lt;p&gt;Tests were never about exercising the code. It's not about clicking around and make sure your breakpoint gets hit and nothing breaks.&lt;/p&gt;

&lt;p&gt;It's not getting that 80% coverage and testing every single branching of logic.&lt;/p&gt;

&lt;p&gt;It's also not about writing unit tests where you control every single step of the test. We have test abusers, leveraging &lt;a href="https://github.com/devlooped/moq" rel="noopener noreferrer"&gt;Moq&lt;/a&gt; and all sort of tools for that.&lt;/p&gt;

&lt;p&gt;I've seen more and more code worried about applying the &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself" rel="noopener noreferrer"&gt;DRY principle&lt;/a&gt; and they forget that tests should be simple, visible and about validating our understanding of how things should work.&lt;/p&gt;

&lt;h2&gt;
  
  
  (possible) Solution
&lt;/h2&gt;

&lt;p&gt;Tests exist to validate our assumptions about something. Because of that, make sure to write tests that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are easy to understand&lt;/li&gt;
&lt;li&gt;Have meaning and clear intent&lt;/li&gt;
&lt;li&gt;Asserts everything (and more!!!) and explain why&lt;/li&gt;
&lt;li&gt;You choose the right type of test for your scenario&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Let me show you what I mean with some concrete examples.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad:&lt;/strong&gt; Testing code branches, not assumptions&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Register_WhenEmailIsNull_ThrowsException&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;mockRepo&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;Mock&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IUserRepository&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
      &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mockEmail&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;Mock&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IEmailService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
      &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mockLogger&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;Mock&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;RegistrationService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;();&lt;/span&gt;
      &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sut&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;RegistrationService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mockRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mockEmail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mockLogger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Throws&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sut&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Register&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="s"&gt;"password123"&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 test mocks everything, exercises one branch, and tells you nothing about whether registration actually works.&lt;br&gt;
It's testing that an if statement exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better:&lt;/strong&gt; Validating your understanding of the behavior&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;New_user_can_register_and_receives_welcome_email&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Arrange - use real components; only fake what crosses system boundaries&lt;/span&gt;
      &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TestDatabase&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;emailInbox&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;FakeEmailInbox&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;service&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;RegistrationService&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;UserRepository&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;Connection&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FakeEmailService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emailInbox&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="n"&gt;NullLogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RegistrationService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Act&lt;/span&gt;
      &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"jane@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"SecureP@ss1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Assert — each assertion documents a business expectation&lt;/span&gt;
      &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;True&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Succeeded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Registration should succeed for a valid new 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;savedUser&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;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT * FROM Users WHERE Email = @Email"&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;Email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"jane@example.com"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NotNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedUser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// The user must actually be persisted&lt;/span&gt;
      &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;True&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsActive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"New users should be active immediately"&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;email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;emailInbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"jane@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;To&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Welcome"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// We promised a welcome email&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the difference. This test reads like a specification: a new user registers, gets persisted, is active, and receives a welcome email. Every assertion has intent. If any of these break, you know exactly which assumption was wrong.&lt;/p&gt;

&lt;p&gt;Testing across boundaries, like that QA team did:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Updating_product_price_does_not_change_price_in_existing_carts&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;db&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TestDatabase&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;productId&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="nf"&gt;Insert&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;Product&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;"Widget"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;9.99m&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;cartId&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="nf"&gt;Insert&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;Cart&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;CustomerId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;42&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="nf"&gt;Insert&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;CartItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;CartId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cartId&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;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PriceAtTimeOfAdd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;9.99m&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;catalog&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CatalogService&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;ProductRepository&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;Connection&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="n"&gt;catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UpdatePrice&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;newPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14.99m&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Product price updated&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;product&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;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;14.99m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// But the cart must be untouched — the customer saw 9.99 when they added it&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cartItem&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;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CartItem&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"WHERE CartId = @CartId"&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;CartId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cartId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;9.99m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cartItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PriceAtTimeOfAdd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s"&gt;"Carts must snapshot the price at add-time; a catalog update should never silently change what a customer
&lt;/span&gt;  &lt;span class="n"&gt;expects&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;pay&lt;/span&gt;&lt;span class="s"&gt;");
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That QA team from my past would have caught this. They knew that if you touch the catalog, you check the cart, checkout, invoicing — everything downstream. Two different teams own these areas, and neither writes tests for the other's assumptions. That's exactly where production bugs hide.&lt;/p&gt;

&lt;p&gt;So, what should you take away?&lt;/p&gt;

&lt;p&gt;Stop relying on coverage. &lt;br&gt;
Stop mocking everything. &lt;br&gt;
Start writing tests that describe what your system promises to do! &lt;br&gt;
Verify those promises end to end. &lt;/p&gt;

&lt;p&gt;If your test reads like a spec that a new team member could understand on day one, you're on the right track.&lt;/p&gt;

&lt;p&gt;Tests are not about exercising code. They're about proving you understand what your software is supposed to do and that it actually does it.&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>testing</category>
      <category>beginners</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Interface definition has never been so important!</title>
      <dc:creator>Paulo Pozeti</dc:creator>
      <pubDate>Sat, 21 Mar 2026 10:20:45 +0000</pubDate>
      <link>https://dev.to/paulopozeti/interface-definition-has-never-been-so-important-3p18</link>
      <guid>https://dev.to/paulopozeti/interface-definition-has-never-been-so-important-3p18</guid>
      <description>&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;I know you might be looking at this and thinking: interface definition has always been important! And I agree to that, but that's not what I'm seeing on the ground.&lt;/p&gt;

&lt;p&gt;With the increase of tutorials of prescribed architectures (hexagon, clean...), it's clear to see that the only intent for interfaces now is to create a boundary between layers, instead of a boundary of meaning.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A boundary between layers says 'this code lives here.' A boundary of meaning says 'this is what I can do and why.'&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What I mean by that? Well, lets look at a simple example I found on the web:&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;// https://github.com/jasontaylordev/CleanArchitecture/blob/main/src/Application/Common/Interfaces/IApplicationDbContext.cs&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IApplicationDbContext&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;DbSet&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TodoList&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TodoLists&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;DbSet&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TodoItem&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TodoItems&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="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;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;IApplicationDbContext&lt;/code&gt; doesn't have a specific job. Its only purpose is to give you a limited number of actions against EF DbSet's. And if you can't see any usings in that about EntityFramework is because that is hidden via &lt;a href="https://github.com/jasontaylordev/CleanArchitecture/blob/main/src/Application/GlobalUsings.cs" rel="noopener noreferrer"&gt;global usings&lt;/a&gt;.&lt;br&gt;
So all they are doing is "I'm going to define an interface, that doesn't abstract anything, doesn't have any meaning other than represent some other layer's capabilities".&lt;/p&gt;
&lt;h2&gt;
  
  
  Why does this matter now?
&lt;/h2&gt;

&lt;p&gt;I don't know if you noticed, but we are entering a new phase of software engineering. We are shifting from trying to find existing NuGet packages that does the work we need to now use AI that generates the minimum amount of code needed to suffice our requirements.&lt;/p&gt;

&lt;p&gt;But the problem is that AI often does two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Perpetuate the "meaningless" interface approach&lt;/li&gt;
&lt;li&gt;Might entangle your code with other packages&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To me, the 2nd point isn't as critical because that happens nowadays anyway. But the 1st point we must think about and address properly!&lt;/p&gt;
&lt;h2&gt;
  
  
  Solution: meaningful interfaces
&lt;/h2&gt;

&lt;p&gt;I picked the first example I could find on the internet when I searched for "clean architecture template", luckily that is also the easiest one to fix!&lt;/p&gt;

&lt;p&gt;If we look back at &lt;code&gt;IApplicationDbContext&lt;/code&gt;, and I didn't mention it as a problem, but we can see that it holds two meanings:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;State management: &lt;a href="https://github.com/jasontaylordev/CleanArchitecture/blob/main/src/Application/TodoItems/Commands/CreateTodoItem/CreateTodoItem.cs" rel="noopener noreferrer"&gt;creating/updating saving changes&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Queries: &lt;a href="https://github.com/jasontaylordev/CleanArchitecture/blob/main/src/Application/TodoItems/Queries/GetTodoItemsWithPagination/GetTodoItemsWithPagination.cs" rel="noopener noreferrer"&gt;accessing the raw DbContext to do whatever query&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since that interface holds two meanings we already know what we gotta do, let's split them.&lt;/p&gt;
&lt;h3&gt;
  
  
  State management abstraction
&lt;/h3&gt;

&lt;p&gt;First lets create a new interface and name it very explicitly:&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;// Explicit intent which is to handle Todo's state. We can only pull the ToDo or add one. Maybe in the future we can also remove it.&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ITodoStateData&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;NewTodoList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TodoList&lt;/span&gt; &lt;span class="n"&gt;newItem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;TodoList&lt;/span&gt; &lt;span class="nf"&gt;GetTodoList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Saving changes isn't part of the ITodoStateData, it belongs to something else.&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IUnitOfWork&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;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;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;As you can see, now those interfaces have a whole new meaning. Just by looking at the name of the interface you can grasp what they should be doing.&lt;/p&gt;

&lt;p&gt;Then when you implement those, you can go nuts on EF capabilities.&lt;/p&gt;

&lt;p&gt;An example would be:&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;ApplicationDbContext&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;ITodoStateData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IUnitOfWork&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Here you would have your dbsets as usual...&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;DbSet&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TodoList&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TodoLists&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TodoList&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;DbSet&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TodoItem&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TodoItems&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TodoItem&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// ITodoStateData members&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;NewTodoList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TodoList&lt;/span&gt; &lt;span class="n"&gt;newItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TodoLists&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;newItem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;TodoList&lt;/span&gt; &lt;span class="nf"&gt;GetTodoList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TodoLists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;First&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// IUnitOfWork SaveChangesAsync just works out of the box&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That only solves the state management problem, now let's go to the query one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Queries
&lt;/h3&gt;

&lt;p&gt;For queries, many people are now thinking that exposing the DbContext is the best approach as it gives you most of the flexibility. That is true but it doesn't give you reusability and meaning.&lt;/p&gt;

&lt;p&gt;To me, queries can fall into two buckets:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It fulfills an API request or all the data for a specific view&lt;/li&gt;
&lt;li&gt;It's a shared piece of logic that must be true all the time&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The approach of having DbContexts is fine for the bucket #1 but falls short on #2.&lt;/p&gt;

&lt;p&gt;How I would solve this problem is by, guess what, creating more interfaces with meaning!&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;// When you read: Todos list page, what comes to your mind?&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ITodosListPage&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PaginatedList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TodoItemBriefDto&lt;/span&gt;&lt;span class="p"&gt;&amp;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;GetTodoItemsWithPaginationQuery&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So now, that interface is very specific to the page that shows a list of Todos!&lt;br&gt;
Now where you implement this? Can be in &lt;code&gt;ApplicationDbContext&lt;/code&gt; itself, or another class in the Infrastructure project that has access to &lt;code&gt;ApplicationDbContext&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you have sharp eyes you might ask the question: hold on, you are passing &lt;code&gt;GetTodoItemsWithPaginationQuery&lt;/code&gt; down to Infrastructure? &lt;br&gt;
Ok, so you are paying attention. How you solve this is by creating a more general abstraction for the "pagination data" that your infrastructure can fulfill. I shall write about this later!&lt;/p&gt;

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

&lt;p&gt;We should be creating interfaces to ensure proper meaning between layers and also be very explicit about their intent! &lt;br&gt;
It's completely fine to have smaller but full of intent interfaces. Then have all of those implemented by a single class. Especially when they are all tied to external packages like EF!&lt;br&gt;
Another thing worth saying is that once you've set up this pattern in your code base, AI will definitely pick this pattern up and give you more aligned solutions.&lt;br&gt;
As with anything, this doesn't come for free. More interfaces mean you may end up with multiple implementors of the same logic. But to be honest, this already happens with the queries being duplicated everywhere anyway.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
      <category>csharp</category>
    </item>
  </channel>
</rss>
