Accessing dbContext in a C# console application - c#

I have tried to figure this out, but I am stuck.
I have a Net Core 2 application with Service/Repo/Api/Angular layers - but now I want to 'bolt on' a console application and access all the goodies I have already built up. I seem to be in a mess of static objects and DI and null parameters. Anyway, here is a simplified version of my code.
namespace SimpleExample
{
class Program
{
private static ApplicationDbContext _appDbContext;
public Program(ApplicationDbContext appDbContext)
{
_appDbContext = appDbContext;
}
static void Main(string[] args)
{
var instance = new Program(); // this doesn't work!
var instance = new Program(_appDbContext); // neither does this!
instance.GetData();
}
private void GetData()
{
Console.WriteLine("Let's read some data! Press a key to continue.");
Console.ReadLine();
var data = "my data";
var result = GetId(data);
}
private string GetId(string original)
{
var data = _appDbContext.Data
.Where(x => x.Name == original.Trim)
.FirstOrDefault();
return data;
}
}
}
I am getting the classic
'An object reference is required for the non-static field'
error. Then from investigating on here I changed things to static and then everything becomes null.
It's not just the DbContext I am trying to inject. I'm also trying to inject
private ManagerService _managerService;
but getting same errors.
Update
If I try
private static ApplicationDbContext _appDbContext = new
ApplicationDbContext();
as suggested a few times below, then I get the error
There is no argument given that corresponds to the required formal
parameter 'options' of
'ApplicationDbContext.ApplicationDbContext(DbContextOptions)'

OK, I have figured this out, and I'll post my answer for anyone else struggling in this situation.
When you launch the console app, your normal startup.cs doesn't execute, so you have to put a lot of that code in your console app.
private static SiteService _siteService;
private static ApplicationDbContext _appDbContext;
public static void Main()
{
var services = new ServiceCollection();
services.AddTransient<ISiteInterface, SiteRepo>();
services.AddTransient<SiteService>();
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer("blah-blah"));
var serviceProvider = services.BuildServiceProvider();
_siteService = serviceProvider.GetService<SiteService>();
_appDbContext = serviceProvider.GetService<ApplicationDbContext>();
GetData();
}
and now your _appDbContext will be available throughout the rest of your console app.
Hope that helps!

Basically, if you do not plan extensive usage of DbContext nor use DI, there is no need for ServiceProvider. Just remember to make DbContext instance short living and use it for single unit-of-work, not longer.
Your context may look like this:
using Microsoft.EntityFrameworkCore;
namespace YourNamespace;
public class ApplicationContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Your conn string");
}
public DbSet<YourType> YourEntity { get; set; }
}
You can pass conn string by ApplicationContext ctor as well. This is nicely explained here by Microsoft .
Then you can utilise your ApplicationContext like this:
// Unit-of-work closed in using statement
// Here you can query/update your DbContext
using (var dbContext = new ApplicationContext())
{
var queryResult = dbContext.YourEntity.Where(....);
}
You can prepare number of such units-of-work as separate methods for querying a database.
Your repository service can consist of these methods.
Then you can instantiate the service as needed.

Related

Error when trying to 'Seed' mock db into mock dbContext

I've been searching stack overflow for a solution to this problem but I haven't been able to find that one that fixes my error yet. I'm attempting to write unit test for an API that I developed. I created a mock db and mock context for it but when I try to 'seed' my mock context I'm getting this error.
The call is ambiguous between the following methods or properties: 'AppointmentAPI.UnitTests.DbContextExtensions.Seed(AppointmentAPI.Appt_Models.ApptSystemContext)' and 'AppointmentAPI.UnitTests.DbContextExtensions.Seed(AppointmentAPI.Appt_Models.ApptSystemContext)' [AppointmentAPI.UnitTests, AppointmentAPI.UnitTests]
Not really sure what the problem is because it was working fine the other day with no error and then when I started working on it today the error appeared. I'm fairly new to C# and especially writing unit tests for a .net API so any help is greatly appreciated. I'll post my two files below.
DbContextExtensions.cs
namespace AppointmentAPI.UnitTests
{
using System;
using AppointmentAPI.Appt_Models;
public static class DbContextExtensions
{
public static void Seed(this ApptSystemContext dbContext)
{
// add entities for dbContext instance
dbContext.AppointmentSlots.Add(new AppointmentSlots
{
SlotId = 1,
Date = Convert.ToDateTime("2020-03-31 00:00:00.000"),
Time = TimeSpan.Parse("12:00:00.0000000"),
ApptJson = "{'fname':'Billy','lname':'Joel','age':70,'caseWorker':'Donna', 'appStatus':'finished'}",
Timestamp = Convert.ToDateTime("2020-02-24 12:00:00.000")
});
dbContext.AppointmentSlots.Add(new AppointmentSlots
{
SlotId = 6,
Date = Convert.ToDateTime("2020-07-24 00:00:00.000"),
Time = TimeSpan.Parse("10:00:00.0000000"),
ApptJson = "{'fname':'Michael','lname':'Smith','age':52,'caseWorker':'Donna', 'appStatus':'finished'}",
Timestamp = Convert.ToDateTime("2020-06-25 09:34:00.000")
});
dbContext.SaveChanges();
}
}
}
DbContextMocker.cs
namespace AppointmentAPI.UnitTests
{
using Microsoft.EntityFrameworkCore;
using AppointmentAPI.Appt_Models;
public static class DbContextMocker
{
public static ApptSystemContext GetApptSystemContext(string dbName)
{
// create option for DbContext instance
var options = new DbContextOptionsBuilder<ApptSystemContext>()
.UseInMemoryDatabase(databaseName: dbName)
.Options;
// create instance of DbContext
var dbContext = new ApptSystemContext(options);
// add entities in memory
dbContext.Seed(); <-- error happens here
return dbContext;
}
}
}
This exception is usually thrown when two or more methods are overloaded with same amount of arguments but in different types
eg:
static void Seed(this ApplicationDbContext dbContext, string word1, string[] array = null);
static void Seed(this ApplicationDbContext dbContext, string word1, string word2 = null);
dbContext.Seed("test word"); // will throw the exception or will show as an syntax error.
With the above two files i dont see any methods as such. A clean and build might work hopefully.

Ninject IntransientScope not working with DBContext?

Working with EntityFramework and Ninject i need to dispose the context each time a call to a repository is finished. I need that so each time, a new call to the database is made, instead of using the EF context scope.
Here is my repository for testing:
public class VehicleRepositoryTest : IVehicleRepository
{
private DBEntities _context;
public VehicleRepositoryTest(DBEntities context)
{
_context = context;
}
....
public List<TB_VEHICULO> GetAll()
{
return _context.TB_VEHICULO.ToList();
}
And here is how i implement the ninject module. I use "IntransientScope" with the idea of disposing the context after each call:
Kernel.Bind<DBEntities>().ToSelf().InTransientScope();
Kernel.Bind<IVehicleRepository>().To<Test.VehicleRepositoryTest>().InTransientScope();
The idea is that each time a call "GetAll()" a new context is created, so each time a call to database is made.
But it is not working. If i make a call to "GetAll()", and supose i get data A; then i change in database data A to data B, make a new call to "GetAll()", i still getting data A.
More Info:
My application is a WinForms application, to call the instantiate the injected objects i use the composition pattern:
public static class CompositionRoot
{
public static IKernel kernel { get; private set; }
public static void WireModule(INinjectModule module)
{
kernel = new StandardKernel(module);
}
public static T Resolve<T>()
{
return kernel.Get<T>();
}
}
and the call to the repository is like this:
_vehicleRepository = CompositionRoot.Resolve<IVehicleRepository>();
var test = _vehicleRepository.GetAll();
I was with the same problem.
My old code:
kernel.Bind(typeof(IUnitOfWork)).To<UnitOfWork>().WithConstructorArgument("context", kernel.Get<MyContext>());
My new Code:
kernel.Bind<DbContext>().To<MyContext>();
kernel.Bind(typeof(IUnitOfWork)).To<UnitOfWork>();
This work to me.

GetRequiredService<DbContextOptions<MovieContext>> v.s. GetRequiredService<MovieContext>

I am reading this tutorial and found two approaches used by the author to obtain MovieContext.
In SeedData.Initialize, MovieContext is obtained as follows.
public static class SeedData
{
public static void Initialize(IServiceProvider isp)
{
DbContextOptions<MovieContext> options =
isp.GetRequiredService<DbContextOptions<MovieContext>>();
using (var context = new MovieContext(options))
{
// trimmed for simplicity
}
}
}
But in Program.Main, the context is obtain as follows.
public class Program
{
public static void Main(string[] args)
{
IWebHost iwh = BuildWebHost(args);
using (IServiceScope iss = iwh.Services.CreateScope())
{
IServiceProvider isp = iss.ServiceProvider;
try
{
MovieContext context = isp.GetRequiredService<MovieContext>();
// trimmed for simplicity
}
}
}
}
Question
Is there any difference between
new MovieContext(isp.GetRequiredService<DbContextOptions<MovieContext>>());
and
isp.GetRequiredService<MovieContext>();
where isp is of type IServiceProvider ?
Is there any difference between the two approaches.
In the first example you manually instantiate the context and inject its explicit dependency by using the container to resolve and instantiate the options (Service Locator).
In the second example the container handles everything. It will resolve the option and inject it into the context when it is being resolved.
When do we need to do the former and the latter approach?
Totally a matter of preference. Both can be done as the end result is the same depending on how the context was registered with the IoC container.

Allow the end-user to switch the Entity Framework provider at runtime

Consider that I have configured EF with a .NET Core web app:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(...));
I can also download a package to support for example SQLite:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(...));
How can we allow a user to "select" the provider on app install? I mean - for example, in WordPress you can choose from a dropdown.
Is this possible in .NET Core? The only way I see is to restart the app only...
Here is an example on how you can implement a DbContextFactory or a DbContextProxy<T> which will create the correct provider and return it.
public interface IDbContextFactory
{
ApplicationContext Create();
}
public class DbContextFactory() : IDbContextFactory, IDisposable
{
private ApplicationContext context;
private bool disposing;
public DbContextFactory()
{
}
public ApplicationContext Create()
{
if(this.context==null)
{
// Get this value from some configuration
string providerType = ...;
// and the connection string for the database
string connectionString = ...;
var dbContextBuilder = new DbContextOptionsBuilder();
if(providerType == "MSSQL")
{
dbContextBuilder.UseSqlServer(connectionString);
}
else if(providerType == "Sqlite")
{
dbContextBuilder.UseSqlite(connectionString);
}
else
{
throw new InvalidOperationException("Invalid providerType");
}
this.context = new ApplicationContext(dbContextBuilder);
}
return this.context;
}
public void Dispose(){
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing){
if (disposing){
disposing?.Dispose();
}
}
}
Also make sure you implement the disposable pattern as show above, so the context gets disposed as soon as the factory gets disposed, to prevent the DbContext remaining in memory longer than necessary and free unmanaged resources as soon as possible.
Finally register the factory as scoped, as you would the context itself:
services.AddScopedd<IDbContextFactory, DbContextFactory>();
A more advanced and generic/extendable approach is by creating a IDbContextProxy<T> class which uses a bit of reflection to get the correct constructor and the DbContextOptionsBuilder to it.
Also possible to create a IDbContextBuilder which abstracts the provider creation.
public class SqlServerDbContextBuilder IDbContextBuilder
{
public bool CanHandle(string providerType) => providerType == "SqlServer";
public T CreateDbContext<T>(connectionString)
{
T context = ... // Create the context here
return context;
}
}
Then you can pick the correct provider w/o a hard coded if/else or switch block just by doing
// Inject "IEnumerable<IDbContextBuilder> builders" via constructor
var providerType = "SqlServer";
var builder = builders.Where(builder => builder.CanHandle(providerType)).First();
var context = builder.CreateDbContext<ApplicationContext>(connectionString);
and adding new types of provider is as easy as adding the dependencies and an XxxDbContextBuilder class.
See here, here or here for more information about this and similar approaches.
I think you can use repositories which are using a db context you specified and you can pass a parameter to context constructor to choose the endpoint. I am not sure on this but it might work for your situation.
I followed this article for repository pattern, I recommend to read it :)
http://cpratt.co/generic-entity-base-class/

Entity Framework initializer in console/library app

I am currently working on a project with has a console app and few library projects. One library project is a EF code first project which contains my models and the context:
public class MyDbContext: DbContext
{
public MyDbContext() : base("MyConnectionString")
{
}
public DbSet<File> Files { get; set; }
}
I also have a singleton class through which I want to access the database. The singleton looks like this:
public sealed class DbLogger : IDbLogger
{
private static readonly DbLogger instance = new DbLogger();
private static MyDbContext ctx = new MyDbContext();
static DbLogger() {
Database.SetInitializer<MyDbContext>(new DbInitializer());
}
private DbLogger() { }
public static DbLogger Instance
{
get {
return instance;
}
}
public void AddFile(string fileName)
{
ctx.Files.Add(new File() { FullPath = fileName });
}
}
The db initializer is very simple and just implements the CreateDatabaseIfNotExists. Nothing is done in Seed yet.
In the console all which references the library project I just want to use it as:
private DbLogger logger = DbLogger.Instance;
and call the logger from a Task using:
logger.AddFile("myFileName");
When the app gets to logger.AddFile call I get the following exception:
An exception of type 'System.InvalidOperationException' occurred in
EntityFramework.dll but was not handled in user code
Additional information: The context cannot be used while the model is
being created. This exception may be thrown if the context is used
inside the OnModelCreating method or if the same context instance is
accessed by multiple threads concurrently. Note that instance members
of DbContext and related classes are not guaranteed to be thread safe.
How can I delay the using of the context until the model was created?
I am currently a bit stuck with this and any idea on how to solve this would be appreciated.
Thank you!
I recommend this approach
public void AddFile(string fileName){
using(var ctx = new MyDbContext() ){
ctx.Files.Add(new File() { FullPath = fileName });
ctx.SaveChanges();
}
}
You should only use the DbContext when needed. Open the DB connection, interact with the DB and close the connection. The using statement take care of the opening and closing of the DB connection.
EDIT - updated with SaveChanges()
ad to #Kunukn answer:
I think that you should blame
private static MyDbContext ctx = new MyDbContext();
It was trying to acces context before database initializer run.
If you don't wan't to create new context on every AddFile() call, try create context in static constructor.
I see problem with line
Database.SetInitializer(new DbInitializer());
if you use
public void AddFile(string fileName){
using(var ctx = new MyDbContext() ){
ctx.Files.Add(new File() { FullPath = fileName });
ctx.SaveChanges();
}
}
then your purpose of singleton is not getting solved because it will create a new MyDbContext every time AddFile is called ( and this is recommended)
but even if you insist on having a single dbcontext object then you should create some
initialization fucntion and might be call it after object is created.
might be something like
private DbLogger logger = DbLogger.Instance;
logger.Initialize()

Categories