For the last few days, I have been playing around with Asp.net's Identity framework.
I have been able to get the Register and Login working however when I try to extend the functionality to saving data against specific users, I find it is different to how I would normally implement it with stock standard EF.
Normally I would use something like below to save data:
using(var context = myDbContex())
{
context.Add(object);
context.SaveChanges();
}
However, when I try to use this approach after inheriting the IdentityDbContext it is expecting an argument. Is it okay for me to create a default constructor that doesn't take any arguments or should I be passing something in?
My Context currently looks like this:
public class AppContext : IdentityDbContext<ApplicationUser>
{
//I am not really sure why options needs to be specified as an argument
public AppContext(DbContextOptions<AppContext> options)
: base(options)
{
}
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
public DbSet<Xxxxx> Xxxxx { get; set; }
public DbSet<Yyyyy> Yyyyy { get; set; }
public DbSet<Zzzzz> Zzzzz { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppContext>(options =>
options.UseSqlite("Data Source=App.db"));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<AppContext>()
.AddDefaultTokenProviders();
});
Why is this implementation of the context different to the standard dbContext, and how can I save data using this context?
Thanks
Because of this line
services.AddDbContext<AppContext>(options => options.UseSqlite("DataSource=App.db"));
You need to provide a constructor that has the DbContextOptions as paramter, which has nothing todo with IdentityDbContext.
You have two choices now.
Use dependency injection, that is how you are supposed to use it anyway
public class MyController : Controller
{
private AppContext context;
public MyController(AppContext context)
{
this.context = context;
}
}
Secondly you could register your context differently.
services.AddDbContext<AppContext>();
And apply changes in your context, remove the constructor and override OnConfiguring(DbContextOptionsBuilder optionsBuilder)
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=App.db");
}
Now you can use it as you usually would do.
using(var context = new AppContext())
{
// do stuff
}
EDIT:
Not part of the actual question but signin, registration and role managing is handled by these classes, that can be injected when using IdentityDbContext
SignInManager
UserManager
RoleManager
Related
I want to use identity in my project. I did create identity DbContext and also I did add Identity services to my startup file
But when I want create a new migration I see this error:
Unable to create an object of type context name for the deferent design patterns supported at design time
My context code here:
public class websitecontext : IdentityDbContext
{
public websitecontext(DbContextOptions<websitecontext> options) : base(options)
{
}
}
My startup codes here
services.AddDbContext<websitecontext>(s =>
s.UseSqlServer(Configuration.GetConnectionString("websiteconnectionstring"))
);
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<websitecontext>()
.AddDefaultTokenProviders();
Please helps me I did search in google but I didn't find any results
I solved the problem
I did add seed data in my context but I did not pass this to identity dbcontext and now I did pass this with :
base.OnModelCreating(modelBuilder);
I Resolved this by just adding a plain constructor to my Context
public class DataContext : DbContext
{
public DataContext()
{
}
public DataContext(DbContextOptions options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
if (!options.IsConfigured)
{
options.UseSqlServer("A FALLBACK CONNECTION STRING");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
I am using EFCore 3.1.5 and I have a DbContext I want to be able to use in the same controller or service either lazy or eager. However, it seems like I cannot get it to load lazy properly. Eager seems to work fine.
Whenever I do something as simple as:
var users = await _lazyDbContext
.Users
.Take(10)
.ToListAsync();
each navigational property on each User is null. However, with eager loading, it works fine:
var users = await _dbContext
.Users
.Include(x => x.Contact)
.Take(10)
.ToListAsync();
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<LazyUserContext>((sp, opt) =>
{
var connectionString = "very secret";
opt.UseSqlServer(connectionString, x => x.CommandTimeout(300));
opt.UseLazyLoadingProxies();
});
services.AddDbContext<UserContext>((sp, opt) =>
{
var connectionString = "very secret";
opt.UseSqlServer(connectionString, x => x.CommandTimeout(300));
});
services.AddScoped<IUserContext, UserContext>();
services.AddScoped<ILazyUserContext, LazyUserContext>();
}
UserContext.cs
public interface IUserContext
{
DbSet<User> Users { get; set; }
DbSet<Contact> Contacts { get; set; }
}
public class UserContext : DbContext, IUserContext
{
public UserContext(DbContextOptions<UserContext> options) : base(options) {}
public DbSet<User> Users { get; set; }
public DbSet<Contact> Contacts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Users>(e =>
{
e.HasOne(x => x.Contact).WithOne(x => x.User).HasForeignKey(x => x.ContactId);
}
}
}
LazyUserContext.cs
public interface ILazyUserContext : IContext {}
public class LazyUserContext : UserContext, ILazyUserContext
{
public LazyUserContext(DbContextOptions<UserContext> options) : base(options) {}
}
What could the issue be here? I have tried to IoC both the interface and the class in my controller/service. I have tried with and without the services.AddScoped<>().
All I want is be able to use a lazy dbContext or eager dbContext, where I want to default to the eager one.
The solution
All I want is be able to use a lazy dbContext or eager dbContext
You should be able to just set the configuration in the subclasses:
public class LazyContext : MyContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLazyLoadingProxies();
}
}
public class EagerContext : MyContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
}
}
There are two options of setting db option configuration, via the the constructor (and thus the DI route), or via the class itself. Since this particular setting is class-specific and you don't want to juggle it in the DI registration, it makes sense to rely on the class-specific configuration method.
Why your solution didn't work
The reason your initial approach didn't work is because AddDbContext<T> registers that specific T as your dependency type. From the source:
The AddDbContext extension method registers DbContext types with a scoped lifetime by default.
Note that it registers the context type, not any interfaces/ancestors of that context type.
So when you do this:
services.AddDbContext<LazyUserContext>((sp, opt) =>
{
var connectionString = "very secret";
opt.UseSqlServer(connectionString, x => x.CommandTimeout(300));
opt.UseLazyLoadingProxies();
});
services.AddScoped<ILazyUserContext, LazyUserContext>();
If your class has a dependency of type ILazyUserContext, it only listens to the second registration and flatout ignores the first one.
Only when your class has a dependency of LazyUserContext will you actually get the db context options you specified in the first registration.
Note that you can use this type-specific registration behavior to your advantage when you want to register a default implementation:
// Specific interface => specific type
services.AddScoped<IUserContext, UserContext>();
services.AddScoped<ILazyUserContext, LazyUserContext>();
// General interface => explicitly chosen default type
services.AddScoped<IContext, UserContext>();
This allows you to have some classes that demand to specifically have the eager/lazy loading, and other classes which just take in whatever is the default (which could change over time).
The problem is caused by the fact that both context constructors use (depend on) one and the same options type - DbContextOptions<UserContext>.
AddDbContext<TContext> actually registers two types - the context itself TContext as well as factory for the context options dependency DbContextOptions<TContext>.
So you are registering two options factories (along with configuration action) - DbContextOptions<UserContext> and DbContextOptions<LazyUserContext>. However, as mentioned at the beginning LazyUserContext depends on DbContextOptions<UserContext>, so it's simply instantiated with the first options set-up, i.e. exactly like the other.
This is not specific for lazy loading, but for any scenario which requires different options (different database type/connection string etc.) and is the reason generic class DbContextOptions<TContext> exists.
The solution is to change the LazyUserContext dependency
public LazyUserContext(DbContextOptions<LazyUserContext> options) : base(options) { }
and since this won't compile because the base expects DbContextOptions<UserContext>, add second protected constructor to the base class accepting just DbContextOptions
protected UserContext(DbContextOptions options) : base(options) { }
The ApiAuthorizationDbContext is my default DBContext.
Now how do I get this context in a controller?
I can create the normal DBContext with new DbContext() but with the ApiAuthorizationDbContext I have to give options where I don't know how to get them.
My ApiAuthorizationDbContext:
public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser>
{
public DbSet<Tenant> Tenants { get; set; }
public DbSet<SiteSettings> SiteSettings { get; set; }
public ApplicationDbContext(
DbContextOptions options,
IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options, operationalStoreOptions)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
AppSettings.DbOptions(optionsBuilder);
}
}
}
My Controller:
public class TenantHelper
{
private readonly ApplicationDbContext _context;
public TenantHelper(ApplicationDbContext context)
{
_context = context;
}
public static List<Tenant> GetAllTenants()
{
List<Tenant> tenants = new List<Tenant>();
tenants = _context.Tenants.ToList();
return tenants;
}
}
Dependency injection in your application allows you to use any object as a set of functionalities that can be reused by multiple objects, to do add your database context as an dependency injection you should add it to the Startup.cs file that the .NetCore Web Applications default template creates.
using Microsoft.Extensions.DependencyInjection;
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>();
}
Now your context can be injected in any object constructor of your application and you can save it as a property to be accessed at any point of that object scope
public class MyController
{
private readonly ApplicationDbContext _dbContext;
public MyController(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
private void MethodA()
{
//accessing dbcontext
_dbContext.MyTable.ToList();
}
}
Edit: Apparently OP meant that he wants his TenantHelper to accessible on all his application, still is a problem that dependency injection solves but just a quick rework needs to be done.
public void ConfigureServices(IServiceCollection services)
{
//This adds your object as a reusable set of functions that is initialized for every different request
services.AddScoped<TenantHelper>();
}
Now do the same process to inject your TenantHelper in your other code as you did on the ApplicationDbContext
Create library with you ApiAuthorizationDbContext and put reference from this lib in your project with controller
I have a project, in business it will creates table dynamicaly, its working with netcore3.0 and EF.
When an instance of dbcontext is created after dynamic table is created, I will use Assembly Emit to create a new type of the table, and use OnModelCreating method to add dbsets corresponding to tables.
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public virtual DbSet<Book> Books { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
//Use assmbly emit to create dynamic types
var types = CreateDynamicTypes();
foreach (var type in types)
{
builder.Entity(type);
}
base.OnModelCreating(builder);
}
}
But when a table is created after the dbcontext is created, I dont know how to add new dbset yet, because the OnModelCreating only run 1 time.
The question: How do I add new dbsets to an instance of dbcontext after its created?
OnModelCreating run only 1 time (when it first initialized) because of performance overhead.
There is one way, to bypass this, by using "Model Customizer"
First, you need some tweaking in OnConfiguring (you need to override basic implementation)
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
var serviceCollection = new ServiceCollection()
.AddEntityFrameworkSqlServer();
serviceCollection = serviceCollection.AddSingleton<IModelCustomizer, YourModelCustomizer>();
var serviceProvider = serviceCollection.BuildServiceProvider();
optionsBuilder
.UseInternalServiceProvider(serviceProvider);
}
And your Customizer should look like
public class YourModelCustomizer : ModelCustomizer
{
public override void Customize(ModelBuilder modelBuilder, DbContext dbContext)
{
base.Customize(modelBuilder, dbContext);
var entityTypeBuilderCart = modelBuilder.Entity<Models.Cart>()
.ToTable("ABC");
entityTypeBuilderCart.Property(a => a.UserId).HasColumnName("XYZ");
entityTypeBuilderCart.Property(a => a.ContractorId).HasColumnName("DFG");
entityTypeBuilderCart.Ignore(a => a.CompanyId);
var entityTypeBuilderCartArticle = modelBuilder.Entity<Models.CartArticle>()
.ToTable("IJK");
entityTypeBuilderCartArticle.Property(a => a.UserId).HasColumnName("QWE");
}
public YourModelCustomizer(ModelCustomizerDependencies dependencies) : base(dependencies)
{
}
}
I hope it will help you.
Be aware that this kind of configuration may cause performance issue.
This code works in EF Core 2.x, in EF 3.x may be some changes, and this code might need some changes.
I have setup .net core project and db context also. But i cant start using dbContext yet due this error-
"there is no argument given that corresponds to the required formal
parameter 'options'"
Controller:
public IActionResult Index()
{
using (var db = new BlexzWebDb())
{
}
return View();
}
Dbcontext Code:
public class BlexzWebDb : DbContext
{
public BlexzWebDb(DbContextOptions<BlexzWebDb> options)
: base(options)
{ }
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<AssignedRole> AssignedRoles { get; set; }
}
error picture attached. How can this issue be fixed?
Instantiate new object of DbContext from ConnectionString
var connectionstring = "Connection string";
var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsBuilder.UseSqlServer(connectionstring);
ApplicationDbContext dbContext = new ApplicationDbContext(optionsBuilder.Options);
// Or you can also instantiate inside using
using(ApplicationDbContext dbContext = new ApplicationDbContext(optionsBuilder.Options))
{
//...do stuff
}
Note
At the time of writing the use of EF Core with the Dependency injection framework wasn't as known as it is now. This answers gives answer to the question from a DI perspective, which at the time, helped out OP.
The other answer provides you a conventional way to instantiate the DbContext using the new operator.
TL;DR, 3 options:
Option 1
Register the DbContext during application configuration:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextPool<BlexzWebDb>(options =>
options.UseSqlServer(Configuration.GetConnectionString("BlexzWebConnection")));
}
and use the DI framework to retrieve it:
public class SomeController : Controller
{
private readonly BlexzWebDb _db;
//the framework handles this
public SomeController(BlexzWebDb db)
{
_db = db;
}
}
Option 2
If you are looking for a design-time IdentityDbContext using IOptions<OperationalStoreOptions>, see: Add migration for ApiAuthorizationDbContext from another project - EF Core
Option 3
Or use the new operator and provide the details, see #Qamar Zaman's answer for details.
The long answer, and why DI is a treat
In EF Core it's common to pass some DbContextOptions to the constructor.
So in general, a constructor looks like this:
public BlexzWebDb(DbContextOptions<BlexzWebDb> options) : base(options)
As you can see there, there is no valid overload in the form of a parameter-less constructor:
Thus, this does not work:
using (var db = new BlexzWebDb())
Obviously, you can pass in an Option object in the constructor but there is an alternative. So,
Instead
.Net Core has IoC implemented in it's roots. Okay, this means; you don't create a context, you ask the framework to give you one, based on some rules you defined before.
Example: somewhere you will register your dbcontext, (Startup.cs):
//typical configuration part of .net core
public void ConfigureServices(IServiceCollection services)
{
//some mvc
services.AddMvc();
//hey, options!
services.AddDbContextPool<BlexzWebDb>(options =>
options.UseSqlServer(Configuration.GetConnectionString("BlexzWebConnection")));
//...etc
Now the registering part is done, you can retrieve your context from the framework. E.g.: inversion of control through a constructor in your controller:
public class SomeController : Controller
{
private readonly BlexzWebDb _db;
//the framework handles this
public SomeController(BlexzWebDb db)
{
_db = db;
}
//etc.
why?
So, why not just provide the arguments and new it?
There is nothing wrong with the use of new - there are a lot of scenario's in which it works best.
But, Inversion Of Control is considered to be a good practice. When doing asp dotnet core you're likely to use it quite often because most libraries provide extension methods to use it. If you are not familiar with it, and your research allow it; you should definitely give it a try.
Therefore, instead of providing "just a way to instantiate" the object, I'll try to get you onto this track - inline with the framework. It will save you some hassle afterwards. Besides, otherwise "use an activator's CreateInstance" would just be as valid as an answer ;-)
Some links:
MSDN Fundamentals
MSDN Dependency Injection
Wikipedia Inversion Of Control
As addition of #Stefan's answer there is another way to achieve this. You can set db connection string in OnConfiguring method of DbContext class without adding DbContext service in startup.cs.
Setting.cs
public static class Setting
{
public static string ConnectionString { get; set; }
}
Startup.cs
Setting.ConnectionString = Configuration.GetSection("ConnectionStrings:BlexzDbConnection").Value;
BlexzWebDb.cs
public class BlexzWebDb : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(Setting.ConnectionString);
}
}
}
HomeController.cs
public class HomeController : Controller
{
private readonly BlexzWebDb db;
public HomeController()
{
this.db = new BlexzWebDb();
}
//etc.
Code sample for EF Core 3.1:
public class Test
{
private readonly IServiceProvider _serviceProvider;
public Test(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task<RequestResult> Handle(...)
{
await using var context = CreateContext();
...
}
private DocumentContext CreateContext()
{
var options = _serviceProvider.GetService<IOptions<DocumentContextOptions>>();
return new DocumentContext(options);
}
}