Getting user information in DbContext - entity-framework

I am attempting to use EF migrations to build/seed my database but my DbContext is causing an error when I use the update-database command.
The DbContext I have implemented has two constructors: one accepts no arguments and the other accepts an IUserContext interface. The IUserContext interface returns a username as a string. The IUserContext is used in the SaveChanges() method for setting user-related audit fields (ex. CreatedBy, UpdatedBy) common to all of my entities. Depending on the implementation of the IUserContext interface, the username may be retrieved from a HttpContext (in a mvc app), WindowsIdentity (in a console app), etc.
If a user attempts to call SaveChanges on the DbContext and the IUserContext is not set, it throws an exception. Basically, I do not want changes saved in the DbContext if a username cannot be provided by the IUserContext interface for purposes of recording who is making the modifications. If the DbContext is being used just for querying, using the no-args constructor isn't an issue since IUserContext is only used during SaveChanges.
When I attempt to use the update-database command, the DbMigrationsConfiguration is given an instance of the DbContext instantiated using the no-args constructor. Therefore, it throws an exception when it tries to invoke the DbContext's SaveChanges after the Seed() method.
Question: How do I pass an IUserContext to my DbContext so the update-database command will not fail and, more importantly, be able to set the appropriate entity fields to the name of the user seeding the changes? Is there some form of DI or other customization I can perform in the DbMigrationsConfiguration? Should I simply add an IUserContext accessor method to the DbContext so the IUserContext can be set outside of the constructor?
Maybe I should be asking a larger question: What is the best (most generic?) way of relaying user information to a DbContext for the purposes of recording who is making modifications? I gave some thought about moving this logic to the business layer of my application but it seems so convenient to encapsulate it in the SaveChanges of the DbContext (even more so when change tracking is enabled) .

We have a similar situation where we record modified user in SaveChanges() override. What we ended up doing was creating a BaseWrapper and use IoC to fill it. The second part is creating a MigrationsContextFactory that migrations will use to create the context. This class lives in the same folder as our ApplicationDbContext.
using System.Data.Entity.Infrastructure;
namespace MyApp.Data
{
public class MigrationsContextFactory : IDbContextFactory<ApplicationDbContext>
{
public ApplicationDbContext Create()
{
return new ApplicationDbContext(new HttpContextBaseWrapper());
}
}
}
public class HttpContextBaseWrapper : IHttpContextBaseWrapper
{
public string UserName
{
get
{
if (HttpContext.Current == null || HttpContext.Current.User == null)
return string.Empty;
return HttpContext.Current.User.Identity.Name;
}
}
}
https://marazt.wordpress.com/2015/01/09/entity-framework-dbcontext-idbcontextfactory-and-codefirst-migration-problem/

Related

registering DbContext with multiple parameters

I'm trying to inject TenantProvider into DbContext
public class AppDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, long>
{
public int? _tenantId;
public ITenantProvider _tenantProvider;
public AppDbContext(
DbContextOptions<AppDbContext> options,
ITenantProvider tenantProvider
)
: base(options)
{
_tenantProvider = tenantProvider;
}
but I don't understand how to register it correctly - if I put the breakpoint in the constructor - tenantProvider is null.
The bit from Startup.cs
services.AddDbContext<AppDbContext>(options => AppDbContextOptionsBuilder.Get());
the next line is required to inject the DbContext into a controller or a service (if I add ServiceLifetime.Scoped as a second parameter to the method above - AddDbContext - the feature doesn't work):
services.AddScoped(p => new AppDbContext(AppDbContextOptionsBuilder.Get(), p.GetService<ITenantProvider>()));
(Entity Framework is a separate project in my solution)
When using .AddScoped method - we can pass TenantProvider into constructor by resolving it using .GetService method.
Does anyone have an idea of how to resolve TenantProvider in .AddDbContext method?
Additional info:
I was trying to replace ITenantProvider in the constructor of DbContext with IHttpContextAccessor - the latter is registered as singleton. But the acessor parameter is still null.
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
I don’t really understand what your AddScoped call is supposed to do. AddDbContext will already register the database context properly with the service collection. So when you resolve the context through dependency injection, additional dependencies will be automatically resolved.
So it should be enough to do this:
services.AddDbContext<AppDbContext>(options => …);
services.AddSingleton<ITenantProvider, TenantProvider>();
And then, you can depend on your AppDbContext using constructor injection, e.g. in your controllers.
Two notes though:
When configuring options, you should modify the passed options object. So you should not just return AppDbContextOptionsBuilder.Get() but instead use the passed options object and edit that.
You should really think about whether your database context having a dependency on your tenant provider is the right thing to do. As per SRP, your database should only do a single thing and that is provide database access.
Depending on how your tenant provider affects your database access, it might make more sense to move this dependency up one level into some service that uses both the database context and the tenant provider to query data in the right way.

How do I implement DbContext inheritance for multiple databases in EF7 / .NET Core

I am building web APIs in ASP.NET Core 1.1.
I have a number different databases (for different systems) which have common base schemas for configuration items such as Configuration, Users and groups (about 25 tables in all). I am trying to avoid duplicating the quite extensive EF configuration for the shared part of the model by inheriting from a base class as shown in the diagram.
However, this does not work because of the Entity Framework (EF) requirement to pass DbContextOptions<DerivedRepository> as a parameter to the constructor, where DerivedRepository must match the type of the repository the constructor is called on. The parameter must then be passed down to the base DbContext by calling :base(param).
So when (for example) InvestContext is initialised with DbContextOptions<InvestContext>, it calls base(DbContextOptions<InvestContext>) and EF throws an error because the call to the ConfigurationContext constructor is receiving a parameter of type DbContextOptions<InvestContext> instead of the required type DbContextOptions<ConfigurationContext>. Since the options field on DbContext is defined as
private readonly DbContextOptions _options;
I can't see a way around this.
What is the best way to define the shared model once and use it multiple times? I guess I could create a helper function and call it from every derived context, but it's not nearly as clean or transparent as inheritance.
I would like to bring this post from the OP's GitHub issue to everyone's attention:
I was able to resolve this without a hack by providing a protected constructor that uses DbContextOptions without any type. Making the second constructor protected ensures that it will not get used by DI.
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) {
}
}
OK, I have got this working in a way which still uses the inheritance hierarchy, like this (using InvestContext from above as the example):
As stated, the InvestContext class receives a constructor parameter of type DbContextOptions<InvestContext>, but must pass DbContextOptions<ConfigurationContext> to it's base.
I have written a method which digs the connectionstring out of a DbContextOptions variable, and builds a DbContextOptions instance of the required type. InvestContext uses this method to convert its options parameter to the right type before calling base().
The conversion method looks like this:
protected static DbContextOptions<T> ChangeOptionsType<T>(DbContextOptions options) where T:DbContext
{
var sqlExt = options.Extensions.FirstOrDefault(e => e is SqlServerOptionsExtension);
if (sqlExt == null)
throw (new Exception("Failed to retrieve SQL connection string for base Context"));
return new DbContextOptionsBuilder<T>()
.UseSqlServer(((SqlServerOptionsExtension)sqlExt).ConnectionString)
.Options;
}
and the InvestContext constructor call changes from this:
public InvestContext(DbContextOptions<InvestContext> options):base(options)
to this:
public InvestContext(DbContextOptions<InvestContext> options):base(ChangeOptionsType<ConfigurationContext>(options))
So far both InvestContext and ConfigurationContext work for simple queries, but it seems like a bit of a hack and possibly not something the designers of EF7 had in mind.
I am still concerned that EF is going to get itself in a knot when I try complex queries, updates etc. It appears that this is not a problem, see below)
Edit: I've logged this problem as an issue with the EF7 team here, and a team member has suggested a change to the EF Core core as follows:
"We should update the check to allow TContext to be a type that is derived from the current context type"
This would solve the problem.
After further interaction with that team member (which you can see on the issue) and some digging through the EF Core code, the approach I've outlined above looks safe and the best approach until the suggested change is implemented.
Depending on your requirements you can simply use the non type specific version of DbContextOptions.
Change these:
public ConfigurationContext(DbContextOptions<ConfigurationContext> options):base(options)
public InvestContext(DbContextOptions<InvestContext> options):base(options)
to this:
public ConfigurationContext(DbContextOptions options):base(options)
public InvestContext(DbContextOptions options):base(options)
Then if you create your ConfigurationContext first, the classes that inherit it seem to get the same configuration. It may also depend on the order in which you initialize the different contexts.
Edit:
My working example:
public class QueryContext : DbContext
{
public QueryContext(DbContextOptions options): base(options)
{
}
}
public class CommandContext : QueryContext
{
public CommandContext(DbContextOptions options): base(options)
{
}
}
And in Startup.cs
services.AddDbContext<CommandContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddDbContext<QueryContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
alternatively, in a test class:
var connectionString = "Data Source=MyDatabase;Initial Catalog=MyData;Integrated Security=SSPI;";
var serviceProvider = new ServiceCollection()
.AddDbContext<QueryContext>(options => options.UseSqlServer(connectionString))
.BuildServiceProvider();
_db = serviceProvider.GetService<QueryContext>();

How should I create secondary DbContext using ContextProvider.EntityConnection?

I'm using breeze with Code First EF. My production DbContext has IDatabaseInitializer that throws an exception if !context.Database.CompatibleWithModel(true). If I create a context like suggested in the documentation database compatibility cannot be checked.
// The following line will throw NotSupportedException.
// Unable to verify the compatibility of the model because
// the DbContext instance was not created using Code First patterns.
var context2 = new MyDbContext(EntityConnection, false); // create a DbContext using the existing connection
How should I instantiate DbContexts providing ContextProvider's EntityConnection?
During SaveChanges, Breeze's EFContextProvider creates a DbContext instance using the default constructor. This happens prior to BeforeSaveEntity() and BeforeSaveEntities(). So you can rely on that first DbContext instance to check compatibility before your second DbContext instance is created.
In your DbContext, set the database initializer only in the default constructor. In the constructor that takes a DbConnection, set the initializer to null:
public MyDbContext() : base()
{
Database.SetInitializer(new CompatibilityCheckingInitializer<MyDbContext>);
}
public MyDbContext(DbConnection connection) : base(connection, false)
{
Database.SetInitializer(null);
}
This way, you can re-use the database connection on your second DbContext, but still have the initializer working on your first.
Naturally, you are still free to create DbContexts using any constructor you want, as you would have prior to Breeze 1.4. Using the EntityConnection property in your constructor is suggested as a way to help you conserve database connections.

Adding DbContext into existing .net 4 application

I have existing .net 4 application which is based on object context.
Now I'm adding DbContext in to existing application by inheriting DbContext and call constructor method and pass the existing object context. i.e.
public class DemoModelEntitiesDbContext : DbContext
{
public DemoModelEntitiesDbContext():base(new DemoModelEntities(), dbContextOwnsObjectContext:true)
{
}
public DbSet<ELMAH_Error> ELMAH_Error { get; set; }
}
Than When I call,
using (DemoModelEntitiesDbContext context = new DemoModelEntitiesDbContext())
{
foreach (ELMAH_Error entity in context.ELMAH_Error.ToList())
{
Console.WriteLine(entity.ID);
}
}
I am getting following error message,
"The type 'ObjectContextDemo.ELMAH_Error' was not mapped. Check that the type has not been explicitly excluded by using the Ignore method or NotMappedAttribute data annotation. Verify that the type was defined as a class, is not primitive, nested or generic, and does not inherit from EntityObject."
I checked my existing entities are inheriting from EntityObject.
How to add DbContext in to existing application and not changing existing code?
You could cast to object context but then you gain nothing from DbContext if you 100% stick with existing code. but Ive done that with POCOs, not EntityObjects and can't confirm that it would work with entityobjects.
Otherwise it's not possible to do that without changes. DbContext does not support EntityObject. DbContext is designed for POCOs. Plus there are other code differences between DbContext and ObjectContext. You'd have to change even if you were already using POCOs. If you are using the EF designer, you'd have to start by using a different code gen template (ef5 DbContext template). But that will result in very different classes and plenty of changes to your EF calls in your app.

ObjectDisposedException thrown everytime a "User" (membership) object is updated

I am using asp.net mvc 3, entity framework with structure map for IoC.
I have my own custom membership provider.
I had the same kind of problem when I was injecting the dbcontext object and the membership instances using StructureMap so removed this for my UserRepository.
The UserRepository now has a DBContext property which is initialised in the constructor.
Everytime a new user is created/updated (the method doing this is wrapping the dbcontext object in using statement), the next time the dbcontext is referenced I get ObjectDisposedException thrown.
I don't understand what I am doing wrong. Obviously, the membership provider class isn't instantiating the userRepository object everytime it needs it and when a user is updated, the context is disposed because of the using statement. But I thought this was standard practice?
Any help is greatly appreciated.
EDITED:
There is no complex code really. The set up is:
CustomMembershipProvider overrides the standard Membership provider (config file has got the details).
CustomMembershipProvider uses IUserService object that uses IUserRepository which is implemented by UserRepository that has the DBContext private object at class level.
In the UserRepository, I've got:
public void UpdateUser(User user)
{
using(_db)
{
... code to indicate that the user state has changed
_db.SaveChanges();
}
}
Once this code is run, the request is complete. But when another request is made to retrive role information or read the user from the database (in the UserRepository class), I get the ObjectDisposedException thrown. At this point nothing related to User Or Role works because they use the same UserRepository class whose _db variable has been disposed by UpdateUser.
I initially had StructureMap resolve the DBContext object, the customMembership object etc but have had to since remove it from StructureMap's mapping.
Everytime a new user is
created/updated (the method doing this
is wrapping the dbcontext object in
using statement), the next time the
dbcontext is referenced I get
ObjectDisposedException thrown.
DbContext is a disposable resource(Which implements IDisposable). So when you wrap it in a using block the context is disposed when the control flow goes out of the using block.
using(myContext)
{
//do stuff
}
//try to access myContext after the using block throw ObjectDisposedException
You have to re-design how you use the DbContext and when you are going to dispose it.