惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threatpost
aimingoo的专栏
aimingoo的专栏
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
Tailwind CSS Blog
J
Java Code Geeks
博客园_首页
Google Online Security Blog
Google Online Security Blog
Hugging Face - Blog
Hugging Face - Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
I
Intezer
P
Palo Alto Networks Blog
V
Vulnerabilities – Threatpost
雷峰网
雷峰网
O
OpenAI News
SecWiki News
SecWiki News
小众软件
小众软件
酷 壳 – CoolShell
酷 壳 – CoolShell
美团技术团队
N
News | PayPal Newsroom
Project Zero
Project Zero
Forbes - Security
Forbes - Security
IT之家
IT之家
A
Arctic Wolf
WordPress大学
WordPress大学
Jina AI
Jina AI
T
Tor Project blog
博客园 - 三生石上(FineUI控件)
S
Secure Thoughts
Google DeepMind News
Google DeepMind News
Attack and Defense Labs
Attack and Defense Labs
博客园 - 聂微东
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Privacy International News Feed
Cloudbric
Cloudbric
G
GRAHAM CLULEY
博客园 - 叶小钗
H
Hacker News: Front Page
腾讯CDC
量子位
Help Net Security
Help Net Security
人人都是产品经理
人人都是产品经理
C
Cyber Attacks, Cyber Crime and Cyber Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
宝玉的分享
宝玉的分享
爱范儿
爱范儿
L
Lohrmann on Cybersecurity
Hacker News - Newest:
Hacker News - Newest: "LLM"
Recorded Future
Recorded Future
C
CERT Recently Published Vulnerability Notes

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Singleton, Scoped, and Transient: What They Actually Mean for Your DbContext
Alimur Razi Rana · 2026-06-24 · via DEV Community

Anyone who has worked with dependency injection in .NET, might have registered something like this:

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(connectionString));

It works. app runs. But why does AddDbContext default to Scoped, and what would break if it didn't?

Let's find out — by breaking it on purpose.

The three lifetimes

Before touching any code, here's what each lifetime actually promises:

Transient — A new instance is created every single time it's requested. Ask for it twice in the same method, and get two different instances.

Scoped — One instance is created per scope, and reused for every request within that scope. In a web app, a scope is one HTTP request — so everything inside that request shares the same instance. The next request gets a fresh one.

SingletonExactly one instance, ever, for the entire lifetime of the app. Every request, every user, every caller — all sharing that one object, forever, until the app shuts down.

That's the theory. Now let's watch it actually happen.

Setting up a DbContext that tells us who it is

The trick to seeing lifetime behavior instead of just reading about it: give the DbContext a fingerprint.

public class AppDbContext : DbContext
{
    public Guid InstanceId { get; } = Guid.NewGuid();

    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
}

Every time a new AppDbContext gets constructed, it stamps itself with a new Guid. If two services end up holding the same InstanceId, we know for certain they got handed the same object — not two copies that look alike.

Two services that both need a database connection

In any real app, most cases never have just one class touching the database. Let's add two:

public class OrderWriter
{
    private readonly AppDbContext _db;
    public OrderWriter(AppDbContext db) => _db = db;

    public void ShowInstance(string label) =>
        Console.WriteLine($"[OrderWriter]     {label}{_db.InstanceId}");
}

public class InventoryWriter
{
    private readonly AppDbContext _db;
    public InventoryWriter(AppDbContext db) => _db = db;

    public void ShowInstance(string label) =>
        Console.WriteLine($"[InventoryWriter] {label}{_db.InstanceId}");
}

Both just print which AppDbContext they were handed. Think of these as stand-ins for an OrderRepository and an InventoryService in a real order-processing flow — two unrelated classes that both happen to need the same database context.

We still haven't registered anything with DI yet.

Wiring it up — starting with Singleton

Here's where it gets interesting. Let's register AppDbContext as a Singleton and see what happens across two separate "requests":

var services = new ServiceCollection();

services.AddSingleton<AppDbContext>(_ =>
{
    var options = new DbContextOptionsBuilder<AppDbContext>()
        .UseInMemoryDatabase("SingletonTest").Options;
    return new AppDbContext(options);
});

services.AddTransient<OrderWriter>();
services.AddTransient<InventoryWriter>();

var provider = services.BuildServiceProvider();

I used an in-memory database here on purpose — no real SQL Server needed, so anyone can copy this straight into a console app and run it.

Quick note on OrderWriter and InventoryWriter being Transient: that's deliberate. We want them rebuilt fresh every time, so the only thing that can explain a repeated InstanceId is the DbContext's own lifetime — not some side effect of caching the writer itself.

Simulating two HTTP requests

In a real web app, a "scope" is created automatically for you on every incoming request. In a console app, we have to make that explicit:

using (var requestOne = provider.CreateScope())
{
    var order = requestOne.ServiceProvider.GetRequiredService<OrderWriter>();
    var inventory = requestOne.ServiceProvider.GetRequiredService<InventoryWriter>();
    order.ShowInstance("Request 1");
    inventory.ShowInstance("Request 1");
}

using (var requestTwo = provider.CreateScope())
{
    var order = requestTwo.ServiceProvider.GetRequiredService<OrderWriter>();
    order.ShowInstance("Request 2");
}

Each using block is standing in for one separate incoming HTTP request, completely unrelated to the other.

Run this with the Singleton registration from above, and here's what prints:

[OrderWriter]     Request 1 → 3f1a2b4c-...
[InventoryWriter] Request 1 → 3f1a2b4c-...
[OrderWriter]     Request 2 → 3f1a2b4c-...

Same InstanceId, every single time — even across two completely separate requests.

Why that's actually a problem

In a real app: two unrelated users, hitting your API at the same time, would be sharing one AppDbContext.

DbContext isn't thread-safe. If request 1 and request 2 land close enough together, you'll hit:

InvalidOperationException: A second operation was started on this 
context instance before a previous operation completed.

And even if you never hit that exact exception, there's a quieter problem: EF Core's change tracker never gets cleared. With Scoped, a new DbContext — and therefore a new empty change tracker — gets created for every request. When the request ends, that DbContext gets disposed, and the tracker (along with everything it was holding onto) gets thrown away with it. But in this case, every entity ever loaded by any request stays tracked in that one singleton context, forever, growing without bound for as long as the app runs.

Singleton is wrong here. Let's try the opposite extreme.

Swapping in Transient

Same harness — change the registration line:

services.AddTransient<AppDbContext>(_ =>
{
    var options = new DbContextOptionsBuilder<AppDbContext>()
        .UseInMemoryDatabase("TransientTest").Options;
    return new AppDbContext(options);
});

Run the same two-request simulation, and now you get:

[OrderWriter]     Request 1 → 9c2e7f10-...
[InventoryWriter] Request 1 → 7b44d821-...
[OrderWriter]     Request 2 → a91f3c05-...

Every InstanceId is different — including OrderWriter and InventoryWriter, within the same request.

Why that's also a problem —

Picture OrderService written like this:

public class OrderService
{
    private readonly AppDbContext _db;
    private readonly IInventoryService _inventory;
    private readonly IOrderRepository _repository;

    public OrderService(AppDbContext db, IInventoryService inventory, IOrderRepository repository)
    {
        _db = db;
        _inventory = inventory;
        _repository = repository;
    }

    public async Task PlaceOrderAsync(Order order, OrderItem item)
    {
        await _inventory.ReserveAsync(item.ProductId, item.Quantity);
        await _repository.SaveAsync(order);
        await _db.SaveChangesAsync();
    }
}

ReserveAsync and SaveAsync are written to only stage their changes — no internal SaveChangesAsync() calls — trusting that one final commit at the bottom will save everything. That's the correct pattern, but only if every _db in this picture is the same object.

What happens with Transient

If AppDbContext is registered Transient, OrderService's own constructor gets handed its own fresh instance — call it DbContext#3. That's separate from whatever IInventoryService was given (DbContext#1) and whatever IOrderRepository was given (DbContext#2).

So:

  • _inventory.ReserveAsync stages its change on DbContext#1.
  • _repository.SaveAsync stages its change on DbContext#2.
  • _db.SaveChangesAsync() commits DbContext#3 — which nothing ever touched.

The method runs to completion. No exception. No error. And nothing was saved — because the one context that actually got told to commit was never the one holding any staged changes. The changes inside DbContext#1 and DbContext#2 are simply discarded the moment those objects are disposed, since nobody ever called SaveChangesAsync() on either of them specifically.

Now the one that actually works: Scoped

Last swap:

services.AddScoped<AppDbContext>(_ =>
{
    var options = new DbContextOptionsBuilder<AppDbContext>()
        .UseInMemoryDatabase("ScopedTest").Options;
    return new AppDbContext(options);
});

Run it again:

[OrderWriter]     Request 1 → c450e9a2-...
[InventoryWriter] Request 1 → c450e9a2-...
[OrderWriter]     Request 2 → e702b614-...

Look at that pattern closely — OrderWriter and InventoryWriter share an InstanceId within Request 1, but Request 2 gets a completely different one.

That's exactly the property we need:

  • Within one request, every service shares one DbContext — one change tracker, one real unit of work. OrderWriter and InventoryWriter can both stage changes and commit them together.
  • Across requests, nothing leaks. Request 2 starts clean, with no leftover tracked entities from Request 1, and no thread-safety conflict if both requests happen to run at the same time.

Putting it side by side

Lifetime Within one request Across two requests What goes wrong
Singleton Same instance Same instance Thread-safety crashes, unbounded change tracker growth
Transient Different instances Different instances Two services in one request can't share a transaction
Scoped Same instance Different instances Nothing — this is the one that fits

Try it yourself

The full thing runs as a plain console app — no real database required, since we're using EF Core's in-memory provider for the experiment. You'll need:

dotnet add package Microsoft.EntityFrameworkCore.InMemory

Then just swap the single registration line between AddSingleton, AddTransient, and AddScoped, rerun, and watch the GUIDs tell the story. Once you've seen all three side by side like this, the rule stops being something you memorized and starts being something you actually understand: match the lifetime to how long the state inside the object is supposed to live. A DbContext should live exactly as long as one unit of work — which, in a web app, is exactly one request. That's not a coincidence. That's the entire reason Scoped exists.

That covers Scoped pretty thoroughly. But Scoped isn't the only lifetime that earns its place — Singleton and Transient each have a scenario where they're not just "safe," they're the better choice than the other two. Let's look at one real example of each.

When Singleton is the right call

Use Singleton for stateless objects, require heavy initialization, or hold global application state. Common examples:

  • Caching services
  • Configuration providers
  • Logging utilities
  • Background workers and hosted services

When to avoid Singleton:

  • Thread-unsafe state — if multiple requests can hit the same instance at the same time, concurrent modifications to mutable data will cause bugs that are hard to reproduce.
  • Captive dependencies — never inject a shorter-lived service (Scoped or Transient) into a Singleton. Doing so forces that shorter-lived service to live far longer than it was designed to, which is exactly the bug our AppDbContext experiment above demonstrated: a Scoped DbContext trapped inside something longer-lived stops behaving like a per-request unit of work and starts leaking state across requests instead.

Picture an app-wide pricing settings provider — tax rates, currency exchange rates, feature flags — loaded once from configuration or a slow-changing table, then read over and over by every request that calculates a price.

If this were Scoped, every single request would re-read config and rebuild that exchange-rate data from scratch — paying the same cost over and over for a result that never changes between requests. Transient would be worse, potentially rebuilding it multiple times within one request if several services injected it.

When Transient is the right call

Use Transient when:

  • The service is stateless — the class doesn't hold or track data across multiple calls.
  • You're building lightweight utilities — simple validators, formatters, or data mappers are a natural fit.
  • The service isn't thread-safe — since you get a fresh instance every time, you sidestep the concurrency issues that come from sharing one object across multiple threads.
  • You need to avoid state contamination — you want one operation's execution to never leak data into another operation's execution, even within the same request.

When not to use Transient:

  • Heavily used dependencies — since the container builds a new object every single time, excessive use of Transient services increases allocations and adds pressure on the garbage collector, which can hurt performance at scale.
  • When sharing context is required — if multiple services need to work on the same unit of work at the same time (Entity Framework's DbContext being the textbook case), Transient breaks that sharing apart. That's exactly what we watched happen in the experiment above — use Scoped instead.

Now picture a report builder that accumulates lines as it works — say, one service builds the text for an invoice summary, and a separate, unrelated service builds the text for a shipping label. These aren't two pieces of one shared document; they're two completely independent outputs that just happen to use the same kind of builder. Transient guarantees each service gets its own instance, starting from a clean slate, so the invoice service's lines never show up inside the shipping service's result, and vice versa.

The pattern across all three

Lifetime What actually justifies it What goes wrong with the wrong choice
Singleton Holds no per-request data; nothing mutates after construction Scoped/Transient would rebuild expensive, identical state on every request for no benefit
Scoped Wraps a per-request unit of work (like DbContext) that multiple services need to share Singleton leaks state across users; Transient splits one logical operation into disconnected pieces
Transient Mutates its own state mid-operation, and that state must never be shared between callers Scoped or Singleton would let one caller's leftover state corrupt another's result

Every lifetime decision in this article comes down to the same one question, asked about the specific object in front of you: what does this object store on itself, who else might be holding the same instance at the same time, and what would happen if they were? Answer that honestly for any service in your own app, and the right registration call — AddSingleton, AddScoped, or AddTransient — stops being a guess.