Singleton means new instance per application lifecycle. Scoped means new instance per request. Transient means new instance every time it's injected.
The problem you're likely having is that, in development, there's a fair amount of overlap between these things. If you're injecting it only one time, after you just made a code change, there's effectively no difference in the three scopes as all will result in a single new instance being created. This is because:
- With singleton scope, the site was just restarted due to the code change. As a result, it's a brand new process which needs a new instance.
- With request scope, you're making a request, so you're always going to get a new instance
- With transient scope, you only ever inject it once, so only one instance is being created.
However, in the "real world" the differences are more pronounced. Your app may be up for days, weeks, months, without being restarted. A singleton-scoped instance will survive this whole time. Transient is pretty much bound to the scope of the object it's being injected into. If you inject something with transient-scope into something with singleton-scope, then the transient-scoped instance is effectively singleton-scoped, as long as it doesn't get injected into anything else. Finally, request scope is request scope. New instance, every request, every time.
It's important to note that request-scoped instances don't tend to play nice with other types of scoped instances. For example, the database context is usually request-scoped. As a result, you cannot inject it into something that is singleton-scoped. You'll actually get an exception if you try. However, you can go the other way: inject a singleton into a request-scoped instance.