This approach requires that each derived controller class also implements a constructor that takes any arguments required by my controller base class. Not only does this seem like a lot of extra typing, which I have to remember to add to any new derived class, but it also means that if I change the data passed to the constructor, then I have to modify the constructor in each derived class of the controller.
This is one of the approaches (Heavy Controller) that you can use EF in your application, and IMO is not the cleanest approach. And you correctly noticed your flaws.
If we attribute this approach to the design principle, it violates the principle of single responsibility, since it is expected that the controller will do more (select or update the database) than just collecting data and returning the corresponding representation with the data. What about business rules, whether the controller will apply it, if you need to send an email, whether the controller will do it. You should have another class of business classes specifically designed for a set of requirements, for example. EmailHelper would send emails.
It also violates the Open Close principle, since you need to change the constructors every time you change the input parameter.
This gives me a DBContext for all my controller classes. But what about the other classes in my model that need DBContext? Should I need to manually pass the instance in DBContext to all of these classes?
Regarding dependency injection, one of the goals is to inject dependency where it is needed directly. If you have a model class that requires a DbContext, you should enter it in your model class constructor (mainly to support DI environment support properties, but the constructor remains a favorite approach).
With the DI Framework, you will configure the dependencies in one place (application initialization code), and then each class that needs the dependency just accepts it in the constructor.
A DI container can be compared to a dictionary, where the key is the interface and the value is the finished object. After installing it, you can request any object at any time using the right key through your application.
Or is there a way to use DI for each of these classes to get their own copy of DBContext?
The DI framework supports various methods of instantiating, allowing you to control the lifetime of an instance. Usually, for each request, for stream and single point. More details here . If you want each controller to receive a copy of DbContext, you can use the configuration of each request when configuring the creation of an instance of DbContext.
Alternative solution:
In most of my MVC applications, I had a service level (a set of classes that apply a business rule). DbContext was introduced to each of these classes (not exactly DbContext, but IDataContext). The controllers have been introduced with a class of service that they need to retrieve or update data.
Distracted DbContext behind IDataContext, I could set up the stub data context in my test or tomorrow, if I want to switch from EF to NHibernate or something more intelligent DI Framework, I just need to implement IDataContext and change the code dependency initialization.
Hope this helps.