【发布时间】:2022-12-27 01:06:55
【问题描述】:
In Entity Framework (EF) Core, SCOPED OBJECTS are the same within a request (but different across different requests). Calling AddDbContext is supposed to be SCOPED by-default...so I am expecting each DbContext instance to be the same instance when marked as SCOPED...and it's not.
I know this because every DbContext handed-up using Dependency Injection (DI) has a different ContextId...and "save changes" no longer works across all Repository's in my UnitOfWork. As such, it seems like DbContext creation is acting TRANSIENT not SCOPED.
Q: How do I guarantee each instance of the concrete DbContext is the same object in EF Core's DI-model?
Why do I want this?
Calling the UnitOfWork's "save changes" used to work across all Repository's...but not anymore because each DbContxet is different (and has a separate change tracker)
Lamar Service Registry Code:
public class ContainerRegistry : ServiceRegistry
{
public ContainerRegistry()
{
Scan(scan =>
{
scan.TheCallingAssembly();
scan.WithDefaultConventions();
scan.LookForRegistries();
scan.SingleImplementationsOfInterface();
});
// --------
// DATABASE
//ForSingletonOf<WorkflowComponentDbContext>(); //<-- Doesnt work b/c each DbContext is still a separate instance
For<DbContext>().Use<WorkflowComponentDbContext>();
For(typeof(IAuditableRepository<>)).Use(typeof(GenericAuditableRepository<>));
// Policies (are used to map Constructor args)
Policies.Add<GenericRepositoryConfiguredInstancePolicy>();
Policies.Add<UnitOfWorkConfiguredInstancePolicy>();
}
}
Host Builder Code:
private IHostBuilder CreateHostBuilder(string[] args)
{
var builder = new HostBuilder()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
})
.UseServiceProviderFactory<ServiceRegistry>(new LamarServiceProviderFactory())
.ConfigureServices((hostContext, services) =>
{
var connectionString = hostContext.Configuration.GetConnectionString(JsonSettings.ConnectionStrings.WorkflowComponentDb);
services.AddLamar(IoC.Build());
services.AddScoped<IWindowsIdentityHelper, WindowsIdentityHelper>();
// This is supposedly SCOPED by-default?
// And while, this passes-back OPTIONS correctly...it isn't passing a "singleton object" throughout the request
services.AddDbContext<ProjectManagementDbContext>((provider, options) =>
{
options.UseSqlServer(connectionString);
});
services.AddDbContext<WorkflowComponentDbContext>((provider, options) =>
{
options.UseSqlServer(connectionString);
});
// This doesnt work either b/c it hands-back a new instacne of the Factoty each time (I tested this)....
//services.AddDbContextFactory<WorkflowComponentDbContext, WorkflowComponentDbContextFactory>((provider, options) =>
//{
// options.UseSqlServer(connectionString);
//}, ServiceLifetime.Scoped);
});
return builder;
}
LOW-TECH OPTION: Pass-in the IContainer
I really don't want to do this...but can
// -----
// NOTE: Some code omitted for brevity
public class WorkflowComponentUnitOfWork : IUnitOfWork
{
// OPTION: I could pass the IContainer to build some dependecies?
public WorkflowComponentUnitOfWork(DbContext dbContext, IContainer container)
{
DbContext = dbContext;
ContextType = new GenericAuditableRepository<ContextType>(DbContext);
ContextType.AuditResolver = container.GetRequiredService<IAuditResolverOf<ContextType>>();
ObjectState = new GenericAuditableRepository<ObjectState>(DbContext);
ObjectState.AuditResolver = container.GetRequiredService<IAuditResolverOf<ObjectState>>();
ObjectStateEvent = new GenericAuditableRepository<ObjectStateEvent>(DbContext);
ObjectStateEvent.AuditResolver = container.GetRequiredService<IAuditResolverOf<ObjectStateEvent>>();
Workflow = new GenericAuditableRepository<Workflow>(DbContext);
Workflow.AuditResolver = container.GetRequiredService<IAuditResolverOf<Workflow>>();
WorkflowEvent = new GenericAuditableRepository<WorkflowEvent>(DbContext);
WorkflowEvent.AuditResolver = container.GetRequiredService<IAuditResolverOf<WorkflowEvent>>();
WorkflowTransition = new GenericAuditableRepository<WorkflowTransition>(DbContext);
WorkflowTransition.AuditResolver = container.GetRequiredService<IAuditResolverOf<WorkflowTransition>>();
}
public virtual void SubmitChanges()
{
DbContext.SaveChanges();
}
}
LOW-TECH OPTION: Call "save changes" across all repository's
I really don't want to do this...but can
// -----
// NOTE: Some code omitted for brevity
public class WorkflowComponentUnitOfWork : IUnitOfWork
{
[SetterProperty]
public IAuditableRepository<ContextType> ContextType { get; set; }
[SetterProperty]
public IAuditableRepository<ObjectState> ObjectState { get; set; }
[SetterProperty]
public IAuditableRepository<ObjectStateEvent> ObjectStateEvent { get; set; }
[SetterProperty]
public IAuditableRepository<Workflow> Workflow { get; set; }
[SetterProperty]
public IAuditableRepository<WorkflowEvent> WorkflowEvent { get; set; }
[SetterProperty]
public IAuditableRepository<WorkflowTransition> WorkflowTransition { get; set; }
// OPTION: I could call "Save Changes" across each Repository
public virtual void SubmitChanges()
{
ContextType.SaveChanges();
ObjectState.SaveChanges();
ObjectStateEvent.SaveChanges();
Workflow.SaveChanges();
WorkflowEvent.SaveChanges();
WorkflowTransition.SaveChanges();
}
}
UPDATES:
Using the following does not work...
For<DbContext>().Use<WorkflowComponentDbContext>().Scoped();
【问题讨论】:
标签: c# entity-framework