34

How am I supposed to inject my MyDbContext into my database service layer MyService?

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<MyDbContext>(options =>
    options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
    services.AddMvc();
}

MyDbContext.cs

public partial class MyDbContext : DbContext
{
    public virtual DbSet<User> User { get; set; }

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

appsettings.json

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  },
  "ConnectionStrings": {
    "DefaultConnection": "Server=MyServer;Database=MyDatabase;user id=MyUser;password=MyPassword;"
  }
}

MyService.cs

public class MyService
{
    public User GetUser(string username)
    {
        // Should i remove this call and get the reference from the injection somehow?
        MyDbContext db_context = new MyDbContext(optionsBuilder.Options);
        using (db_context)
        {
            var users = from u in db_context.User where u.WindowsLogin == username select u;
            if (users.Count() == 1)
            {
                return users.First();
            }
            return null;
        }
    }
}

In my GetUser method, I know I am supposed to be using my injected MyDbContext here but I am not quite sure how to fetch it. Which piece of the puzzle am I missing?

Danny Cullen
  • 1,782
  • 5
  • 30
  • 47

1 Answers1

58

You don't have to include the dbcontext yourself, ASP.NET core dependency injection service will do this for you.

You have just to declare your services and your database context in your startup class, and put the dbcontext you need in your service's constructor :

Startup.cs (your have to choose the service lifetime you want, here it's a scoped service, once per request):

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<MyDbContext>(options =>
    options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
    services.AddMvc();
    services.AddScoped<IMyService, MyService>();
}

Your service class:

public class MyService : IMyService
{
    private readonly MyDbContext _context;

    public MyService(MyDbContext ctx){
         _context = ctx;
    }

    public User GetUser(string username)
    {
        var users = from u in _context.User where u.WindowsLogin == username select u;
        if (users.Count() == 1)
        {
            return users.First();
        }
        return null;
    }
}

public interface IMyService
{
    User GetUser(string username);
}

In your controller, you have to declare the services (or the database context) you need to use in the same way :

public class TestController : Controller
{
     private readonly IMyService _myService;

      public TestController(IMyService serv)
      {
           _myService = serv;
      }

      public IActionResult Test()
      {
          return _myService.MyMethod(); // No need to instanciate your service here
      }
}

Note about the controller : you don't have to add them in your startup class like you do with your database context or your services. Just implement their constructor.

If you need more information about the .NET Core depencency injection, the official documentation is clear and very complete : https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection


NB: In the startup.cs, the AddScoped line is an option. Your can choose the lifetime you want for your service. There is the different lifetimes you can choose :

Transient

Transient lifetime services are created each time they are requested. This lifetime works best for lightweight, stateless services.

Scoped

Scoped lifetime services are created once per request.

Singleton

Singleton lifetime services are created the first time they are requested (or when ConfigureServices is run if you specify an instance there) and then every subsequent request will use the same instance.

Above taken from: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection


Note: It's not the question here but your GetUser data query looks like a little strange to me. If your count()==1 goal is to check the unicity of the user, the good way is to add a unicity constraint in your database. If count()==1 goal is to check you have data to avoid a object null reference exception, you can use .FirstOrDefault(), it will manage this for you. You can simplify this method :

public User GetUser(string username) => (from u in _context.User where u.WindowsLogin == username select u).FirstOrDefault();
AdrienTorris
  • 9,111
  • 9
  • 34
  • 52
  • Please always attribute direct quotes to where they came from to avoid plagarism. Also format quotes as "quotes" to make it obvious you are quoting an external source – Liam Dec 09 '16 at 10:42
  • @Liam I didn't know how to quote some text, I was searching about that. It's one of the reasons why I put the direct link to the official documentation. I was searching how to do that when you updated my answer ;) – AdrienTorris Dec 09 '16 at 10:44
  • This is great - I am however calling my service `MyService service = new MyService();` from my controller, but it is requiring MyDbContext as the constructor. – Danny Cullen Dec 09 '16 at 10:48
  • 1
    You don't have to instanciate your service yourself in your controller. I update my answer to add this – AdrienTorris Dec 09 '16 at 10:48
  • 1
    Controller stuff added in my answer. Dependency injection is a very central feature of the .NET core architecture, I strongly recommand you to read the documentation about that. – AdrienTorris Dec 09 '16 at 10:53
  • Got the initial setup done, now getting `ObjectDisposedException: Cannot access a disposed object` – Danny Cullen Dec 09 '16 at 11:05
  • 1
    Have you delete your using() in your GetUser method ? Can you update your code in your answer and add the controller's one ? – AdrienTorris Dec 09 '16 at 11:09
  • Where is throw the exception ? in your startup ? in your service ? – AdrienTorris Dec 09 '16 at 11:10
  • throws it in `MyService` and yes I am using `using(_context)`. The error occurs when I call `users.ToList();` – Danny Cullen Dec 09 '16 at 11:35
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/130212/discussion-between-danny-cullen-and-adrien). – Danny Cullen Dec 09 '16 at 11:50
  • 1
    All sorted, thank you so much Adrien. removing the using(_context) did the job. – Danny Cullen Dec 09 '16 at 11:56
  • Great. Welcome, my pleasure :) – AdrienTorris Dec 09 '16 at 12:01
  • Was this discussed in chat? The link is dead. –  Nov 06 '19 at 04:41
  • @james-m I didn't remember but the link is dead to me too. What is your problem? How can I help you? – AdrienTorris Nov 07 '19 at 09:05
  • 1
    @AdrienTorris I've run into a few of those and just wondered if the discussions are being deleted (by the user who created the chat? by mods?) or didn't happen at all. –  Nov 07 '19 at 12:31
  • 1
    May i ask what does the service lifetime for MyDbContext set to in this case? – Phuc Vuong Jun 01 '20 at 14:48
  • I have this error: Exception thrown: 'System.ObjectDisposedException' in Microsoft.EntityFrameworkCore.dll – Hasan Mhd Amin Nov 15 '22 at 08:41