<?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: Vitalii Yavorskyi</title>
    <description>The latest articles on DEV Community by Vitalii Yavorskyi (@vyavorskyi).</description>
    <link>https://dev.to/vyavorskyi</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%2F3170857%2F2285fca2-190a-490b-9394-7c269e684a55.png</url>
      <title>DEV Community: Vitalii Yavorskyi</title>
      <link>https://dev.to/vyavorskyi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vyavorskyi"/>
    <language>en</language>
    <item>
      <title>Entity Framework Core is slow or Blind Engineers</title>
      <dc:creator>Vitalii Yavorskyi</dc:creator>
      <pubDate>Sun, 04 Jan 2026 21:06:17 +0000</pubDate>
      <link>https://dev.to/vyavorskyi/entity-framework-core-is-slow-or-blind-engineers-338b</link>
      <guid>https://dev.to/vyavorskyi/entity-framework-core-is-slow-or-blind-engineers-338b</guid>
      <description>&lt;p&gt;Entity Framework Core is one of the most productive tools in the .NET ecosystem.&lt;br&gt;
It allows teams to move fast, model domains cleanly, and ship features quickly. You should not dive into details how database works, which allows you to write code fast. And it's an advantage and disadvantage at the same time.&lt;/p&gt;
&lt;h2&gt;
  
  
  EF Core works great — until the project grows.
&lt;/h2&gt;

&lt;p&gt;More data.&lt;br&gt;
More relations.&lt;br&gt;
More edge cases.&lt;br&gt;
More performance-sensitive paths.&lt;/p&gt;

&lt;p&gt;At that point, problems start appearing — and EF often gets blamed. Someone starts to add additional indexes, which just temporarily masks the issue.&lt;/p&gt;

&lt;p&gt;But the real root cause: engineers have no idea how EF query is converted into SQL.&lt;/p&gt;

&lt;p&gt;In many mature codebases, I’ve seen LINQ queries written without any consideration for how they translate to SQL.&lt;/p&gt;

&lt;p&gt;At a small scale, this often goes unnoticed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Few records&lt;/li&gt;
&lt;li&gt;Low concurrency&lt;/li&gt;
&lt;li&gt;Acceptable latency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But as data grows, those “innocent” queries turn into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple joins you didn’t expect&lt;/li&gt;
&lt;li&gt;N+1 queries&lt;/li&gt;
&lt;li&gt;Full table scans&lt;/li&gt;
&lt;li&gt;Excessive memory usage&lt;/li&gt;
&lt;li&gt;Unoptimized Execution plan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;EF Core doesn’t hide SQL — it generates it.&lt;br&gt;
If you don’t understand the generated SQL, you are effectively coding blind.&lt;/p&gt;
&lt;h2&gt;
  
  
  A Real Example from Production
&lt;/h2&gt;

&lt;p&gt;A few years ago, while investigating performance issues in a production system, I found legacy code that did the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Loaded a few hundred entities into memory&lt;/li&gt;
&lt;li&gt;Enabled change tracking&lt;/li&gt;
&lt;li&gt;Updated them in a loop&lt;/li&gt;
&lt;li&gt;Called SaveChangesAsync()
&lt;/li&gt;
&lt;/ol&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;cutoff&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nowUtc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDays&lt;/span&gt;&lt;span class="p"&gt;(-&lt;/span&gt;&lt;span class="m"&gt;90&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;users&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;Users&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;UserStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Active&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LastLoginUtc&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="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LastLoginUtc&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;cutoff&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&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;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;u&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UserStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Archived&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UpdatedAtUtc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nowUtc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;All of this — just to update a small subset of fields.&lt;/p&gt;

&lt;p&gt;At that time, the correct approach was a set-based SQL operation (a stored procedure).&lt;br&gt;
Later, EF Core introduced ExecuteUpdateAsync, which solves this problem cleanly and efficiently.&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;Users&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;UserStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Active&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LastLoginUtc&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="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LastLoginUtc&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;cutoff&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteUpdateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;setters&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setters&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UserStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Archived&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UpdatedAtUtc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nowUtc&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the root cause was not “EF is slow”.&lt;br&gt;
&lt;strong&gt;The root cause was using an ORM abstraction without understanding its cost.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;EFCore is cross-database and flexible. It's designed to support a wide range of use cases, and it cannot be perfectly optimized for every scenario out of the box. And it shouldn’t be.&lt;/p&gt;

&lt;p&gt;Expecting EF to magically generate optimal SQL for every complex business case is unrealistic.&lt;/p&gt;

&lt;p&gt;The Responsibility Is on Us. As software engineers, especially at senior levels, we must:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understand how LINQ translates to SQL&lt;/li&gt;
&lt;li&gt;Know when tracking is needed — and when it’s not&lt;/li&gt;
&lt;li&gt;Recognize when a query should be "set-based", compiled, or raw query&lt;/li&gt;
&lt;li&gt;Use the right tool for the job&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes EF Core is the best solution. Sometimes stored procedure, view or raw query is better suite. &lt;/p&gt;

&lt;p&gt;Do not blame tool, if you do not understand how it works.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>EF Query vs SQL View in Entity Framework – Which One Would You Choose?</title>
      <dc:creator>Vitalii Yavorskyi</dc:creator>
      <pubDate>Mon, 16 Jun 2025 12:36:54 +0000</pubDate>
      <link>https://dev.to/vyavorskyi/ef-query-vs-sql-view-in-entity-framework-which-one-would-you-choose-23d3</link>
      <guid>https://dev.to/vyavorskyi/ef-query-vs-sql-view-in-entity-framework-which-one-would-you-choose-23d3</guid>
      <description>&lt;p&gt;Hi folks!&lt;br&gt;
I had few performance issue in my past projects using Entity Framework Core to retrieve and summarize data. Sometimes my EF queries were not converted into SQL in the best way, and it causes performance issues. I would like to share with you some example of task and will be happy to hear your thoughts about it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Task requirements
&lt;/h2&gt;

&lt;p&gt;There is a simple SQL Server database that contains 3 tables:&lt;br&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%2F1mfhf2h4f4dv8ttav8g9.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%2F1mfhf2h4f4dv8ttav8g9.png" alt="Image description" width="800" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The query should return all orders (including OrderType name and count of items where ItemType == 1) if the order contains any item where ItemType = 1 and with Order.CreatedAt &amp;gt; 12/05/2024. &lt;/p&gt;
&lt;h2&gt;
  
  
  Option 1: EF Query
&lt;/h2&gt;

&lt;p&gt;Here is the simple EF query 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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;test&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;Orders&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;05&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;t&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;Any&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="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ItemType&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="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;t&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="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&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;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CategoryName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderType&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="n"&gt;ItemCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&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;Count&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="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ItemType&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy to refactor or extend&lt;/li&gt;
&lt;li&gt;Fully integrated with EF and C#&lt;/li&gt;
&lt;li&gt;Strongly typed and expressive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Translates into a large SQL query under the hood&lt;/li&gt;
&lt;li&gt;May become inefficient for large datasets&lt;/li&gt;
&lt;li&gt;Harder to optimize with SQL indexes or query plans&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this query demonstrates performance issue (5.7s). When I look at the generated SQL query, I understood that using OrderItems is not an optimal solution. For some reason, this table is used twice in this query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o2&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o2&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o2&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o1&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;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;CategoryName&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;FROM&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="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o2&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="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o3&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="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o3&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;ItemType&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ItemCount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;TOP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;__p_0&lt;/span&gt;&lt;span class="p"&gt;)&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;].[&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;],&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;].[&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt;&lt;span class="p"&gt;],&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;].[&lt;/span&gt;&lt;span class="n"&gt;TypeId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;FROM&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="k"&gt;AS&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;]&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&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;].[&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2024-12-05T00:00:00.0000000'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;FROM&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="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&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;].[&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o0&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="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o0&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;ItemType&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&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;].[&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;OrderTypes&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o2&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;TypeId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o1&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;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o2&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is an execution plan for it:&lt;br&gt;
&lt;em&gt;Note: I did not create indexes to see the difference between the different approaches.&lt;/em&gt;&lt;br&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%2Fhrcb1lhp7uvl3orw69bp.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%2Fhrcb1lhp7uvl3orw69bp.png" alt="Image description" width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Option 2: SQL View + EF
&lt;/h2&gt;

&lt;p&gt;I was not able to change this query for better performance and that's why I created SQL view that does the aggregation directly in the database (as I wanted):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vw_Orders_ItemsCount&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;as&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;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;OrderType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&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;ItemType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ItemsCount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderTypes&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TypeId&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderItems&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;ON&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;OrderId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; 
    &lt;span class="n"&gt;t&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;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="k"&gt;c&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="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&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;ItemType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is simple EF query 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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;test&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;OrderItemTypeView&lt;/span&gt;
                        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;05&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ItemType&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Faqac0gkwdqnv5byzxqlw.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%2Faqac0gkwdqnv5byzxqlw.png" alt="Image description" width="800" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It provides 2x better execution time - 3.1s. (I had not created any non-clustered indexes)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simpler LINQ in C#&lt;/li&gt;
&lt;li&gt;Easier to optimize with indexes, execution plans, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Less flexibility (changing logic requires DB migration)&lt;/li&gt;
&lt;li&gt;View becomes an additional layer to maintain&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Do You Think?
&lt;/h2&gt;

&lt;p&gt;Is there a better way to write the EF query (without SQL view) above to make it cleaner or more performant?&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>efcore</category>
      <category>sql</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>.NET EF Core: When to Use (or don't) .AsSplitQuery()</title>
      <dc:creator>Vitalii Yavorskyi</dc:creator>
      <pubDate>Mon, 02 Jun 2025 20:31:52 +0000</pubDate>
      <link>https://dev.to/vyavorskyi/net-ef-core-when-to-use-or-dont-assplitquery-4po2</link>
      <guid>https://dev.to/vyavorskyi/net-ef-core-when-to-use-or-dont-assplitquery-4po2</guid>
      <description>&lt;p&gt;&lt;strong&gt;Entity Framework Core&lt;/strong&gt; is a fantastic and powerful tool — much like a BMW M4. If you’re just cruising slowly, everything works perfectly. But when you want to push it hard on the track, you need to understand the physics and specifications of the car. If you don’t know how to handle a 500-horsepower machine, it can hurt you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple Query with JOINs
&lt;/h2&gt;

&lt;p&gt;When you write a query like this in EF Core:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var result = await _context.TestTables
                .Include(t =&amp;gt; t.Category)
                .Include(b =&amp;gt; b.TestTableItems)
                .Where(b =&amp;gt; b.CreatedAt &amp;gt; startDate &amp;amp;&amp;amp; b.CategoryId == 3)
                .OrderBy(b =&amp;gt; b.CreatedAt)
                .Take(100)
                .ToListAsync(cancellationToken);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;EF Core by default generates one large SQL query using multiple JOINs. That’s great for minimizing database round-trips, but there’s a catch...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT [t1].[TestTableId], [t1].[CategoryId], [t1].[CreatedAt], [t1].[Name], [t0].[Id], [t0].[CreatedAt], [t0].[Name], [t2].[ItemId], [t2].[CreatedAt], [t2].[TestTableId], [t2].[Title]
      FROM (
          SELECT TOP(100) [t].[TestTableId], [t].[CategoryId], [t].[CreatedAt], [t].[Name]
          FROM [TestTables] AS [t]
          WHERE [t].[CreatedAt] &amp;gt; '2025-05-05T00:00:00.0000000' AND [t].[CategoryId] = 3
          ORDER BY [t].[CreatedAt]
      ) AS [t1]
      INNER JOIN [TestTableCategories] AS [t0] ON [t1].[CategoryId] = [t0].[Id]
      LEFT JOIN [TestTableItems] AS [t2] ON [t1].[TestTableId] = [t2].[TestTableId]
      ORDER BY [t1].[CreatedAt], [t1].[TestTableId], [t0].[Id]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQL server returns a big dataset with all required properties. In my example, code contains limit 100 entities - &lt;code&gt;.Take(100)&lt;/code&gt;. But SQL Server returns 600+ records due to a phenomenon known as 'Cartesian Explosion'. It's because every record of "TestTables" contains multiple items in "TestTableItems".&lt;br&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%2Fw5ssf86irs75xpvil2xb.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%2Fw5ssf86irs75xpvil2xb.png" alt="Image description" width="800" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In most scenarios, it won't be an issue. But when entities grows and more &lt;code&gt;.Include()&lt;/code&gt; sections are added - it can cause some issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Memory bloat due to row duplication&lt;/li&gt;
&lt;li&gt;Slower query materialization&lt;/li&gt;
&lt;li&gt;Increased deserialization overhead&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  &lt;code&gt;AsSplitQuery()&lt;/code&gt; helps
&lt;/h2&gt;

&lt;p&gt;This instructs EF Core to split the query into separate SQL commands — one for the main entity, and one per collection navigation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var result = await _context.TestTables
                .Include(t =&amp;gt; t.Category)
                .Include(b =&amp;gt; b.TestTableItems)
                .Where(b =&amp;gt; b.CreatedAt &amp;gt; startDate
                    &amp;amp;&amp;amp; b.CategoryId == 3)
                .AsSplitQuery()
                .OrderBy(b =&amp;gt; b.CreatedAt)
                .Take(100)
                .ToListAsync(cancellationToken);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this EF query is converted into two separate SQL queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- The First Query:
SELECT [t1].[TestTableId], [t1].[CategoryId], [t1].[CreatedAt], [t1].[Name], [t0].[Id], [t0].[CreatedAt], [t0].[Name]
      FROM (
          SELECT TOP(@__p_0) [t].[TestTableId], [t].[CategoryId], [t].[CreatedAt], [t].[Name]
          FROM [TestTables] AS [t]
          WHERE [t].[CreatedAt] &amp;gt; '2025-05-05T00:00:00.0000000' AND [t].[CategoryId] = 3
          ORDER BY [t].[CreatedAt]
      ) AS [t1]
      INNER JOIN [TestTableCategories] AS [t0] ON [t1].[CategoryId] = [t0].[Id]
      ORDER BY [t1].[CreatedAt], [t1].[TestTableId], [t0].[Id]

-- The Second:
      SELECT [t2].[ItemId], [t2].[CreatedAt], [t2].[TestTableId], [t2].[Title], [t1].[TestTableId], [t0].[Id]
      FROM (
          SELECT TOP(@__p_0) [t].[TestTableId], [t].[CategoryId], [t].[CreatedAt]
          FROM [TestTables] AS [t]
          WHERE [t].[CreatedAt] &amp;gt; '2025-05-05T00:00:00.0000000' AND [t].[CategoryId] = 3
          ORDER BY [t].[CreatedAt]
      ) AS [t1]
      INNER JOIN [TestTableCategories] AS [t0] ON [t1].[CategoryId] = [t0].[Id]
      INNER JOIN [TestTableItems] AS [t2] ON [t1].[TestTableId] = [t2].[TestTableId]
      ORDER BY [t1].[CreatedAt], [t1].[TestTableId], [t0].[Id]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the second query avoids duplicating information from [TestTables], potentially reducing memory usage when there are a lot of properties in the TestTables.&lt;br&gt;
You can find more in &lt;a href="https://learn.microsoft.com/en-us/ef/core/querying/single-split-queries" rel="noopener noreferrer"&gt;Microsoft arcticle "Single vs. Split Queries"&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Caution
&lt;/h2&gt;

&lt;p&gt;As you saw above, EF Core will repeat the base query for each collection navigation. That means if your base query is expensive, using split queries might actually make things slower, not faster.&lt;/p&gt;

&lt;p&gt;For example, this part of the query is reused:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM (
          SELECT TOP(@__p_0) [t].[TestTableId], [t].[CategoryId], [t].[CreatedAt]
          FROM [TestTables] AS [t]
          WHERE [t].[CreatedAt] &amp;gt; '2025-05-05T00:00:00.0000000' AND [t].[CategoryId] = 3
          ORDER BY [t].[CreatedAt]
      ) AS [t1]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your filter or sort involves heavy computation, indexing, or joins — that cost gets multiplied for each split query. So always benchmark both approaches (&lt;code&gt;.AsSingleQuery()&lt;/code&gt; vs &lt;code&gt;.AsSplitQuery()&lt;/code&gt;) with real production-like data before making a decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use vs. When to Avoid
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;AsSplitQuery()&lt;/code&gt; When:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Queries involve multiple &lt;code&gt;Include()&lt;/code&gt; statements&lt;/li&gt;
&lt;li&gt;You experience Cartesian Explosion due to related collections (e.g., one-to-many or many-to-many)&lt;/li&gt;
&lt;li&gt;The primary entity has many properties and duplicating them across joined rows leads to high memory usage&lt;/li&gt;
&lt;li&gt;You want to reduce materialization time and improve overall performance for large result sets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid &lt;code&gt;AsSplitQuery()&lt;/code&gt; When:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Queries are simple with minimal joins&lt;/li&gt;
&lt;li&gt;Main Entity has only few properties (columns)&lt;/li&gt;
&lt;li&gt;Included tables has small amount of records, so JOINs are efficient&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Like a high-performance car, Entity Framework Core can either take you from 0 to 100 smoothly — or leave you spinning out in a corner. Tools like &lt;code&gt;AsSplitQuery()&lt;/code&gt; help avoid pitfalls like Cartesian explosions, but understanding when and how to use them is the key to writing efficient, scalable data access code.&lt;br&gt;
Don’t forget: performance isn’t about one trick. It’s about using the right combination of tools.&lt;/p&gt;

&lt;p&gt;So next time you’re optimizing EF Core, remember: don’t just step on the gas — know how to handle the machine. 🏎️&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>sql</category>
      <category>efcore</category>
      <category>dotnetcore</category>
    </item>
    <item>
      <title>SQL Project in VS: Why column order matters?</title>
      <dc:creator>Vitalii Yavorskyi</dc:creator>
      <pubDate>Fri, 16 May 2025 19:45:44 +0000</pubDate>
      <link>https://dev.to/vyavorskyi/sql-project-in-vs-why-column-order-matters-48ce</link>
      <guid>https://dev.to/vyavorskyi/sql-project-in-vs-why-column-order-matters-48ce</guid>
      <description>&lt;p&gt;Many legacy projects use separate SQL projects to maintain SQL database code in Visual Studio. SQL Projects provide a great way to manage your database schema as code and CI/CD integration. But subtle details can make a big impact - like how you add new columns to existing tables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Column Order Matters?
&lt;/h2&gt;

&lt;p&gt;When using SQL Projects, any schema change (no matter how small) is interpreted by the SSDT build process and turned into deployment scripts. Here is an example:&lt;/p&gt;

&lt;p&gt;There is an existing table TestTable123&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE [dbo].[TestTable123]
(
    [Id] INT NOT NULL PRIMARY KEY, 
    [Name] NVARCHAR(10) NULL,
    [Description] NVARCHAR(100) NULL,
    [StatusId] INT NULL
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You add a new column into the middle of the existing table&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE [dbo].[TestTable123]
(
    [Id] INT NOT NULL PRIMARY KEY, 
    [Name] NVARCHAR(10) NULL,
    [Description] NVARCHAR(100) NULL,
    [Created] DATETIME NULL,             -- new column
    [StatusId] INT NULL                  -- existing column
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you deploy this change to your database, SSDT treats it as a breaking change and uses the following SQL code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- create the temp table
CREATE TABLE [dbo].[tmp_ms_xx_TestTable123] (
    [Id]          INT            NOT NULL,
    [Created]     DATETIME       NULL,
    [Name]        NVARCHAR (10)  NULL,
    [Description] NVARCHAR (100) NULL,
    [StatusId]    INT            NULL,
    PRIMARY KEY CLUSTERED ([Id] ASC)
);

-- copies all data into new temp table
IF EXISTS (SELECT TOP 1 1 
           FROM   [dbo].[TestTable123])
    BEGIN
        INSERT INTO [dbo].[tmp_ms_xx_TestTable123] ([Id], [Name], [Description], [StatusId])
        SELECT   [Id],
                 [Name],
                 [Description],
                 [StatusId]
        FROM     [dbo].[TestTable123]
        ORDER BY [Id] ASC;
    END

-- remove the existing table
DROP TABLE [dbo].[TestTable123];

-- rename the new temp table 
EXECUTE sp_rename N'[dbo].[tmp_ms_xx_TestTable123]', N'TestTable123';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is slow for large tables,&lt;/li&gt;
&lt;li&gt;Requires more transaction log space,&lt;/li&gt;
&lt;li&gt;Risks of locking or downtime.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Best Practice: Append Columns
&lt;/h2&gt;

&lt;p&gt;Whenever possible, add new columns at the end of the table. This helps SSDT generate a simple ALTER TABLE ... ADD statement, which:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is fast,&lt;/li&gt;
&lt;li&gt;Non-blocking (in most cases),&lt;/li&gt;
&lt;li&gt;Doesn’t require temp tables,&lt;/li&gt;
&lt;li&gt;Keeps deployments safer and more predictable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Disable column order setting
&lt;/h2&gt;

&lt;p&gt;You can add next "IgnoreColumnOrder" setting into .sqlproj file. It will ignore column order during your release.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;PropertyGroup&amp;gt;
  &amp;lt;IgnoreColumnOrder&amp;gt;true&amp;lt;/IgnoreColumnOrder&amp;gt;
&amp;lt;/PropertyGroup&amp;gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Just because SSDT simplifies your deployments doesn't mean you shouldn't understand how it works under the hood.
&lt;/h2&gt;

&lt;p&gt;Knowing why certain practices—like adding columns at the end—matter can help you avoid performance issues, deployment delays, and production downtime.&lt;/p&gt;

&lt;p&gt;SSDT is a powerful tool, but it's only as effective as the developer using it.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>cicd</category>
      <category>sqlproject</category>
      <category>dotnet</category>
    </item>
  </channel>
</rss>
