Wat!

LINQ and the art of clarity

After fifteen years of writing C#, I have a small confession: I think LINQ is one of the most under-rated language features of any modern language. Not because of what it can do — though that is a long list — but because of what it lets you delete.

Here's a real example from our codebase. The original:

var followedIds = new List<int>();
foreach (var f in db.Follows)
{
    if (f.UserId == userId)
        followedIds.Add(f.NotebookId);
}

var posts = new List<Post>();
foreach (var p in db.Posts)
{
    if (p.Published && followedIds.Contains(p.NotebookId))
        posts.Add(p);
}
posts.Sort((a, b) => b.CreatedAt.CompareTo(a.CreatedAt));
return posts.Take(30).ToList();

The same thing, in LINQ:

var followedIds = await db.Follows
    .Where(f => f.UserId == userId)
    .Select(f => f.NotebookId)
    .ToListAsync(ct);

return await db.Posts
    .Where(p => p.Published && followedIds.Contains(p.NotebookId))
    .OrderByDescending(p => p.CreatedAt)
    .Take(30)
    .ToListAsync(ct);

Half the code, twice as readable, and — because EF translates it to SQL — about a hundred times faster on real data.

Three rules I've come to live by

  1. Project early, materialize late. Use .Select(…) to slim down what comes back from the database. Don't .ToListAsync() until you need to.
  2. Any over Count() > 0. Any short-circuits; Count does not.
  3. Avoid .Include() if you only need one column. A projection into an anonymous type is almost always cheaper.
// Don't:
var nb = await db.Notebooks
    .Include(n => n.Owner)
    .FirstOrDefaultAsync(n => n.Id == id);
var name = nb?.Owner.DisplayName;

// Do:
var name = await db.Notebooks
    .Where(n => n.Id == id)
    .Select(n => n.Owner.DisplayName)
    .FirstOrDefaultAsync();

The second version translates to a single small SELECT. The first one drags an entire Notebook row and an entire User row across the wire just to read one string.

A closing thought

LINQ is, fundamentally, a way to say what you want without having to spell out how. That's a rare and lovely thing in a working programmer's life. Most code is how. LINQ gives you a small, well-lit room to work in what.

5

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please retry or reload the page.

An unhandled error has occurred. Reload 🗙