Isaac.

LINQ: A Practical Deep Dive

Why LINQ matters, examples across frameworks, and a focus on ASP.NET + EF Core.

What is LINQ?

LINQ (Language Integrated Query) is a set of query operators in .NET that lets you query collections (in-memory or remote) using a unified syntax. It brings SQL-like expressive queries to C# and VB.NET — and the same ideas are found in Java streams, JavaScript array methods, Python comprehensions, and ORM query APIs.

Key LINQ concepts

  • Deferred execution — most LINQ operators build a query; work happens when you enumerate (e.g. ToList(), foreach).
  • IQueryable vs IEnumerable — IQueryable translates expressions to a provider (like EF Core -> SQL); IEnumerable runs in memory.
  • Composability — you can chain Where, Select, OrderBy, GroupBy, Join and more.

ASP.NET Core (Primary focus)

In ASP.NET Core, LINQ is commonly used with Entity Framework Core. Use IQueryable to let EF translate queries to SQL (more efficient) and remember to avoid client-side evaluation for large datasets.

Console LINQ (C#)
using System;
using System.Linq;
using System.Collections.Generic;

public class Product {
  public int Id { get; set; }
  public string Name { get; set; }
  public decimal Price { get; set; }
}

public class Example {
  public static void Main() {
    var products = new List<Product> {
      new Product { Id = 1, Name = "Apple", Price = 1.2m },
      new Product { Id = 2, Name = "Banana", Price = 0.8m },
      new Product { Id = 3, Name = "Cherry", Price = 2.5m }
    };

    // Where + Select (deferred until enumeration)
    var cheapNames = products.Where(p => p.Price < 2m).Select(p => p.Name);

    foreach(var name in cheapNames)
      Console.WriteLine(name);

    // GroupBy + OrderBy
    var byPrice = products.GroupBy(p => p.Price >= 2m)
                         .Select(g => new { Key = g.Key, Count = g.Count() });
  }
}
ASP.NET Controller (EF Core)
// ASP.NET Core controller example using Entity Framework Core
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Linq;

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase {
  private readonly AppDbContext _db;
  public ProductsController(AppDbContext db) { _db = db; }

  [HttpGet("cheap") ]
  public async Task<IActionResult> GetCheap(decimal maxPrice = 10m) {
    // IQueryable: translates LINQ to SQL via EF Core (deferred execution until ToListAsync)
    var q = _db.Products.Where(p => p.Price <= maxPrice)
                        .OrderBy(p => p.Price)
                        .Select(p => new { p.Id, p.Name, p.Price });
    var list = await q.ToListAsync();
    return Ok(list);
  }
}

Explanation: The controller returns an IQueryable chain which is translated to SQL by EF Core when ToListAsync() runs. This keeps filtering and ordering on the database server for efficiency.

Other frameworks

Spring Boot (Java)
import java.util.*;
import java.util.stream.*;

class Product {
  int id; String name; double price;
}

public class Example {
  public static void main(String[] args) {
    List<Product> products = new ArrayList<>();
    // ... populate ...
    // Streams: filter + map + collect
    List<String> cheapNames = products.stream()
      .filter(p -> p.price < 2.0)
      .map(p -> p.name)
      .collect(Collectors.toList());
  }
}
Express (Node.js)
// Express + in-memory example using JavaScript array methods (similar to LINQ)
const express = require('express');
const app = express();
const products = [ { id:1, name:'Apple', price:1.2 }, { id:2, name:'Banana', price:0.8 } ];

app.get('/cheap', (req, res) => {
  const max = Number(req.query.max) || 2;
  const result = products.filter(p => p.price <= max).map(p => ({ id: p.id, name: p.name }));
  res.json(result);
});
Next.js (TypeScript / Server)
// Next.js (app/router) server component example filtering server-side
import { NextResponse } from 'next/server';

export async function GET(request) {
  const products = await getProductsFromDb();
  const cheap = products.filter(p => p.price < 2).map(p => ({ id: p.id, name: p.name }));
  return NextResponse.json(cheap);
}
Flask (Python)
# Flask + SQLAlchemy example
from flask import Flask, jsonify, request
from models import Product, db

app = Flask(__name__)

@app.route('/cheap')
def cheap():
    max_price = float(request.args.get('max', 2))
    q = Product.query.filter(Product.price <= max_price).order_by(Product.price)
    return jsonify([{'id':p.id,'name':p.name,'price':p.price} for p in q])
Laravel (PHP)
// Laravel (PHP) controller example using Eloquent
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;

class ProductController extends Controller {
  public function cheap(Request $request) {
    $max = $request->query('max', 2);
    $list = Product::where('price', '<=', $max)->orderBy('price')->get(['id','name','price']);
    return response()->json($list);
  }
}

Best practices & Gotchas

  • Prefer IQueryable + provider translation when working with databases to avoid loading whole tables into memory.
  • Beware of client-side evaluation (EF Core will warn); it can cause huge memory and performance issues.
  • Profile generated SQL when migrating complex LINQ queries — some constructs produce suboptimal SQL.

Conclusion

LINQ provides a powerful, composable query model that reduces boilerplate and centralizes business logic. In ASP.NET + EF Core it's especially useful because it lets you write type-safe queries that translate to efficient SQL. Ignoring LINQ (or using it poorly) can lead to:

  • Excessive data transfer (pulling large tables into app memory)
  • Hard-to-maintain ad-hoc loops scattered through code
  • Security issues if filtering/escaping isn't centralized

Use LINQ thoughtfully: prefer provider-backed queries for DB work, test generated SQL, and keep heavy transforms server-side.