ASP.NET vNext EF7 dbContext issues - c#

I am starting a vNext project, and I'm having some issues kicking it off the ground. I have added a table to the ApplicationDbContext class, and it successfully created the table in the db (which in my case is in Azure). However, I can't seem to correctly instantiate a dbContext to use in my Controllers.
In my experience with previous ASP.NET EF projects, I could instantiate the ApplicationDbContext class without passing it any parameters, but in the case of vNext however, it seems to expect a number of things (IServiceProvider, and IOptionsAccessor<DbContextOptions>). I have tried creating a parameter-less constructor, but the App breaks due to not knowing what connection strings to use. My code is below -- as you see in the OnConfiguring(DbContextOptions options) override, I force the connection string in via the DbContextOptions, but that's obviously not ideal, and I feel like I'm just not understanding where those two IServiceProvider, and IOptionsAccessor parameters need to come from.
Thanks for any help!
namespace Project.Models
{
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser
{
public string CompanyName { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
private static bool _created = false;
public DbSet<Business> Businesses { get; set; }
public ApplicationDbContext()
: base()
{
if (!_created)
{
Database.EnsureCreated();
_created = true;
}
}
protected override void OnConfiguring(DbContextOptions options)
{
var configuration = new Configuration();
configuration.AddJsonFile("config.json");
configuration.AddEnvironmentVariables();
options.UseSqlServer(configuration.Get("Data:DefaultConnection:ConnectionString"));
}
public ApplicationDbContext(IServiceProvider serviceProvider, IOptionsAccessor<DbContextOptions> optionsAccessor)
: base(serviceProvider, optionsAccessor.Options)
{
// Create the database and schema if it doesn't exist
// This is a temporary workaround to create database until Entity Framework database migrations
// are supported in ASP.NET vNext
if (!_created)
{
Database.EnsureCreated();
_created = true;
}
}
}
}

IServiveProvider and IOptionAccessor are injected by the Dependency Injection
the ASP.Net Core DI has limitation, you cannot have more than one constructor.
Read this: http://blogs.msdn.com/b/webdev/archive/2014/06/17/dependency-injection-in-asp-net-vnext.aspx

Related

How can I use a single controller with different DBContexts?

I have ~70 databases which are all exactly the same structure.
What I am attempting to do is to create a quick Blazor (WASM/hosted) site on .net 6 which can access all of these sites using EF. Blazor is probably unimportant because all of the magic is happening in the 'hosted' portion which is really just a .Net API project.
So far, I have created a CommonDBContext which inherits DBContext and then individual DBContexts for each database which inherit CommonDBContext.
CommonDbContext.cs
public partial class CommonDbContext : DbContext
{
public CommonDbContext(DbContextOptions<CommonDbContext> options) : base(options)
{
}
protected CommonDbContext(DbContextOptions options) : base(options)
{
}
+2600 lines of EF scaffolding code
DB1-Context.cs
public partial class DB1Context : CommonDbContext
{
public DB1Context(DbContextOptions<DB1Context> options)
: base(options)
{
}
}
These various context are then injected in Program.cs
builder.Services.AddDbContext<DB1Context>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DB1"));
});
builder.Services.AddDbContext<DB2Context>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DB2"));
});
I am not convinced this is a wonderful solution for 70 databases but it is a start. This approach seems to work perfectly - I can create a controller and whichever DB context I use, it seems to pull from the proper database.
Here's the problem
How can I select which DBContext I want to use in the controller? Building ~70 duplicate controllers seems very wrong but I haven't figured out how to pass in the DBContext from my request to the controller.
DopplegangerController.cs
[Route("api/[controller]")]
[ApiController]
public class DopplegangerController: ControllerBase
{
private DB1Context_context; // <-- RIGHT HERE
public DopplegangerController(DB1Contextcontext)
{
_context = context;
}
// GET: api/<DopplegangerController>
[HttpGet]
public List<Stuffs> Get()
{
return _context.Stuffs.ToList();
}
In you Program.cs when configuring your services, you can give a function that will fetch the connection string depending on your settings. This function will be call each time the DbContext needs to be injected.
builder.Services.AddDbContext<CommonDbContext>(getSQLOptions, ServiceLifetime.Transient);
void getSQLOptions(IServiceProvider serviceProvider, DbContextOptionsBuilder dbContextBuilder)
{
YourSettings? settings = serviceProvider.GetService<YourSettings>();
IHttpContextAccessor? httpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
/* Get your connectionString from your settings and HttpContext (Can be headers, query param or whatever) */
dbContextBuilder.UseSqlServer(connectionString);
}
You can then just inject your unique CommonDbContext in your Controller and use it. You don't need the child DbContext anymore

How to use DbContext with DI in desktop applications?

I've been working on several non-web applications with Entity Framework and always it was struggling for me to find a correct approach for implementing Generic Repository with DbContext.
I've searched a lot, many of articles are about web applications which have short-living contexts. In desktop approaches I can't find suitable method.
One approach is DbContext per ViewModel but I don't agree with coupling View with Repository layers.
Another one is using using clause this way:
using(var context = new AppDbContext())
{
// ...
}
but this way we will not have Unit of Work and also can't use IoC Containers.
So what is the best practice for using DbContext in desktop applications?
A DbContext is meant to be short-lived: it represents a unit-of-work in itself. If you need long-term object state management then you can use the ObjectStateManager in Entity Framework directly.
For ensuring access to a DbContext, add an interface IDbContextFactory<TDbContext> (or just IMyDbContextFactory if you only have a single DbContext type) and inject that into your ViewModels and use a short-lived DbContext from it:
interface IDbContextFactory<TDbContext>
where TDbContext : DbContext
{
TDbContext Create();
}
// Configure:
void ConfigureServices( YourContainer container )
{
container.RegisterSingleton( IDbContextFactory<YourDbContextType1>, // etc );
container.RegisterSingleton( IDbContextFactory<YourDbContextType2>, // etc );
container.RegisterSingleton( IDbContextFactory<YourDbContextType3>, // etc );
}
// Usage:
public class LongLivedViewModel
{
private readonly IDbContextFactory<YourDbContextType3> dbFactory;
public LongLivedViewModel( IDbContextFactory<YourDbContextType3> dbFactory)
{
this.dbFactory = dbFactory ?? throw new ArgumentNullException(nameof(dbFactory));
this.DoSomethingCommand = new RelayCommand( this.DoSomethingAsync )
}
public RelayCommand DoSomethingCommand { get; }
public async RelayCommand DoSomethingAsync()
{
using( YourDbContextType3 db = this.dbFactory.Create() )
{
// do stuff
await db.SaveChangesAsync();
}
}
}
Entity Framework Core has a built in IDbContextFactory interface.
If using SQL Server, for instance, you declare the following in the ConfigureServices method (which in WPF is generally put in App.xaml.cs).
private static void ConfigureServices(IServiceCollection services)
{
services.AddDbContextFactory<MyDbContext>(
options =>
options.UseSqlServer(MyConnectionString));
}
Make sure MyDbContext exposes this constructor:
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options)
{
}
}
After that use constructor injection in the class that will be using the context (which could be either in the ViewModel layer or the Model layer, depending on your architecture):
private readonly IDbContextFactory<MyDbContext> _contextFactory;
public ModelClass(IDbContextFactory<MyDbContext> contextFactory)
{
this._contextFactory = contextFactory;
}
public void DatabaseOperationMethod()
{
using (var context = this._contextFactory.CreateDbContext())
{
// Do database stuff
}
}

Registering multiple DBContexts in .net core with same base class

So just curiosity here, I have an app that has an internal site and an external site. The db for them is identical. Basically the internal app is for users to change data that is shown on the external app. So for this, I decided to create a single db context for the app for the internal db connection. I then created another context that inherited from the internal one for the external connection I did this because I thought I could add them to the service and set each up to it's own database. When I inject them into my promoter class, both db connections are pointing to the same db. Why is this? So what I have for my contexts is:
public class AppContext : DbContext
{
public AppContext() { }
public AppContext(DbContextOptions<AppContext> options): base(options)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Folder.Folder>().ToTable("Folders");
modelBuilder.Entity<File.File>().ToTable("Files");
modelBuilder.Entity<DocumentView>().ToTable("DocumentViews")
.HasKey(c=> new { c.PersonId, c.DocId });
modelBuilder.Entity<AppInfo>().ToTable("AppInfo");
}
public DbSet<Folder.Folder> Folders { get; set; }
public DbSet<File.File> Files { get; set; }
public DbSet<DocumentView> DocumentViews { get; set; }
public DbSet<AppInfo> AppInfos { get; set; }
}
public class ExternalAppContext : AppContext
{
public ExternalAppContext(DbContextOptions<AppContext> options) : base(options)
{ }
}
When I registered them in my startup, I registered them as such.
services.AddDbContext<AppContext>(options => options.UseSqlite(#"Data Source=" + Configuration["SqlConnection:adminDbLocation"]));
services.AddDbContext<ExternalAppContext>(options => options.UseSqlite(#"Data Source=" + Configuration["SqlConnection:externalDbLocation"]))
Promoter class injection is:
public Promoter(ExternalAppContext externalContext, AppContext adminContext)
{
_externalContext = externalContext;
_adminContext = adminContext;
}
So the real answer here was nested in a github answer from three years ago that can be found https://github.com/dotnet/efcore/issues/7533. The issue comes down to how the services are being rendered for pooling, which is why when registering multiple DbContexts that you have to specify the type on the DbContextOptions. To get around this on the base class you can have a protected constructor for the options. This will allow the pooling to work correctly and allow you to inherit the context. The example given by greggbjensen on Dec 2017 is below and was exactly what I was looking for.
public class MainDbContext : DbContext
{
public MainDbContext(DbContextOptions<MainDbContext> options)
: base(options)
{
}
protected MainDbContext(DbContextOptions options)
: base(options)
{
}
}
public class SubDbContext : MainDbContext
{
public SubDbContext (DbContextOptions<SubDbContext> options)
: base(options)
{
}
}
This allows for two services to be set up in the .net core services code as such.
services.AddDbContext<MainDbContext >(options => [[SomeOptionsHere]]);
services.AddDbContext<SubDbContext >(options => [[SomeOptionsHere]]);

Extending DbContext to avoid code duplication

I have a project with multiple DbContext classes: one for every domain. Since EF Core isn't perfect I have some functionality I have to add to every DbContext like allowing it to handle the new JSON functionality of SQL Server 2017. There is other functionality I'm adding too but for the sake of brevity I have removed it from the example.
To add this JSON functionality to every DbContext I work with a BaseDbContext which is an abstract class that simply extends the DbContext. One of the prerequisites for the functionality is interception of SQL queries that are sent to the database. To perform this I make use of the tactic described here.
In the next few weeks we are moving into different development environments and I want to inject the correct connection string based on the current environment through the Options pattern. This doesn't play well with the interception tactic. Since to perform interception GetService<DiagnosticSource>() is called which actually triggers the OnConfiguring function BEFORE the continuation of the constructor of my SomeDbContext, which means my options are never filled in leading to a NullReferenceException.
How can I extend DbContext without triggering OnConfiguring while at the same time avoiding code duplication? I can also run a custom EF Core but I'd like to avoid that if at all possible.
public abstract class BaseDbContext : DbContext, IBaseDbContext
{
protected BaseDbContext()
{
DiagnosticListener listener = this.GetService<DiagnosticSource>() as DiagnosticListener; // This calls OnConfiguring.
listener.SubscribeWithAdapter(new JsonInterceptor());
}
[DbFunction("JSON_VALUE", "")]
public static string JsonValue(string source, string path)
{
throw new NotSupportedException();
}
[DbFunction("TRY_CAST", "")]
public static string TryCast(string source, string typeName)
{
throw new NotSupportedException();
}
}
public class SomeDbContext : BaseDbContext, ISomeDbContext
{
private readonly SomeDbContextOptions _options;
public DbSet<Order> Orders { get; set; }
public SomeDbContext(IOptions<SomeDbContextOptions> options)
{
_options = options.Value;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// This is called before _options is set meaning it is null in the line below.
optionsBuilder
.UseSqlServer(_options.ConnectionString);
}
}

Configuring DbContext Constructor

I'm trying to use EF Core tools to manage an SqlServer database I'm designing in a C# class library. It's in a class library because I need to use the database schema in both an MVC6 website and some command line tools.
I had to convert the class library to being a netapp because the current version of the tooling doesn't support class libraries, but I don't think that's the source of my problem.
My DbContext class looks like this:
public class ConnellDbContext : IdentityDbContext<ConnellUser>
{
public ConnellDbContext( DbContextOptions<ConnellDbContext> options )
{
}
// core tables
public DbSet<Ballot> Ballots { get; set; }
public DbSet<Campaign> Campaigns { get; set; }
//...
}
When I run "dotnet ef migrations list" on the Package Manager Console, I get the following error message:
No parameterless constructor was found on 'ConnellDbContext'. Either
add a parameterless constructor to 'ConnellDbContext' or add an
implementation of 'IDbContextFactory' in the same
assembly as 'ConnellDbContext'.
I'm not quite sure how to resolve this. It's easy enough to insert a parameterless constructor, but when I do I get the following error:
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.
I >>think<< this means the console commands are not picking up the connection string information in my appsettings.json file:
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-ConnellCampaigns;Trusted_Connection=True;MultipleActiveResultSets=true;AttachDbFilename=e:\\SqlServer\\Data\\ConnellCampaigns.mdf;"
}
}
I'm missing something about how the EF tooling accesses the source code to do its magic. Any pointers or leads would be much appreciated.
Additional Info
Thanx to Mr. Anderson I've made a bit of progress. I added a parameterless constructor and overrode the OnConfiguring() method in my DbContext class:
protected override void OnConfiguring( DbContextOptionsBuilder optionsBuilder )
{
var builder = new ConfigurationBuilder()
.AddJsonFile( "appsettings.json", optional: true, reloadOnChange: true );
IConfigurationRoot config = builder.Build();
optionsBuilder.UseSqlServer(config.GetConnectionString("DefaultConnection") );
}
That didn't work, but explicitly including the actual connection string in the call to UseSqlServer() did. Thoughts on why the call based on "DefaultConnection" didn't work?
public class ConnellDbContext : IdentityDbContext<ConnellUser>
{
internal static string connection_string
{
get
{
return System.Configuration.ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
}
}
public ConnellDbContext() : base(connection_string)
{
}
// core tables
public DbSet<Ballot> Ballots { get; set; }
public DbSet<Campaign> Campaigns { get; set; }
//...
}

Categories