CodeOn.NET

Code, Learn, Build in .NET
Theme:
    December 04, 2025 3 min read

    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:

    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.

    1. Loop 1 (First Category): The query runs. It prepends ../. The slug for the first post becomes ../post-title-1.
    2. 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; }
    }