<?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: Agave Information Soultions, LLC</title>
    <description>The latest articles on DEV Community by Agave Information Soultions, LLC (@agave_info_solutions).</description>
    <link>https://dev.to/agave_info_solutions</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%2F3965683%2F98aaa7fd-e15b-4d78-a06d-74459ecd4390.jpg</url>
      <title>DEV Community: Agave Information Soultions, LLC</title>
      <link>https://dev.to/agave_info_solutions</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/agave_info_solutions"/>
    <language>en</language>
    <item>
      <title>EF6 to EF Core: 5 query translations that quietly break in production</title>
      <dc:creator>Agave Information Soultions, LLC</dc:creator>
      <pubDate>Wed, 03 Jun 2026 05:08:00 +0000</pubDate>
      <link>https://dev.to/agave_info_solutions/ef6-to-ef-core-5-query-translations-that-quietly-break-in-production-1ghm</link>
      <guid>https://dev.to/agave_info_solutions/ef6-to-ef-core-5-query-translations-that-quietly-break-in-production-1ghm</guid>
      <description>&lt;p&gt;Most teams plan an EF6 to EF Core migration as "swap the package, fix some namespaces, ship it." For straightforward CRUD codebases that's mostly true. For anything with real query complexity, you will hit a handful of translation differences that the upgrade tooling does not warn you about. The queries compile fine, the unit tests pass against an in-memory provider, and then production explodes the first time someone hits the page that runs the report.&lt;/p&gt;

&lt;p&gt;These are the five that bit us hardest on real migrations. None of them are obvious from reading the official "what's new" page, and a couple of them are silent regressions in behavior rather than visible errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Client-side evaluation does not silently rescue you anymore
&lt;/h2&gt;

&lt;p&gt;EF Core 1.x and 2.x had a feature called automatic client evaluation. If part of your LINQ query could not be translated to SQL, EF Core would happily run that part in memory after fetching the rest of the data from the database. It logged a warning, but the query worked.&lt;/p&gt;

&lt;p&gt;EF Core 3.0 changed the default to throw instead of silently falling back. Code that ran fine for two years suddenly throws &lt;code&gt;InvalidOperationException: The LINQ expression could not be translated.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The worst version of this is a query like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;csharp&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Customers&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;c&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DisplayName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NormalizeForSearch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NormalizeForSearch&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;NormalizeForSearch() is a custom C# extension method. EF6 happily fetched every row, ran the method on each one in memory, and filtered. The query was a full table scan and stayed quietly slow for years. After the EF Core upgrade it throws at runtime.&lt;/p&gt;

&lt;p&gt;The remediation is not "add .AsEnumerable() before the Where" even though that "works." That just preserves the original full-table-scan behavior with the original O(n) memory cost. The right fix is to push the normalization into the query itself using EF.Functions.Like or a normalized column maintained at write time. Either of those produces real SQL with real index usage.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;GroupBy translates much less aggressively&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;EF6 translated almost any GroupBy you threw at it. EF Core is far more conservative — for years, it required your GroupBy to fit a narrow shape (group key + aggregate functions) and would throw if you tried to project the group elements directly.&lt;/p&gt;

&lt;p&gt;This works in both:&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;totals&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&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;GroupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&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;g&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;CustomerId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&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;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This worked in EF6 but did not in EF Core 3-5, and only partially works in EF Core 7+:&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;groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&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;GroupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&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;g&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;CustomerId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&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="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second one is asking for "give me each group along with the list of items in that gr# LINQ but hard to express in standard SQL. EF Core 7 added partial support via SQL Server window functions; older versions throw.&lt;/p&gt;

&lt;p&gt;For migration purposes, audit every GroupBy in the codebase before you flip the switch. The shape that breaks most often is grouping followed by projecting the actual group elements rather than aggregations.&lt;/p&gt;

&lt;p&gt;Restructuring usually means doing the grouping client-side after a flat query, or rewrifor aggregates.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;String comparisons changed collation behavior&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;EF6 ran string comparisons through SQL Server with the database's default collation, whie accent-insensitive on most installations. EF Core leaves the comparison semantics up tothe provider, and the defaults are different across providers.&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="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c&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;"foo@bar.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;against a Latin1_General_CI_AS database returns rows where Email equals &lt;a href="mailto:FOO@BAR.COM"&gt;FOO@BAR.COM&lt;/a&gt; because the SQL collation is case-insensitive. The same query against PostgreSQL&lt;br&gt;
returns zero rows because PostgreSQL is case-sensitive by default. If your application wver provision a PostgreSQL instance, you will spend a confused afternoon.&lt;/p&gt;

&lt;p&gt;EF Core 5+ exposes EF.Functions.Collate for explicit collation control, and EF.Functionsttern matching that works the same across providers. Use those instead of relying on thedatabase default if your code might run against more than one provider.&lt;/p&gt;

&lt;p&gt;A subtler form: string.Equals(c.Name, query, StringComparison.OrdinalIgnoreCase) translated to ordinary = in EF6 (the comparison enum was just ignored), but in EF Core it can throw "could not be translated" depending on version. Same shape of bug as #1.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;DateTime.Now lands client-side in confusing ways&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;DateTime.Now evaluated in the context of a LINQ query does not have a consistent transla and providers.&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;recent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&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;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;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&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;7&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;EF6 translated this to WHERE CreatedAt &amp;gt; DATEADD(day, -7, GETDATE()) on SQL Server, whicery time. EF Core sometimes evaluates DateTime.Now once on the client at query construction time, then sends a constant to SQL. If your app builds the query at 12:00 and executes it at 12:30, EF6 uses 12:30, EF Core often uses 12:00.&lt;/p&gt;

&lt;p&gt;For most queries this 30-minute drift does not matter. For session expiry, rate limiting, scheduled job pickups, or audit windows, it matters a lot.&lt;/p&gt;

&lt;p&gt;The deterministic pattern: capture the time explicitly in a local variable and pass it as a parameter. Or use EF.Functions.GetUtcDate() to force server-side evaluation. Or move the time calculation into a&lt;br&gt;
database function and call it explicitly. Any of those produces predictable behavior.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Lazy loading is off by default and the fix is not what you think&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;EF6 enabled lazy loading by default if your navigation properties were marked virtual. Ty fetches it.&lt;/p&gt;

&lt;p&gt;EF Core does not enable lazy loading by default. The migration path that the documentatirosoft.EntityFrameworkCore.Proxies and call UseLazyLoadingProxies() on the options. That technically restores lazy loading, but it makes every navigation access into a separate database round trip — exactly the N+1 problem the rest of the EF Core team is trying to push you away from.&lt;/p&gt;

&lt;p&gt;The better migration path is to audit where your code was relying on lazy loading and convert those sites to either explicit Include calls or projection into DTOs. It is more work, but the resulting queries&lt;br&gt;
are faster, the N+1 patterns become visible at code review time instead of in production table.&lt;/p&gt;

&lt;p&gt;If you do install the lazy loading proxies as a "get the migration unblocked" bridge, to put a real plan in place to remove it. The longer you ship on lazy proxies, the more code grows to depend on the implicit fetch behavior and the harder removal gets.&lt;/p&gt;

&lt;p&gt;The pattern across all five&lt;/p&gt;

&lt;p&gt;Every one of these breakages comes from the same root: EF6 was very permissive about hiding the gap between LINQ semantics and SQL semantics, and EF Core is less so. EF Core fails loudly (or silently&lt;br&gt;
differently) in places where EF6 would have happily run something inefficient.&lt;/p&gt;

&lt;p&gt;That sounds like a regression. It is actually the right tradeoff. The EF6 queries that "l table scans, N+1 patterns, or subtly wrong results that nobody noticed because the database was small enough that performance didn't expose them. EF Core's stricter posture forces those decisions to surface during migration instead of in production a year later when the data has grown.&lt;/p&gt;

&lt;p&gt;The practical migration playbook:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Turn on LogTo for Microsoft.EntityFrameworkCore.Query.QueryCompilationStarting events during integration tests so you can see what each query actually translates to.&lt;/li&gt;
&lt;li&gt;Run a load test against representative data volume before declaring the migration don the dev database often fail on production volume after the EF Core change.&lt;/li&gt;
&lt;li&gt;Audit every GroupBy, every Where with custom methods, every navigation property access, and every DateTime.Now reference.&lt;/li&gt;
&lt;li&gt;Treat Microsoft.EntityFrameworkCore.Proxies as a bridge, not a destination.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The "swap the package and ship it" plan turns into a multi-week project for non-trivial modernization is one of the things we work on most often at Agave Information Solutions(&lt;a href="https://agaveis.com/dotnet-modernization" rel="noopener noreferrer"&gt;https://agaveis.com/dotnet-modernization&lt;/a&gt;). If you are heading into one of these migrations and want a sanity check on your pattern audit, the .NET Modernization (&lt;a href="https://agaveis.com/dotnet-modernization" rel="noopener noreferrer"&gt;https://agaveis.com/dotnet-modernization&lt;/a&gt;)&lt;br&gt;
page has more on how we approach it.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>efcore</category>
      <category>csharp</category>
      <category>database</category>
    </item>
    <item>
      <title>Agave Information Solutions</title>
      <dc:creator>Agave Information Soultions, LLC</dc:creator>
      <pubDate>Wed, 03 Jun 2026 04:58:25 +0000</pubDate>
      <link>https://dev.to/agave_info_solutions/agave-information-solutions-d7n</link>
      <guid>https://dev.to/agave_info_solutions/agave-information-solutions-d7n</guid>
      <description>&lt;p&gt;30 years on the Microsoft stack. Started writing .NET when the framework was still in beta in 2001.&lt;/p&gt;

&lt;p&gt;Founded &lt;a href="https://tempestcom.com" rel="noopener noreferrer"&gt;Tempest Telecom&lt;/a&gt; in 1996. We built the first Internet roaming service to provide access on all 7 continents, and a unified AAA platform that combined voice, dialup, WiFi, and satellite into one experience. The 2+ petabyte video platform that came out of that work is where I learned that query plans behave non-linearly at scale, and why backup strategy is architecture.&lt;/p&gt;

&lt;p&gt;Founded &lt;a href="https://agaveis.com" rel="noopener noreferrer"&gt;Agave Information Solutions&lt;/a&gt; in Scottsdale in 2008. Today we modernize legacy .NET Framework applications onto modern .NET 10/9/8 with AI-ready architecture, design databases that need to handle real volume, and ship the kind of senior-engineering work most boutique shops outsource.&lt;/p&gt;

&lt;p&gt;I write here about .NET migrations, EF Core gotchas, SQL Server query plans, AI integration into legacy stacks, and the lessons from running production systems at scale for a long time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Find me elsewhere:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://agaveis.com" rel="noopener noreferrer"&gt;agaveis.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/jasonjacoby/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Agaveis" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://x.com/AgaveInfo" rel="noopener noreferrer"&gt;X&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>dotnet</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
