My application is working fine when I use a plain/classic DBContext implementation, but when I try DbContextFactory, _contextFactory.CreateDbContext() is always failing with 'null' exception. What am I missing?
My App.xaml.cs (no changes were needed in this file whilst using DBContext):
private void ConfigureServices(IServiceCollection services)
{
string defaultConnection = Settings.Default.DefaultConnection;
services.AddDbContextFactory<MyDbContext>(options => options.UseMySql(defaultConnection, ServerVersion.AutoDetect(defaultConnection)));
services.AddTransient(typeof(MainWindow));
}
MyDbContext.cs file (no changes were needed as it seems to match DbContextFactory constructor's requirements already):
public class MyDbContext : DbContext
{
public MyDbContext (DbContextOptions<MyDbContext> options)
: base(options)
{
}
// DbSets
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
string defaultConnection = Settings.Default.DefaultConnection;
var options = new DbContextOptionsBuilder<MyDbContext>()
.UseMySql(defaultConnection, ServerVersion.AutoDetect(defaultConnection))
.Options;
}
optionsBuilder.UseLazyLoadingProxies();
// To be disabled in production
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
optionsBuilder.EnableSensitiveDataLogging();
optionsBuilder.EnableDetailedErrors();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Table building logic for EF code-first
}
}
MainWindow.xaml.cs file:
public partial class MainWindow : Window
{
private readonly IDbContextFactory<MyDbContext> _contextFactory;
private SomeVieModel _someVieModel;
public MainWindow()
{
InitializeComponent();
var _context = _contextFactory.CreateDbContext(); // Throws 'null' exception
// Probably should be instantiating a new DbContext
// in the VM itself, instead of passing it on?
_someVieModel = new SomeVieModel(_context);
}
}
I've checked numerous Blazor examples, because of the lack of WPF ones, and I feel I'm missing something very simple, some one line of DbContextFactory object instantiation? Like in this example - where is IDbContextFactory<MyDbContext> contextFactory object coming from and where is it instantiated? Thank you!
I think I've worked it out, although I'm sure some of you will be pointing out the error of my ways :)
I just realised, that I've already had my own DbContextFactory class created for database migrations, because otherwise the EF Core could not connect to the database via project's DbContext class alone.
public class MyDbContextFactory : IDesignTimeDbContextFactory<MyDbContext>
{
public MyDbContext CreateDbContext(string[]? args = null)
{
string defaultConnection = Settings.Default.DefaultConnection;
var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
optionsBuilder.UseMySql(defaultConnection, ServerVersion.AutoDetect(defaultConnection));
return new MyDbContext(optionsBuilder.Options);
}
}
I've commented the code in App.xaml.cs out and initialised DbContextFactory via my own class instead of IDbContextFactory interface:
public partial class MainWindow : Window
{
private readonly MyDbContextFactory _contextFactory;
private SomeVieModel _someVieModel;
public MainWindow()
{
InitializeComponent();
_contextFactory = new MyDbContextFactory();
_someVieModel = new SomeVieModel(_contextFactory);
}
}
And called CreateDbContext() in a view model:
public class SomeVieModel : ViewModelBase
{
private readonly MyDbContextFactory _contextFactory;
public SomeVieModel(MyDbContextFactory contextFactory)
{
_contextFactory = contextFactory;
await LoadDBStuff();
}
private async Task LoadDBStuff()
{
using (var context = _contextFactory.CreateDbContext())
{
await context.SomeDataModel.LoadAsync();
SomeDataModelObservableCollection = context.SomeDataModel.Local.ToObservableCollection();
}
}
}
Related
I would like to inject my .NET Core EntityFramework DbContext (sitting in a .net standard library) into my WPF app.
I tried this Unity approach:
OnStartup
var container = new UnityContainer();
container.RegisterType<ApplicationDbContext>();
var mainWindow = container.Resolve<MainWindow>();
base.OnStartup(e);
MainWindow
private ApplicationDbContext _db;
[Dependency]
public ApplicationDbContext Db
{
get
{
return _db;
}
set
{
_db = value;
}
}
public MainWindow()
{
//StandardDatabase.Commands.Test();
InitializeComponent();
DataContext = this;
FrameContent.Navigate(new PageConsignments());
}
But I get this error at container.Resolve<MainWindow>():
The current type, System.Collections.Generic.IReadOnlyDictionary`2[System.Type,Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptionsExtension], is an interface and cannot be constructed. Are you missing a type mapping?
Does anyone know if I'm doing something wrong? Any suggestions on a better way of doing this are welcome
ApplicationDbContext
public ApplicationDbContext() : base() { }
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{ }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseLazyLoadingProxies()
.UseSqlServer("Server=L-TO-THE-APTOP\\SQLEXPRESS;Database=Maloli;Trusted_Connection=True;MultipleActiveResultSets=true");
optionsBuilder.ConfigureWarnings(x => x.Ignore(CoreEventId.LazyLoadOnDisposedContextWarning));
}
As per Nkosi's suggestion, I removed the ApplicationDbContext(options) ctor from the context, and that got rid of the error.However I am now checking the value of Db here in MainWindow:
private ICommand goPack;
public ICommand GoPack
{
get
{
return goPack
?? (goPack = new ActionCommand(() =>
{
var c = _db.Parts;
FrameContent.Navigate(new PageConsignments());
}));
}
}
But it returns null
The original error was because the container was selecting the constructor that expected DbContextOptionsBuilder which the conateinr did not know how to resolve properly.
Since the context is being configured within the OnConfiguring override then there is no need for
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{ }
Remove that constructor so the container resolve the context without errors.
Depending on the flow of dependency initialization and access to it, that context should really be explicitly injected into a view model and not directly on the View.
Following MVVM, have all the necessary dependencies and bindable properties in the view model
public class MainWindowViewModel : BaseViewModel {
private readonly ApplicationDbContext db;
public MainWindowViewModel(ApplicationDbContext db) {
this.db = db;
}
private ICommand goPack;
public ICommand GoPack {
get {
return goPack
?? (goPack = new ActionCommand(() =>
{
var c = db.Parts;
FrameContent.Navigate(new PageConsignments());
}));
}
}
}
Update the View to depend on the view model
public class MainWindow : Window {
[Dependency]
public MainWindowViewModel ViewModel {
set { DataContext = value; }
}
public MainWindow() {
InitializeComponent();
Loaded += OnLoaded;
}
void OnLoaded(object sender, EventArgs args) {
FrameContent.Navigate(new PageConsignments());
}
}
All that is left now is to make sure all dependencies are registered with container
public class App : Application {
protected override void OnStartup(StartupEventArgs e) {
IUnityContainer container = new UnityContainer();
container.RegisterType<ApplicationDbContext>();
container.RegisterType<MainWindowViewModel>();
container.RegisterType<MainWindow>();
MainWindow mainWindow = container.Resolve<MainWindow>();
mainWindow.Show();
}
}
Where ever possible, The Explicit Dependencies Principle via constructor injection should be preferred over property injection.
But since most views do not lend well to constructor injection the latter is usually applied. By making sure the view model has all the necessary dependencies before injecting it into the view you ensure that all required values are available when needed.
We are developing windows service, and i want to change dbcontext class dynamically in repositories.
bellow is the scenario.
I have three db context classes
public abstract class Context : DbContext, IUnitOfWork
{
protected Context(string connectionString) : base(connectionString)
{
}
}
public class PlatformContext : Context
{
private readonly string _connectionString;
public PlatformContext(string connectionString)
: base(connectionString)
{
_connectionString = connectionString;
}
}
public class PlatformReplicaContext : Context
{
private readonly string _connectionString;
public PlatformReplicaContext(string connectionString)
: base(connectionString)
{
_connectionString = connectionString;
}
}
public class TempContext : Context
{
private readonly string _connectionString;
public TempContext(string connectionString)
: base(connectionString)
{
_connectionString = connectionString;
}
}
and i have repository
public interface ICategoryRepository : IRepository<Category>
{
}
public class CategoryRepository :Repository<Category>, ICategoryRepository
{
public CategoryRepository(Context context) : base(context)
{
}
}
hence im using CQRS i have another three classes
public class CategoryBasicQuery:IRequest<BaseQueryResponse>
{
public int CategoryId { get; set; }
}
public class CategoryBasicQueryHandler : IRequestHandler<CategoryBasicQuery, BaseQueryResponse>
{
private readonly ICategoryRepository _categoryRepository;
private readonly IMapper _mapper;
public CategoryBasicQueryHandler(ICategoryRepository categoryRepository, IMapper mapper)
{
_categoryRepository = categoryRepository;
_mapper = mapper;
}
public async Task<BaseQueryResponse> Handle(CategoryBasicQuery request, CancellationToken cancellationToken)
{
var entry = await _categoryRepository.FindAsync(request.CategoryId);
if (entry == null)
{
return new NotFoundResponse();
}
var response = _mapper.Map<CategoryBasicResponse>(entry);
return response;
}
}
Now here is the issue
Here category repository should be able to execute queries in all 3 types of contexts.
but how should i register classes in using autofac?
then i came up with a solution generating repositories in run time as below
public class RepositoryFactory
{
public static TRepository GetRepositoryInstance<T, TRepository>(
params object[] args)
where TRepository : IRepository<T>
{
return (TRepository)Activator.CreateInstance(typeof(TRepository), args);
}
}
im calling this method inside CategoryBasicQueryHandler class like this
var categoryRepo = RepositoryFactory.GetRepositoryInstance<Category, CategoryRepository>(new PlatformReplicaContext("connectionString"));
but when calling from CQRS
var categoty = new Category();
var command = new CategoryBasicQuery {CategoryId = categoryId};
var result = _mediator.Send(command);
VS give me following error
and my autofac registration as follows
builder.RegisterType<CategoryService>().AsSelf();
builder.RegisterType<ActionRepository>().As<IActionRepository>();
builder.RegisterType<CategoryRepository>().As<ICategoryRepository>();
builder.RegisterType<Mapper>().As<IMapper>();
can anyone help me resolve this or suggest good method to handle this situation.
thanks.
This may give you a good starting point for a possible solution: http://autofaccn.readthedocs.io/en/latest/resolve/relationships.html#keyed-service-lookup-iindex-x-b
builder.RegisterType<PlatformContext>().Keyed<Context>("platform");
builder.RegisterType<PlatformReplicaContext>().Keyed<Context>("replica");
builder.RegisterType<TempContext>().Keyed<Context>("temp");
You mentioned in a comment that there is a variable named action somewhere that will indicate which implementation to use:
public class Class1
{
private readonly IIndex<string, Context> contexts;
public Class1(IIndex<string, Context> contexts)
{
this.contexts = contexts;
}
public void Whatever()
{
string action = ...; // platform, replica or temp
Context context = this.contexts[action];
...
}
}
Of course this needs to be adapted so that it will fit in the rest of your application design. A possible example could be:
Context context = this.contexts[action];
using(ILifetimeScope scope = container.BeginLifetimeScope(builder =>
{
builder.RegisterInstance(context).As<Context>();
}))
{
// Because we are resolving IMediator from the scope, the selected Context will be used in all dependencies
var mediator = scope.Resolve<IMediator>();
mediator.Send(...);
}
I am building a desktopp app which uses WPF and EF7 with SqLite. In my service classes I have an instance of IContextScopeLocator injected, which main job is to create and reuse instances of EF DbContexts.
ContextScope
public class ContextScope : IDisposable
{
private readonly PrzylepaDbContext _context;
public ContextScope(PrzylepaDbContext context)
{
_context = context;
}
public EventHandler OnDisposed { get; set; }
public PrzylepaDbContext Context
{
get { return _context; }
}
public void Dispose()
{
OnDisposed.Invoke(this, EventArgs.Empty);
}
}
ContextScopeLocator
public class ContextScopeLocator : IContextScopeLocator
{
private readonly IContextFactory _factory;
public ContextScopeLocator(IContextFactory factory)
{
_factory = factory;
}
private PrzylepaDbContext _currentContext;
private readonly List<ContextScope> _currentScopes = new List<ContextScope>();
public ContextScope GetScope()
{
if (_currentContext == null)
{
//building new EF DbContext if nescesary
_currentContext = _factory.Create();
}
var scope = new ContextScope(_currentContext);
scope.OnDisposed += OnDisposed;
_currentScopes.Add(scope);
return scope;
}
private void OnDisposed(object sender, EventArgs eventArgs)
{
var scope = sender as ContextScope;
Debug.Assert(_currentScopes.Contains(scope));
_currentScopes.Remove(scope);
if (_currentScopes.Count == 0)
{
_currentContext.Dispose();
_currentContext = null;
}
}
}
Then in my service method I can use it like that:
public IEnumerable<Client> GetPublicClients()
{
using (var scope = _scopeLocator.GetScope())
{
return scope.Context.Clients.Where(x => x.IsPublic).IncludeStandard().ToList();
}
}
And even with nested queries I can still get the same context. I will not be calling service methods from multiple threads so I thought this approach would work more less fine for me.
Then in my viewmodel class I receive a message in the following way
private void ClientModifiedMessageHandler(NotifyEntityModifiedMessage<Client> msg)
{
if (msg.EntityId == ModifiedOffer.ClientId)
{
var client = _clientService.GetById(ModifiedOffer.ClientId);
ModifiedOffer.Client = client; //exception
}
}
Exception is raised by the DbContext which was used to get ModifiedOffer from the Db:
"The instance of entity type 'Przylepa.Data.Client' cannot be tracked because another instance of this type with the same key is already being tracked. For new entities consider using an IIdentityGenerator to generate unique key values."
The problem is that the old DbContext is still alive because it subscribes PropertyChanged event in the ModifiedOffer even though Dispose() was called on it (DbContext._disposed is true).
How can I make these DbContexts unsubscribe these events, so that I can do what I want with my model class instances? Thank you
I am trying to add Windsor Castle 3.3.0 IoC with my MVC 5 project dbcontext (EF 6) and have had a look at many articles, but I can't figure out how to do it and keep running into the problem of getting the following error:
The operation cannot be completed because the DbContext has been disposed.
This error only seems to occur once the first page has loaded and then I try to load subsequent pages
This is my code:
Context Interface
public interface IMyContext
{
IDbSet<DBProduct> Products { get; set; }
}
Context
public class MyContext : DbContext, IMyContext
{
public MyContext ()
: base("Name=MyDB")
{
Database.SetInitializer<MyContext>(null);
this.Configuration.ProxyCreationEnabled = false;
}
public IDbSet<DBProduct> Products { get; set; }
}
Usage
public sealed class CatalogueDAL : ICatalogueDAL
{
private IMyContext _dbContext;
public CatalogueDAL(IMyContext dbContext)
{
_dbContext= dbContext;
}
public ICollection<DBProduct> GetAllProducts()
{
return _dbContext.Products
.OrderBy(x => x.Order)
.ThenBy(x => x.Name)
.ToList();
}
}
public class CatalogueHelper : ICatalogueHelper
{
private ICategoryDAL _categoryDAL;
private IMapper _mapper;
public CatalogueHelper(ICatalogueDAL catalogueDAL)
{
_catalogueDAL = catalogueDAL;
CreateMapper();
}
public ICollection<Product> GetAllProducts()
{
return _mapper.Map<ICollection<DBProduct>, ICollection<Product>>(_catalogueDAL.GetAllProducts());
}
private void CreateMapper()
{
MapperConfiguration config = new MapperConfiguration(cfg =>
{
cfg.CreateProductMap();
});
_mapper = config.CreateMapper();
}
}
public class LandingPageController : Controller
{
private ICatalogueHelper _helper;
public LandingPageController(ICatalogueHelper helper)
{
_helper = helper;
}
public ActionResult AllProducts()
{
return View(_helper.GetAllProducts());
}
}
Castle Initialiser
container.Register(Component.For<IMyContext>().ImplementedBy<MyContext>().LifeStyle.PerWebRequest);
container.Register(Component.For<ICatalogueDAL>().ImplementedBy<CatalogueDAL>().LifeStyle.PerWebRequest);
container.Register(Component.For<ICatalogueHelper>().ImplementedBy<CatalogueHelper>().LifeStyle.PerWebRequest);
If I remove the IoC and put a using(var context = new MyContext()) in the GetAllProducts, it will work fine but that seems to against the point?
How do I change this to use IoC on the dbContext.
I have tried just using the MyContext (instead of the interface) and registering that with
container.Register(Component.For<MyContext>().ImplementedBy<MyContext>().LifeStyle.PerWebRequest);
And this seems to work better, but when I load two pages at the same time, the one that starts second will always get the following error message:
There is already an open DataReader associated with this Command which must be closed first
Having searched both these errors, I cannot figure out how to apply it to my solution. Any help would be greatly appreciated
I am starting to use Ninject in my MVC5 code-first app. Here's my NinjectWebCommon.cs:
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
try
{
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
kernel.Bind<CMSContext>()
.ToSelf()
//.InSingletonScope();
.InRequestScope();
kernel.Bind<IExecutiveRepository>()
.To<ExecutiveRepository>();
kernel.Bind<IExecutiveSectionRepository>()
.To<ExecutiveSectionRepository>();
kernel.Bind<IExecutiveSectionMappingRepository>()
.To<ExecutiveSectionMappingRepository>();
kernel.Bind<IUserRepository>()
.To<UserRepository>();
kernel.Bind<IContentRepository>()
.To<ContentRepository>();
RegisterServices(kernel);
return kernel;
}
catch
{
kernel.Dispose();
throw;
}
}
I tried .InSingletonScope() as well as .InRequestScope() but I still get the 'entity object cannot be referenced by multiple instances of IEntityChangeTracker' error.
Here is my Interface:
public interface IExecutiveRepository : IDisposable
{
IEnumerable<Executive> GetExecutives();
Executive GetExecutiveById(int executiveId);
void InsertExecutive(Executive executive);
void UpdateExecutive(Executive executive);
void DeleteExecutive(int executiveId);
void Save();
}
Here is my concrete:
public class ExecutiveRepository : IExecutiveRepository, IDisposable
{
private CMSContext context;
public ExecutiveRepository(CMSContext context)
{
this.context = context;
}
public IEnumerable<Executive> GetExecutives()
{
return context.Executives.ToList();
}
public Executive GetExecutiveById(int id)
{
return context.Executives.Find(id);
}
public void InsertExecutive(Executive executive)
{
context.Executives.Add(executive);
}
public void DeleteExecutive(int executiveId)
{
Executive executive = context.Executives.Find(executiveId);
context.Executives.Remove(executive);
}
public void UpdateExecutive(Executive executive)
{
context.Entry(executive).State = EntityState.Modified;
}
public void Save()
{
context.SaveChanges();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
Here is the controller(top pertinent part):
public class ExecutiveController : Controller
{
private IExecutiveRepository executiveRepository;
private IUserRepository userRepository;
private IExecutiveSectionRepository executiveSectionRepository;
private IExecutiveSectionMappingRepository executiveSectionMappingRepository;
private IContentRepository contentRepository;
private Ninject.IKernel _kernel = new StandardKernel();
//[Inject]
public ExecutiveController()
{
executiveRepository = _kernel.Get<ExecutiveRepository>();
userRepository = _kernel.Get<UserRepository>();
executiveSectionRepository = _kernel.Get<ExecutiveSectionRepository>();
executiveSectionMappingRepository = _kernel.Get<ExecutiveSectionMappingRepository>();
contentRepository = _kernel.Get<ContentRepository>();
}
...
Not sure what I am doing wrong but upon adding a new 'Executive' it bombs... I do understand it's trying to use separate contexts and that's the problem, but I 'm just not sure how to fix it. Apparently, the line in the NinjectWebCommon.cs class:
kernel.Bind<CMSContext>()
.ToSelf()
//.InSingletonScope();
.InRequestScope();
Is supposed to be the fix, but it isn't...
any ideas/suggestions?
You should be using NUGET package Ninject.Web.Mvc if you aren't already. This configures your application ready to use Ninject, other than your bindings. It looks like you are reasonably familiar with the bindings side of things already from what I can see in your CreateKernel() method.
Once your bindings are in place, you should not be creating Kernels in your controllers, this is because the Ninject.Web.Mvc library configures Ninject to create your controllers for you under the hood. Therefore any dependencies that you add to them should be automatically resolved.
So, you can use constructor injection to resolve your dependencies:
public class ExecutiveController : Controller
{
private IExecutiveRepository ExecutiveRepository;
private IUserRepository UserRepository;
private IExecutiveSectionRepository ExecutiveSectionRepository;
private IExecutiveSectionMappingRepository ExecutiveSectionMappingRepository;
private IContentRepository ContentRepository;
public ExecutiveController(
IExecutiveRepository executiveRepository,
IUserRepository userRepository,
IExecutiveSectionRepository executiveSectionRepository,
IExecutiveSectionMappingRepository executiveSectionMappingRepository,
IContentRepository contentRepository)
{
// Set the field values
this.ExecutiveRepository = executiveRepository,
this.UserRepository = userRepository,
this.ExecutiveSectionRepository = executiveSectionRepository,
this.ExecutiveSectionMappingRepository = executiveSectionMappingRepository,
this.ContentRepository = contentRepository;
}
public ActionResult Index(int id)
{
// Use one of your dependencies...
var executive = this.executiveRepository.GetExecutiveById(id);
}
}
Or you can use the [Inject] attribute which has the same effect:
public class ExecutiveController : Controller
{
[Inject]
public IExecutiveRepository executiveRepository { get; set; }
[Inject]
public IUserRepository userRepository { get; set; }
[Inject]
public IExecutiveSectionRepository executiveSectionRepository { get; set; }
[Inject]
public IExecutiveSectionMappingRepository executiveSectionMappingRepository { get; set; }
[Inject]
public IContentRepository contentRepository { get; set; }
public ExecutiveController()
{
}
public ActionResult Index(int id)
{
// Use one of your dependencies...
var executive = this.executiveRepository.GetExecutiveById(id);
}
}
You're creating a kernel per controller.
InRequestScope only ensures one instance per request per kernel.
So you need to adapt your setup of the kernel so there's only one kernel per web application. See:
Ninject.Web.Mvc
Tutorial
Youtube
This may not answer the question. But I tend to use the IDbContextFactory that EF provides you with and do something like this:
public interface IDefaultContextFactory : IDbContextFactory<CMSContext> {}
public class DefaultContextFactory : IDefaultContextFactory
{
private readonly Lazy<CMSContext> lazyContext = new Lazy<CMSContext>(() => new CMSContext());
public CMSContext Create()
{
return lazyContext.Value;
}
}
Then you just bind that, and when you need the context you can do something like this:
public class ExecutiveRepository : IExecutiveRepository, IDisposable
{
private readonly CMSContext context;
public ExecutiveRepository(IDefaultContextFactory contextFactory)
{
this.context = contextFactory.Create();
}
}
I believe #BatteryBackupUnit is correct, I would also consider using the above pattern for contexts.