How I accidentally broke my Blog links with a simple LINQ mutation
While building my little blog engine in C#, I introduced a subtle bug.
The first post is fine. As soon as I add another, the site breaks and every new link gives me a 404.
When I looked at the generated URLs, I saw the paths were strangely getting longer:
- First post:
../post-title-1(Correct) - Second post:
../../post-title-2(Broken)
The bug was entirely due to deferred execution and object mutation. The issue happened because of the repeated modification of the underlying object collection every time the LINQ query was executed inside the loop.
The Bad Code
Here's the mistake: I modified the slug directly inside the LINQ projection. At first, it seemed fine, but it introduced the problem you will see below.
var postsBySlug = posts
.Select(p =>
{
// BAD: Modifying the object directly (Mutating the original 'p')
p.Slug = $"../{p.Slug}";
return p;
});
// The loop that triggers the issue on every iteration
foreach (var category in categories)
{
var postsByCategory = postsBySlug
.Where(p => p.Category == category.Name);
}
Why this breaks
Because LINQ uses deferred execution, that Select statement runs every time the loop iterates for a new category.
Since I was mutating the actual object (p.Slug = ...), the changes started prepending to the slug property on every iteration.
- Loop 1 (First Category): The query runs. It prepends
../. The slug for the first post becomes../post-title-1. - Loop 2 (Second Category): The query runs again on the same object. It sees the slug (which is already
../post-title-2) and prepends../again. The result is../../post-title-2.
The Fix
Never mutate the original object inside a projection. Instead, spin up a new object, similar to cloning the original but modifying only the slug property.
var adjustedPosts = posts
.Select(p => new PostItem
{
// GOOD: Project into a new object
Slug = $"../{p.Slug}",
Title = p.Title,
Category = p.Category
});
By creating a new PostItem, the original data stays clean no matter how many times the loop runs. It was a small bug, but a good reminder: LINQ works best when you avoid mutating objects inside your queries.
Here is the complete code
var posts = new List<PostItem>
{
new PostItem { Title = "First Post", Slug = "post-title-1", Category = "A" },
new PostItem { Title = "Second Post", Slug = "post-title-2", Category = "B" },
new PostItem { Title = "Third Post", Slug = "post-title-3", Category = "A" }
};
var categories = new List<string> { "A", "B", "C" };
// Define the Buggy, Mutating LINQ Query does not run until it is enumerated
var postsBySlug = posts
.Select(p =>
{
p.Slug = $"../{p.Slug}";
return p;
});
foreach (var category in categories)
{
var postsByCategory = postsBySlug
.Where(p => p.Category == category);
foreach (var post in postsByCategory)
{
Console.WriteLine($"Generated Link for Post: {post.Title} | Slug: {post.Slug}");
}
}
public class PostItem
{
public string Title { get; set; }
public string Slug { get; set; }
public string Category { get; set; }
}