-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Inheritance problem with DbContext subclasses requiring constructor to supply DbContextOptions<DerivedContext> #7533
Comments
Note for triage: we should update the check to allow TContext to be a type that is derived from the current context type. |
@ajcvickers that seems like a good solution, thanks. In the meantime, do you know whether my workaround of building a new DbContextOptions of the type required by the base class an acceptable way to use inheritance for now, or it is likely to cause problems? |
@Ettery I don't think that will cause problems. |
@ajcvickers - Hmmm, digging into the EF code, it looks like the ContextType of the DbContextOptions may be used in ContextPooling and other services? And if it's not being used, why is EF enforcing it? I think my workaround may cause some strange problems, and the fix you have suggested is really important. |
@Ettery I don't think there will be any issues. Context pooling requires that the options passed to the context instance are not modified in OnConfiguring. Also, context pooling is only used if you explicit configure it to be used. Can you point to some specific places in the code where you think there will be a problem? The reason for the check is because we found that when code registers multiple contexts in D.I. but then uses a constructor that depends on the non-generic DbContext it was really hard to figure out why one of the contexts was using the wrong options. So we want to make sure that the options resolved from D.I. is really for the context being used. |
@ajcvickers thankyou, I think you are right, I picked up that TContext was being used in the pooling, but thats not coming from the DbContextOptions and I can't find anywhere there the TContext on DbContextOptions is used other than that initial check. Thanks for your help, I'll use my 'hack' for now. So great to have the code to work through, and thanks for all your work. |
@Ettery I have been trying to reproduce this but have not been able to. The code already allows the TContext generic type to be of a derived DbContext type--see test code below. Can you post the exact exception message and stack trace you are seeing? public class Program
{
public static void Main()
{
var appServiceProivder = new ServiceCollection()
.AddDbContext<DerivedContext2>()
.BuildServiceProvider();
using (var serviceScope = appServiceProivder
.GetRequiredService<IServiceScopeFactory>()
.CreateScope())
{
var context = serviceScope.ServiceProvider.GetService<DerivedContext2>();
Debug.Assert(context.GetService<IDbContextOptions>().GetType() == typeof(DbContextOptions<DerivedContext2>));
}
}
}
public class DerivedContext1 : DbContext
{
public DerivedContext1(DbContextOptions options)
: base(options)
{
}
}
public class DerivedContext2 : DerivedContext1
{
public DerivedContext2(DbContextOptions<DerivedContext2> options)
: base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseInMemoryDatabase(nameof(DerivedContext2));
} |
EF Team Triage: Closing this issue as the requested additional details have not been provided and we have been unable to reproduce it. |
@ajcvickers I am momentarily facing the same issue. In you example you need to have
and the the compiler will complain |
@theCuriousOne When you say "you need to have" can you be more specific? Need to have for what reason? |
@ajcvickers I meant "you need to have" in order to reproduce the error. My current issue is that I need to extend an DbContext with exactly that kind of code. |
@theCuriousOne I'm curious as to why you need to use that kind of code--I can see it would be problematic, and I can see some scenarios where it might be needed, but I am curious what your scenarios are so that we can make an assessment of the severity of the issue and react appropriately in finding workaround and considering ways to fix. |
@ajcvickers There is a framework build with dotnet core, and I need to extend upon it. And do not know how to do it otherwise. |
@theCuriousOne I see; so the DerivedContext1 is code that you cannot change? I'll re-open this issue to consider what to do. |
@ajcvickers What is the specific reason that |
@sam-wheat It's to do with the way the Microsoft.Extensions.DependencyInjection works. This means that any context type resolved from D.I. should have a The reason I re-opened this issue is because this pattern doesn't work well if the application needs to resolve both two derived context types and their common base context type from D.I. |
@ajcvickers I imagine the resolution matching should take place at the connection string level since an instance of DBContextOptions may theoretically be used for multiple contexts:
Both Db1 and Db2 should be able to accept MSSQLOptions.
Matching the connection string to the right DBContextOptions makes sense in this scenario also. Again, the DbContext does not need to know which type of DBContextOptions is passed in. I know you are aware of AutoFac and resoultion using keys. I have actually written a nuget package around this functionality as it is very powerful and useful in cases such as the one we are discussing now. |
@sam-wheat If a different D.I. framework is being used, then it should be possible to set it up any way you want so long as it resolves the context with the options you want. However, you'll likely have to write some custom code to do this, since AddDbContext (which is what this is for) is written for use with the Microsoft D.I. abstractions and is limited to what those abstractions support. EF Core in general is not limited in that way from an application perspective, although is does also use the D.I. abstractions internally, but that is an implementation detail and isolated from context resolution by the application. |
I was able to resolve this without a hack by providing a protected constructor that uses 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)
{
}
} |
Use both a public constructor for the typed options and a protected constructor for the un-typed options on a DbContext type that is intended to be both instantiated and inherited from in the same service provider.
@greggbjensen Thanks! I added a test for this case and I'm going to close this issue since that pattern looks pretty good to me. |
@greggbjensen Thank you for your input/help. I haven't tested it yet. According to your code, the pattern implies that the base class have to be change, which I believe it is not a very nice solution, but something I can live with :) I would suggest for this pattern to be added in some documentation (@ajcvickers ) since it is a guideline for building a class that needs to be overwritten. |
@theCuriousOne Created dotnet/EntityFramework.Docs#594 to document this. |
Use both a public constructor for the typed options and a protected constructor for the un-typed options on a DbContext type that is intended to be both instantiated and inherited from in the same service provider.
@ajcvickers, this seems to have more side effect. Which would explain why I get Concurrency Issues in this issue |
I know this is old, but I ran into this issue as well. Many folks use scaffold-dbcontext to generate their context and then either use a derived dbcontext to perform all the decorating and other stuff that would otherwise get overridden on the next call to scaffold-dbcontext. Since scaffold-dbcontext generates the constructor that causes the issue, we continually would have to add another constructor to it so that we can use our derived dbcontext without errors each time. |
@sschleicher208 If you're using But if you're using connection pooling you'll actually get an error if you have two constructors (which the scaffolder created). So to avoid manually editing the code I'm generating my scaffolded class as In fact I only need subclasses to allow me to set different options for different scenarios - such as longer timeouts for reporting but still be able to use pooling and DI. |
@simeyla That's what I used to do as well, but then I came across an issue when I had to provide additional code in the |
rename your context from scaffold to name + "FromScaffold"
|
- Added new protected constructor based on following post - dotnet/efcore#7533
* Added MySqlContext * Added MySqlContextFactory * Added Pomelo.EntityFrameworkCore.MySql nuget package * Added initial MySqlContext migrations * Added support for creating derived ElsaContexts: - Added new protected constructor based on following post - dotnet/efcore#7533 * Added Sample 24 - MySql provider with migration execution * Updated other samples that needed to define the derived ElsaContext * Added summary to ElsaContext constructor * Updated persistence/Elsa.Persistence.EntityFrameworkCore MD file with MySql example
If the reason you can't / do-not-want to alter the first inheritor of DbContext is because it is scaffolded then you're in luck. Here is a workaround:
Inheritance: DbContext (1 source: nuget) |
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 model configuration for the shared part of the model by inheriting from a base class.
However, this does not work because of the Entity Framework (EF) requirement to pass
DbContextOptions<DerivedContext>
as a parameter to the constructor, whereDerivedContext
must match the type of the context the constructor is called on. The parameter must then be passed down to the baseDbContext
by calling:base(param)
.So when (for example) InvestContext is initialised with
DbContextOptions<InvestContext>
, it callsbase(DbContextOptions<InvestContext>)
and EF throws an error because the call to theConfigurationContext
constructor is receiving a parameter of typeDbContextOptions<InvestContext>
instead of the required typeDbContextOptions<ConfigurationContext>
.I can work around this by building a new
DbContextOptions<ConfigurationContext>
from theDbContextOptions<InvestContext>
object and passing that to base, but it seems like a hack and results in DbContext being initialised withDbContextOptions<ConfigurationContext>
for any context which inherits from ConfigurationContext.So I guess I'm asking:
DbContextOptions<ConfigurationContext>
whenInvestContext
is constructed?DbContextOptions<DerivedContext>
Thanks, and thanks for all the good work - Peter
The text was updated successfully, but these errors were encountered: