C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
37: LINQ to Objects vs LINQ to Entities
I want to show you something that has tripped up almost every developer I've ever mentored. On the surface, LINQ looks the same regardless of where your data is. Whether you're filtering a List<T> or querying a SQL database via Entity Framework, the syntax—the .Where(), the .Select(), the .First()—is identical. But under the hood, they are two entirely different animals.
Let's dive into a real scenario. Suppose we have a Product class and a helper method that cleans up product names for a report.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public static class ProductHelper
{
public static string CleanName(string name) => name.Trim().ToUpper();
}
The "Everything Just Works" Phase
First, I'll start with a simple local list. This is LINQ to Objects. I'm just querying memory. I'll try to filter my products using that custom CleanName method.
var products = new List<Product>
{
new Product { Id = 1, Name = " laptop ", Price = 1000 },
new Product { Id = 2, Name = " MOUSE ", Price = 20 }
};
var results = products
.Where(p => ProductHelper.CleanName(p.Name) == "LAPTOP")
.ToList();
Console.WriteLine($"Found {results.Count} items.");
This runs perfectly. Why? Because the C# compiler just executes the CleanName method for every item in the list. It's all happening inside the .NET runtime. Easy.
Hitting the Translation Wall
Now, let's swap that local list for a database context. This is LINQ to Entities. I'm using the exact same logic, but now the data is sitting in a SQL Server table.
using (var context = new AppDbContext())
{
var results = context.Products
.Where(p => ProductHelper.CleanName(p.Name) == "LAPTOP")
.ToList();
}
If you run this, it's going to blow up in your face. You'll likely see a NotSupportedException telling you that the LINQ provider could not be translated.
Here is what's happening: LINQ to Entities isn't actually executing C# code against your data. It's acting as a translator. It takes your .Where() expression and tries to turn it into a SQL string (like SELECT * FROM Products WHERE ...). When the translator hits ProductHelper.CleanName, it panics. SQL Server has no idea what a C# static method is; it only understands SQL functions.
The Temptation of the "Quick Fix"
When I first encountered this, my instinct was to just "get the data into memory" so the C# method would work. I did this by adding a .ToList() before the filter.
var results = context.Products
.ToList() // Pull everything into memory FIRST
.Where(p => ProductHelper.CleanName(p.Name) == "LAPTOP")
.ToList();
Technically, this works. The .ToList() forces LINQ to Entities to execute SELECT * FROM Products, dumping the entire table into your RAM. Then, it switches over to LINQ to Objects, and your CleanName method works fine.
But this is a disaster waiting to happen. If your table has 10,000 rows, you're downloading 10,000 rows just to find one "LAPTOP". You've just turned your database into a very expensive file storage system and shifted all the processing load to your application server. Never do this unless you are absolutely sure the dataset is tiny.
Bridging the Gap
To fix this properly, I have to write the query using things the LINQ provider can translate. Most standard string methods like .Trim() or .ToUpper() have direct SQL equivalents that Entity Framework knows how to handle.
var results = context.Products
.Where(p => p.Name.Trim().ToUpper() == "LAPTOP")
.ToList();
Now, the translator sees .Trim() and .ToUpper() and says, "I know those! I'll convert those to LTRIM(RTRIM()) and UPPER() in SQL." The database does the heavy lifting, and only the matching rows are sent over the network.
The rule of thumb I use is this: keep your logic inside the IQueryable (before the ToList) using basic primitives and built-in methods. If you absolutely must use a complex C# method, do it in the .Select() phase after you've filtered your data down to the smallest possible set.
📋 Practical Task
Fixing the Client-Side Evaluation Leak
You have been handed a piece of code for a Warehouse Management System. The current implementation is causing massive memory spikes because a developer used a custom C# method inside a database query, forcing the application to pull the entire inventory into memory.
Your Goal: Modify the GetExpiredProducts method so that the filtering happens on the database server (LINQ to Entities) rather than in memory (LINQ to Objects), without removing the IsExpired logic.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime ExpiryDate { get; set; }
}
public static class DateHelper
{
// This method cannot be translated to SQL
public static bool IsExpired(DateTime date)
{
return date < DateTime.Now;
}
}
public class InventoryService
{
private readonly AppDbContext _context;
public InventoryService(AppDbContext context) => _context = context;
public List<Product> GetExpiredProducts()
{
// BUG: This currently calls .ToList() too early or uses a non-translatable method
return _context.Products
.ToList()
.Where(p => DateHelper.IsExpired(p.ExpiryDate))
.ToList();
}
}
Requirements:
- Remove the premature
.ToList()call. - Replace the
DateHelper.IsExpiredmethod call with a translatable LINQ expression that compares theExpiryDateto the current time. - Ensure the final result is still returned as a
List<Product>.
There are no comments for now.