Why do we use DependecyInjection instead of OnConfiguring Method in the DbContext? - c#

I have a class called StudentDbContext. I call theOnConfiguring method in it. I saw that dependency injection is used in some training videos. Here we already use context once. Why should I use dependency injection instead of OnConfiguring?
Option-1
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySQL("...");
}
Option-2
public StudentDbContext(DbContextOptions<StudentDbContext> context) : base(context)
{
}

protected override void OnConfiguring(
DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySQL("...");
}
With this approach, you decide exactly which provider (MySQL) and which connection-string to use within StudentDbContext directly. If you want to change this to use a different provider or connection-string, you'll have to modify the StudentDbContext class directly. For example, consider a situation where you want to use a different provider for Development vs Production.
public StudentDbContext(DbContextOptions<StudentDbContext> context)
: base(context) { }
This approach allows you to configure both the provider and the connection-string from outside of the StudentDbContext.
Using Dependency Injection, you'll see something like this in Startup.ConfigureServices (or Program.cs with ASP .NET Core 6):
services.AddDbContext<StudentDbContext>(options =>
{
options.UseMySQL("...");
});
This second approach becomes more powerful when you want to use different providers/connection-strings (or even other settings) based on an environment, for example:
if (hostEnvironment.IsDevelopment())
{
services.AddDbContext<StudentDbContext>(options =>
{
options.UseMySQL("...");
});
}
else
{
services.AddDbContext<StudentDbContext>(options =>
{
options.UseSqlServer("...");
});
}

With dependency injection the DbContext doesn't need to know about the actual database in use. Furthermore, the DbContext doesn't even need to know which configuration settings should be used for establishing database connections.
This makes your code more independent. This e.g. makes your code more testable. There are other advantages, too.

you can also configure both approaches for use
db context
protected override void OnConfiguring(
DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseMySQL("...");
}
}
or startup configuration
services.AddDbContext<StudentDbContext>(options =>
{
options.UseMySQL("...");
});
in this case if you don't configure the startup configuration or if you are using you db context in another project without dependency injection will be uses a local db contex configuration, otherwise global.

Related

Entity Framework in child project not reading connectionstring sent to it

I have created a separate Entity Framework project (with .NET 6) to be used with many different solutions. However, when I use it as a child project, it won't read the connection strings sent to it.
Here's what I mean: let's say the projects are called UserProject and EFProject.
EFProject is a class library which includes a DBContext and all the models representing the database.
It includes this class:
public partial class MyDataContext : DbContext
{
public MyDataContext()
{
}
public MyDataContext (DbContextOptions<MyDataContext> options)
: base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer("test database connectionstring");
}
In my main project, UserProject, I have added this to appsettings.json:
"ConnectionStrings": {
"UseThisConnectionString": "production connectionstring"
}
and in Program.cs I have added:
builder.Services.AddDbContext<MyDataContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("UseThisConnectionString"))
, ServiceLifetime.Singleton);
However, no matter what I try, the EFProject keeps using the test database connection string and not reading the connection string I send to it in the main project. It is like these two projects are not in talking terms about this.
How do I relay the proper connection string to the child project?
One approach is trying to check DbContextOptionsBuilder.IsConfigured property:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if(!optionsBuilder.IsConfigured)
optionsBuilder.UseSqlServer("test database connectionstring");
}
Otherwise options will always be overridden (also possibly you can just remove the OnConfiguring override completely).
P.S.
In my personal experience I highly recommend against registering EF database context as ServiceLifetime.Singleton.

How to get the Log property of DatabaseFacade?

When I googled for "entityframework" and "logging", this article popped up and several people from here have also mentioned the same article.
However, when I tried it, I can't seem to get the Log property. What am I missing?
My implementation of the DbContext (btw, this was generated by Entityframework's own scaffolding):
internal partial class SharedContext : DbContext
{
public SharedContext()
{
}...
}
Here's how I tried to use:
SharedContext context = new();
//I am getting CS1061 (DatabaseFacade does not contain a definition for Log....
context.Database.Log = Console.Write;
Please help. Thanks!
Your question is tagged with .NET 6 and EF Core while the article refers to EF 6 which is previous iteration of EF for .NET Framework. You should look into logging documentation for EF Core. For example using simple logging via overloading OnConfiguring method:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine);
Also probably you should consider setting up context via dependency injection (DbContext Lifetime, Configuration, and Initialization):
services.AddDbContext<SharedContext>(opts => opts // or AddDbContextFactory
.Use{YourDatabaseType}(...)
.LogTo(Console.WriteLine));

Entity framework migrations errors

I want to create a migration for existing entities.
I have a DataContext class
public class DataContext : DbContext
{
public DataContext()
{
}
public DataContext(DbContextOptions<DataContext> options) : base(options)
{
}
public DbSet<AppUser> Users { get; set; }
}
And I added it to the services in IServiceCollection extension method -
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
services.AddDbContext<DataContext>(options =>
{
options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly(typeof(DataContext).Assembly.FullName));
});
return services;
}
}
When I try to add a migration from Entity Framework command tool -
"dotnet ef migrations add InitialCreate" such error occurs:
No database provider has been configured for this DbContext. A
provider can be configured by overriding the 'DbContext.OnConfiguring'
method or by using 'AddDbContext' on the application service provider.
If 'AddDbContext' is used, then also ensure that your DbContext type
accepts a DbContextOptions object in its constructor and
passes it to the base constructor for DbContext.
Can't really understand what's wrong with my code. It actually works by overriding OnConfiguring method but I need a solution for this kind of approach. Any ideas?
Try to remove the default public constructor (the parameterless one).
When you execute the CLI command you have some additional options like --project and --startup-project. By specifying them it will enable the use of the host builder to create your DB context, and you don't need this parameterless constructor.
The ef cli is using reflection to look into your project and then executes the host builder setup in order to have a service builder. From there it will instantiate the DB context. If it doesn't work. It will fall back first to a design time factory. And finally will use the default constructor (which is happening in this case).

.NET Core C# MVC DBContext is Null in class file

I have a solution that has several 'projects' inside. One being a MVC Web App and the other being a class library.
In my MVC Web App Startup.cs I am registering the DbContext:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSession();
services.AddHttpContextAccessor();
services.AddMemoryCache();
services.AddSingleton<Controllers.HomeController>();
services.AddRouting(options => options.LowercaseUrls = true);
services.AddDbContext<AffiliateDatabaseContext>();
}
I have the Context in the class library all setup with the on configuring being setup to the connection string within the appsettings (works):
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
IConfigurationRoot configuration = new ConfigurationBuilder().SetBasePath(AppDomain.CurrentDomain.BaseDirectory).AddJsonFile("appsettings.json").Build();
optionsBuilder.UseSqlServer(configuration.GetConnectionString("CustomerBilling"));
}
}
I am trying to use the DBContext within a class file in my class library doing a DI:
private static AffiliateDatabaseContext affilContext;
public PromotionLogic(AffiliateDatabaseContext _affilContext)
{
affilContext = _affilContext;
}
public static IEnumerable<AffPromotion> GetAllPromotions()
{
return affilContext.AffPromotion.Include("Company").Include("PromotionType").Include("Audience").Include("PromotionSportLink").Include("AffPromotionImage").OrderBy(x => x.Priority).AsEnumerable();
}
However it seems affilContext is NULL and I keep getting the following error:
System.NullReferenceException: 'Object reference not set to an
instance of an object.'
Am I doing this wrong? Do I need an interface or something instead? It seems to work fine within a controller.
Would another way be just passing the DBContext through as an arguement to the function?
The issue is that you are assigning to a static field in an instance constructor. Since you never invoke the constructor, the field remains null. The solution is to use an instance field (no static).
Thus:
private static AffiliateDatabaseContext affilContext;
must be:
private AffiliateDatabaseContext affilContext;
Once you make this change, any static method using affilContext will need to be changed to be non-static (i.e. remove static) as well.
You may be tempted to leave the variable as static. This is not a good idea, generally speaking, with regards to DB contexts. They are not designed to be long lived, and they aren't thread-safe. Thus static DB contexts are not appropriate for use in a web app.

When do we need IOptions?

I am learning DI in .Net Core and I do not get the idea about the benefit of using IOptions.
Why do we need IOptions if we can do without it?
With IOptions
interface IService
{
void Print(string str);
}
class Service : IService
{
readonly ServiceOption options;
public Service(IOptions<ServiceOption> options) => this.options = options.Value;
void Print(string str) => Console.WriteLine($"{str} with color : {options.Color}");
}
class ServiceOption
{
public bool Color { get; set; }
}
class Program
{
static void Main()
{
using (ServiceProvider sp = RegisterServices())
{
//
}
}
static ServiceProvider RegisterServices()
{
IServiceCollection isc = new ServiceCollection();
isc.Configure<ServiceOption>(_ => _.Color = true);
isc.AddTransient<IService, Service>();
return isc.BuildServiceProvider();
}
}
Without IOptions
interface IService
{
void Print(string str);
}
class Service : IService
{
readonly ServiceOption options;
public Service(ServiceOption options) => this.options = options;
public void Print(string str) => Console.WriteLine($"{str} with color : {options.Color}");
}
class ServiceOption
{
public bool Color { get; set; }
}
class Program
{
static void Main()
{
using (ServiceProvider sp = RegisterServices())
{
//
}
}
static ServiceProvider RegisterServices()
{
IServiceCollection isc = new ServiceCollection();
isc.AddSingleton(_ => new ServiceOption { Color = true });
isc.AddTransient<IService, Service>();
return isc.BuildServiceProvider();
}
}
In .Net core, it is recommended that all your configurations should be strongly typed based on their use cases. This will help you to achieve separate of concerns.
Practically, you can achieve the same thing without using IOptions as you stated.
So, if I go back one step and if we have a look at all the available options in .net core configuration:
1. Raw Configuration[path:key]
You can directly access IConfiguration instance and provide path of JSON key in the accessor part, and the configuration value would be returned.
This is not good approach because there is no strong typing here while reading the configuration.
2. IOptions binding to a Config Section
You can use IOptions implementation (which you already know).
This is better because you can have a single class with all related configurations. The IOptions interface provides you additional benefits.
As far as I understood, this IOptions interface decouples your configuration from the actors who are reading the configuration and thereby you can use some additional services from .net core framework.
Please refer MSDN article for details about the benefits.
You can also refer to the twitter conversation at this blog. In that blog, Rick also explains that he could not find any practical case on how this approach is different from the 3rd approach below - as generally the configurations are not dynamic and they are done only once before the application startup.
3. Configuration.Bind() to bind to a Config Section
You can use .Bind call to bind a configuration section to a POCO class. You get strongly typed object. Here if multiple actors are using the configurations, they will not get additional services provided by IOptions interface.
I know this is not exactly pointing out the difference. But I am sure this will bring little more clarity on deciding your preference.
Short answer: yes, you can do without it and access your setting directly from ConfigurationManager.AppSettings, like in this answer.
Slightly longer answer: especially when you want to test your (Console) Application, it might be nice to inject services and settings.
ASP.NET Core comes with DI included and it will be set up in your Startup.cs. DI can be used in Console Applications, but it might be hard(er) to set it up, as the default application has no plumbing for it. I wrote a small blog on how to setup DI with IOptions configuration for .NET Core Console Applications.
By itself IOptions<TOptions> doesn't add anything, in your examples. However, it allows you to use the OptionsBuilder API, should you need any of its features:
Configuring your Options objects using other services;
Validate your Options object;
Add post-configuration to your Options object.
From my experience, all of these use cases are quite exotic, though. For the basic use case, where you want to bind a section of your IConfiguration to an Options object, you can just inject the Options object directly, as per your second example. Not using the IOptions<T> interface has the benefit of being less cumbersome to unit test - you don't need to mock it.
However, if you want your Options values to automatically update at runtime as the configuration sources change, you will need to make use of a wrapper interface. But IOptions<T> itself doesn't do that - you'll need to use either IOptionsSnapshot<T> or IOptionsMonitor<T> for that.

Categories