.Net Core 2.1 Web and Console DbContexts - c#

Im not known for my clarity when asking questions, so forgive me, also i have no formal training for any of this but im stumped.
I am mid upgrade from .Net 4.7.1 to .Net Core 2.1, my solution consists of 2 parts, an IIS Web Application for MVC, and a Console Application, the IIS App displays data, and the console application does all the actual processing.
Before i started this port for my console app when i needed stuff from the database i would simply
using (var db = new ApplicationDbContext())
{
SomethingModel model = db.X.First(x => x.Whatever == whatever);
}
And just like that i have the data i want from the database, butttt do you think i can do that with Core 2.1 can i hell.
I got all the code ported all the refrences resolved and so far as i can tell its ready to run. Except i cant call data from the database and im stumped, google just shows code first and ef stuff, or i dont know what im really asking.
So if anyone can help its much appreciated
-- Update 1---
The error is An Object refrence is required for the non-static field, metho or property Program._db
The DbModel is defined in Data/ApplicationDbContext.cs for the IIS App and is as follows
public ApplicationDbContext(DbContextOptions<ApplicationDbContext>
options)
: base(options)
{
}
-- Program.cs for Console App
class Program
{
private ApplicationDbContext _db { get; }
public Program(ApplicationDbContext context)
{
_db = context;
}
static void Main(string[] args)
{
new ExecutionEngine(_db).Run();
}
}

The previous way you wrote the code (using) was never a good idea. Your context should be request-scoped; using using can lead to all sorts of issues with entity tracking and totally destroys all the helpful caching EF does. The best method for getting a context instance was always dependency injection via a DI container.
ASP.NET Core uses dependency injection for everything, and because of this EF Core's DbContext is designed to be dependency injected. In this regard, it no longer uses a default constructor out of the box, which is why your old code is failing (it depends on there being a default constructor).
Long and short, do things right and inject your context. It looks like you're attempting to do this based on your update. However, you cannot inject into something like Program. This is the entry point for your application, which means literally nothing exists yet. If you take a look at your web app, you'll notice that Program there sets up the web host builder (using Startup) and then builds and runs it. Behind the scenes this is doing a bunch of stuff, including setting up the service collection. This is what you need to do in your console app (set up the service collection). That's relatively straight forward:
class Program
{
static void Main(string[] args)
{
var serviceProvider = new ServiceCollection()
.AddDbContext<ApplicationDbContext>(o =>
o.UseSqlServer("connection string"))
.BuildServiceProvider();
var context = serviceProvider.GetRequiredService<ApplicationDbContext>();
new ExecutionEngine(context).Run();
}
}
Now, this is a bit of overkill just based on the code you have here. You can simply new up an instance of your context via DbContextOptionsBuilder:
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlServer("connection string")
.Options;
var context = new ApplicationDbContext(options);
However, using the service collection allows you to handle more advanced scenarios and better reuse your instances of things like your context across your codebase. Also it's worth mentioning that you should probably consider integrating configuration providers as well, so you don't need to hardcode your connection string. That's also relatively straight-forward:
var config = new ConfigurationBuilder()
.SetBasePath(Path.Combine(AppContext.BaseDirectory))
.AddJsonFile("appsettings.json", optional: true)
.Build();
You might also want to add environment-specific configuration:
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
Then:
.AddJsonFile($"appsettings.{environment}.json", optional: true);
This is just the same as doing all this in a web app, so you can add whatever type of configuration you like.

Related

ASP.NET Core : Why do we need IDesignTimeDbContextFactory?

I have an ASP.NET Core application and all I have is the DataContext, I don't have IDesignTimeDbContextFactory implemented.
public class DataContext : DbContext, IUnitOfWork
{...}
With that I can do Add-Migration, Update-Database & Script-Migration.
However, I came across an another project where they have implemented IDesignTimeDbContextFactory, mentioned that this is to generate migration classes.
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<CodingBlastDbContext>
{
public CodingBlastDbContext CreateDbContext(string[] args)
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var builder = new DbContextOptionsBuilder<CodingBlastDbContext>();
var connectionString = configuration.GetConnectionString("DefaultConnection");
builder.UseSqlServer(connectionString);
return new CodingBlastDbContext(builder.Options);
}
}
I wonder why this is needed? especially the first project works without implementing the IDesignTimeDbContextFactory..
Docs have some explanation on when you can leverage the design-time factory:
A design-time factory can be especially useful if you need to configure the DbContext differently for design time than at run time, if the DbContext constructor takes additional parameters are not registered in DI, if you are not using DI at all, or if for some reason you prefer not to have a CreateHostBuilder method in your ASP.NET Core application's Main class.
The only use case I personally encountered was when DbContext was moved into a separate library and we did not want to run CreateHostBuilder for context designing purposes (startup involved some relatively heavy stuff and we didn't want to invoke that). Like for example here.

Why is my EF Core DbContext not bound by DI?

I have an Azure Function App with a function that runs on a blob trigger. I've proven that this function can run through the Azure Portal and responds to this blob trigger without issues... or at least it did.
Now that I've added functionality which makes use of EF Core (2.2.4), it gives me the following error, both when debugging locally and when publishing to Azure:
Microsoft.Azure.WebJobs.Host: Error indexing method 'ParseThings'. Microsoft.Azure.WebJobs.Host: Cannot bind parameter 'context' to type AvastusContext. Make sure the parameter Type is supported by the binding. If you're using binding extensions (e.g. Azure Storage, ServiceBus, Timers, etc.) make sure you've called the registration method for the extension(s) in your startup code (e.g. builder.AddAzureStorage(), builder.AddServiceBus(), builder.AddTimers(), etc.).
I have a Startup class as instructed by Azure Function App documentation here, and have followed their example to the letter, aside from the following line in place of their configured example services:
[assembly: FunctionsStartup(typeof(AvstFunctionApp.Startup))]
namespace AvstFunctionApp
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddDbContext<AvastusContext>(options => options.UseSqlServer(Environment.GetEnvironmentVariable("AvastusDb")));
}
}
}
And the beginning of my function:
public static class ParseThings
{
[FunctionName("ParseThings")]
public static void Run([BlobTrigger("summaries/{name}", Connection = "StorageConnectionString")]Stream myBlob, string name, ILogger log, AvastusContext context)
I can confirm that the AddDbContext line is getting hit in a debugger, so presumably there's some bug happening behind the scenes here, or I'm doing something incredibly silly.
Things I've tried that haven't worked include:
Adding .BuildServiceProvider(true) to the AddDbContext line
Using WebJobsStartup instead of the more recently advertised FunctionsStartup
Downgrading to .NET Core 2.2.0
Changing the Function class and Run method from static to instance
Fixing incorrect namespace of the injected AvastusContext
It's also worth noting that there are two other functions in this Function App project which don't seem to have any serious issues, and I've been able to get dependency injection working using a similar method with EF Core for another (ASP.NET Core MVC) project in this solution.
Thank you in advance for any help anyone can provide!
P.S. I find it incredibly weird that there hasn't been anything describing this situation with the later versions of .NET Core, Azure Function Apps, and EF Core on the interwebs, which leads me to believe that this might be a simple mistake. Hopefully not.
perhaps one solution can be you can try injecting IServiceProvider in your function instead of AvastusContext like I have injected in the repository class below:
private readonly IServiceProvider serviceProvider;
public SomeRepository(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
using var context = this.serviceProvider.GetService<XYZDBContext>();
This will provide a context object to you. Also, Not sure why you are trying to access context in the function directly for good practice have a context class defined, and maintain repository to do any CRUD operation in the code.
Startup.cs you can add extra configurations like :
builder.Services.AddDbContext<XYZDBContext>(
options =>
{
options.UseSqlServer(
conn,
sqlServerOptionsAction:
sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(maxRetryCount: 3, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
}, ServiceLifetime.Transient);
this configuration works perfectly fine in my current solution. Try this out.
Function app can't resolve dbcontext in functions as it can only resolve BindingContext. You need to create custom bindings to use dbcontext directly in function app.
Other way to get dbcontext injected via DI is to pass it to constructor and using a class level variable in the function.
public class ParseThings
{
private AvastusContext _context;
public ParseThings(AvastusContext context){
_context = context;
}
[FunctionName("ParseThings")]
public void Run([BlobTrigger("summaries/{name}", Connection = "StorageConnectionString")]Stream myBlob, string name, ILogger log){
// use _context here
}
}
If it still doesn't resolve you might want to look it into whether the functionsStartup is configured properly

Microsoft Dependency Injection Documentation

In the documentation for dependency injection I notice the following line.
The MVC framework will automatically look at the service provider to
register our dependency in the Controller.
They then provide a basic example with constructor injection, not their example but in essence this.
public class Example
{
private IFooFactory foo;
public Example(IFooFactory foo) => this.foo = foo;
public void SampleUse()
{
using(var context = foo.Create())
context.DoSomething();
}
}
If you have a console application, by default it will not look at the service provider to register your dependency with the concrete implementation. Is there a way to simulate that? Otherwise the console application will require you to do something along these lines:
public static Main(string[] args)
{
// Stuff to prepare the application and build service provider.
var service = serviceProvider.GetService<IFooFactory>();
using(var context = service.Create())
context.DoSomething();
// OR
var fooFactory = serviceProvider.GetService<IFooFactory>();
new Example(fooFactory).SampleUse();
}
Which creates the problem of having to pass IFooFactory or pulling things into the main that you may wanted separated for structure. How can I make the console application look at the provider when a new class is created with a defined interface?
You have to create everything manually as the framework is not there to automagically do it for you.
var services = new ServiceCollection();
services.AddTransient<IFooFactory, FooFactory>();
services.AddTransient<Example>();
IServiceProvider serviceProvider = services.BuildServiceProvider();
Example example = serviceProvider.GetService<Example>();
example.SampleUse();
While not ideal, it is usually the way shown in most examples where DI is configured manually.
When you inspect the framework DI integration, behind the scenes it does the exact same thing during startup.
You could probably write your own code to inspect available types, but that is a very broad task to tackle on your own.
Reference Dependency injection in ASP.NET Core
Default service container replacement
The built-in service container is meant to serve the needs of the
framework and most consumer apps. We recommend using the built-in
container unless you need a specific feature that it doesn't support.
Some of the features supported in 3rd party containers not found in
the built-in container:
Property injection
Injection based on name
Child containers
Custom lifetime management
Func<T> support for lazy initialization

Entity Framework Core 1.0 Connection Strings

We are working on a vary large ASP.NET Core MVC 1.0 application. We have 4-tiers to each of our applications as follows:
DTO
Repository (Entity Framework - Code First)
Service (Business Logic)
MVC (UI-MVC)
Currently, in our repositories, which handle all database operations we have hard coded the database connection strings in the DbContext as follows:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
optionsBuilder.UseSqlServer("Data Source=somedatabase.database.windows.net;Initial Catalog=database;Integrated Security=False;User ID=username;Password=password;Connect Timeout=60;Encrypt=True;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False;MultipleActiveResultSets=true");
}
This project is outside the MVC project as a standalone ASP.NET Core 1.0 project. It also has a empty Program.cs file in it which seems to be required to execute the code-to-database command lines (dotnet ef migrations add and dotnet ef database update).
The reason we have a hard coded connection string in the DbConext is because when we use the following code, we get an object reference not set to an instance to an object exception, when executing the dotnet ef commands.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
optionsBuilder.UseSqlServer(ConfigurationManager.ConnectionStrings["StandardDatabase"].ConnectionString);
}
However, since we have a Program.cs, if we add a Debug.WriteLine for the connection string and run the project, it does return the correct connections string and if we set the connection string in the appsettings.json file in the UI, the UI will successfully connect as well.
THE ISSUE:
The above mentioned stack is what we use for several "Micro Apps", which means we have several projects that connect to several databases. We also want to take advantage of Development, Staging and Production connection strings.
If we use Configuration Manager Connection String, everything is good for daily operations; however, when ever we want to utilize Entity Frameworks code to database command lines, we need to go in to each repository we want to update and change the DbContext to a hard coded connection string, execute the commands, then change them back to when done, which becomes quite troublesome.
THE QUESTION:
Are we just doing this wrong, is there a preferred practice for setting up an Entity Framework Core 1.0 stack which allows us not to manually have to change the DbContext but take advantage of configuration files across the board?
Any direction would be appreciated!
EF Core is intended to be configured via dependency injection. Dependency injection keeps your DbContext clean, and independent of implementation details of the environment.
Your initial solution of hard-coding connection strings tightly coupled the DbContext to the knowledge of where the database is located. That's obviously a problem. But your proposed solution tightly couples the DbContext to the knowledge of a particular configuration file. That, too, is a problem.
To keep the DbContext independent of environmental details, create a constructor that takes a DbContextOptions parameter and calls the base class constructor.
public class MyContext : DbContext
{
public MyContext(DbContextOptions options) :
base(options)
{
}
}
Do this instead of overriding OnConfiguring. Then initialize it in the Startup.cs of your host application. That's where the knowledge of the configuration file belongs.
public class Startup
{
private IConfigurationRoot _configuration;
public Startup(IHostingEnvironment env)
{
_configuration = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json")
.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfigurationRoot>(_configuration);
services.AddDbContext<MyContext>(options => options
.UseSqlServer(_configuration.GetConnectionString("MyContext")));
}
}
Now you can use your DbContext from anywhere.
ANSWER: I was making this much more difficult then it actually was. I followed Juunas' advise and added in the following code in my Repository DbContext Class:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// get the configuration from the app settings
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
// define the database to use
optionsBuilder.UseSqlServer(config.GetConnectionString("StandardDatabase"));
}
Which works perfect with the dotnet ef command line tools and far as the multiple environment setup goes with my MVC UI sticking with the following default code in my startup.cs works great as well.
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddJsonFile("project.json", optional: true, reloadOnChange: true);
IDbContextFactory might also help. EF Command Line Tools and DI can use this factory to create instances of your DBContext. Design Time services (e.g. Migrations) will discover implementations of this interface that are in the same assembly as the derived context.
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace MyProject
{
public class BloggingContextFactory : IDbContextFactory<BloggingContext>
{
public BloggingContext Create()
{
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlite("Filename=./blog.db");
return new BloggingContext(optionsBuilder.Options);
}
}
}

is there another way of changing Database Instance in Autofac

I have an application that use multiple Database.
i found out i can change that by using the connection builder. like so :
var configNameEf = "ProjectConnection";
var cs = System.Configuration.ConfigurationManager.ConnectionStrings[configNameEf].ConnectionString;
var sqlcnxstringbuilder = new SqlConnectionStringBuilder(cs);
sqlcnxstringbuilder.InitialCatalog = _Database;
but then i need to change the autofac Lifescope of UnitOfWork so that it will now redirect the request to the good Database instance.
what i found out after quite a while is that i can do it like this from a DelegatedHandler :
HttpConfiguration config = GlobalConfiguration.Configuration;
DependencyConfig.Register(config, sqlcnxstringbuilder.ToString());
request.Properties["MS_DependencyScope"] = config.DependencyResolver.GetRequestLifetimeScope();
The question is, is there any other way to do that, that change the MS_DependencyScope parametter of the request. This solution work but i think it is kind of shady.
here is the registry in DependencyConfig:
public static void Register(HttpConfiguration config, String bdContext = null)
{
var builder = new ContainerBuilder();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.Register(_ => new ProjectContext(bdContext)).As<ProjectContext>().InstancePerApiRequest();
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerApiRequest();
// Register IMappingEngine
builder.Register(_ => Mapper.Engine).As<IMappingEngine>().SingleInstance();
config.DependencyResolver = new AutofacWebApiDependencyResolver(builder.Build());
config.DependencyResolver.BeginScope();
}
The way the question is described and the way the answer to my comment sounds, you have the following situation:
The application uses per-request lifetime units of work. I see this from your registrations.
Only one database is used in the application at a given point in time. That is, each request doesn't have to determine a different database; they all use the same one until the connection string changes. This is seen in the way the database is retrieved from using a fixed application setting.
The connection string in configuration may change, at which point the database used needs to change.
Assuming I have understood the question correctly...
If the app setting is in web.config (as it appears), then changing the string in web.config will actually restart the application. This question talks about that in more detail:
How to prevent an ASP.NET application restarting when the web.config is modified?
If that's the case, you don't have any work to do - just register the database as a singleton and when the web.config changes, the app restarts, re-runs the app startup logic, gets the new database, and magic happens.
If the app setting is not in web.config then you should probably create a project context factory class.
The factory would serve as the encapsulation for the logic of reading configuration and building the connection to the database. It'll also serve as the place to cache the connection for the times when the setting hasn't changed.
The interface would look something like this:
public interface IProjectContextFactory
{
ProjectContext GetContext();
}
A simple implementation (without locking, error handling, logging, and all the good stuff you should put in) might be:
public class ProjectContextFactory : IProjectContextFactory
{
private ProjectContext _currentContext = null;
private string _currentConnectionString = null;
private const string ConnectionKey = "ProjectConnection";
public ProjectContext GetContext()
{
// Seriously, don't forget the locking, etc. in here
// to make this thread-safe! I'm omitting it for simplicity.
var cs = ConfigurationManager.ConnectionStrings[ConnectionKey].ConnectionString;
if(this._currentConnectionString != cs)
{
this._currentConnectionString = cs;
var builder = new SqlConnectionStringBuilder(cs);
builder.InitialCatalog = _Database;
this._currentContext = new ProjectContext(builder.ToString());
}
return this._currentContext;
}
}
OK, now you have a factory that caches the built project context and only changes it if the configuration changes. (If you're not caching the ProjectContext and are, instead, caching the database connection string or something else, the principle still holds - you need a class that manages the caching and checking of the configuration so the change can happen as needed.)
Now that you have a cache/factory, you can use that in your Autofac registrations rather than a raw connection string.
builder.RegisterType<ProjectContextFactory>()
.As<IProjectContextFactory>()
.SingleInstance();
builder.Register(c => c.Resolve<IProjectContextFactory>().GetContext())
.As<ProjectContext>()
.InstancePerRequest();
The ProjectContext will now change on a per request basis when the configured connection string changes.
Aside: I see odd stuff going on with the request lifetime scope. I see in your registration that you're creating your own request lifetime scope. With this method you shouldn't have to do that. If, however, you find that you still need to (or want to), you need to make sure both the originally-created lifetime scope and the one you created are disposed. Lifetime scopes do not get automatically disposed and do hang onto object references so they can handle disposal. There is a high probability that if you're not handling this properly then you have a subtle memory leak. The Autofac Web API integration will take care of creation and disposal of the request lifetime for you, but if you change out the request lifetime, odd things are going to happen.

Categories

Resources