How to inject .net core EF into WPF application - c#

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.

Related

DbContextFactory in WPF .Net 6 project (EF, MVVM)

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();
}
}
}

Dependency Injection WPF MVVM Navigation .NET Core

I need some help to understand how to instantiate ViewModels without having all of them as parameters in the MainViewModel Class constructor.
Could any of you help me to get my head around and get rid of so many parameters in the constructor. I've read about FactoryPatterns but I don't understand how to implement it or maybe that is not the solution?. Anyway here's the code.
Thank you. Pls help me this is driving me nuts!
app.xaml.cs
private readonly ServiceProvider _serviceProvider;
public App()
{
ServiceCollection services = new ServiceCollection();
ConfigureServices(services);
_serviceProvider = services.BuildServiceProvider();
}
private void ConfigureServices(ServiceCollection services)
{
services.AddSingleton<MainWindow>();
// Services
services.AddSingleton<ICustomerService, CustomerService>();
// ViewModels
services.AddScoped<MainViewModel>();
services.AddScoped<CustomerViewModel>();
services.AddScoped<CustomerAddViewModel>();
services.AddScoped<CustomerEditViewModel>();
services.AddScoped<ServiceViewModel>();
}
private void OnStartup(object sender, StartupEventArgs e)
{
var mainWindow = _serviceProvider.GetService<MainWindow>();
mainWindow.DataContext = _serviceProvider.GetService<MainViewModel>();
mainWindow.Show();
}
MainViewMode.cs
public class MainViewModel : ViewModelBase
{
private CustomerViewModel _customerViewModel;
private CustomerAddViewModel _customerAddViewModel;
private CustomerEditViewModel _customerEditViewModel;
private ViewModelBase _selectedViewModel;
public ViewModelBase SelectedViewModel
{
get => _selectedViewModel;
set
{
_selectedViewModel = value;
NotifyPropertyChanged();
}
}
public RelayCommand CustCommand { get; set; }
public RelayCommand ServCommand { get; set; }
**public MainViewModel(
CustomerViewModel customerViewModel,
CustomerAddViewModel customerAddViewModel,
CustomerEditViewModel customerEditViewModel)
{
_customerViewModel = customerViewModel;
_customerAddViewModel = customerAddViewModel;
_customerEditViewModel = customerEditViewModel;
CustCommand = new RelayCommand(OpenCustomer);
}**
private void OpenCustomer()
{
SelectedViewModel = _customerViewModel;
}
}
CustomerViewModel
public class CustomerViewModel : ViewModelBase
{
private ICustomerService _repo;
private ObservableCollection<Customer> _customers;
public ObservableCollection<Customer> Customers
{
get => _customers;
set
{
_customers = value;
NotifyPropertyChanged();
}
}
public CustomerViewModel(ICustomerService repo)
{
_repo = repo;
}
public async void LoadCustomers()
{
List<Customer> customers = await _repo.GetCustomers();
Customers = new ObservableCollection<Customer>(customers);
}
}
You're using constructor injection, which is only one of the ways to do DI. Another method is property injection, where you typically do something like this:
public class ClassToBeInjected
{
[Inject]
public SomeDataType PropertyToBeInjected {get; set;}
... etc ...
}
So long as ClassToBeInjected is being created via the DI framework, any properties tagged with [Inject] will also be injected automatically. Furthermore, any properties of SomeDataType that are tagged with the [Inject] attribute will also be injected, and so on down the heirarchy.
The exact mechanism of how this is achieved will depend on your DI framework. I've used the [Inject] attribute here, which is what Ninject uses, but each framework will have its own way of doing it.
You don't need to pass all your dependencies to MainViewModel, now it's easy because you only have three, what if they were 20? I think the best thing to do here is to inject the dependency container and get all your view models from there.
public class MainViewModel : ViewModelBase
{
private ServiceProvider _serviceProvider;
public MainViewModel(IServiceProvider provider)
{
_serviceProvider = provider;
}
private void SomeMethod()
{
CustomerViewModel viewModel = _serviceProvider.GetRequiredService<CustomerViewModel>();
}
}

Dependency Injection in Xamarin.Android

I am working on a Xamarin.Android project developed by another developer. I got to know that they have used Dependancy Injection. There is a class like this
public class RemoteSupportHandler : DataAccessor<FwDataContext>, IRemoteSupportSettingHandler
{
private RemoteSupportSetting[] _cached;
public RemoteSupportHandler(IDatabaseController db, ILogger logger, IPerfLogger perfLogger)
: base(db, logger, perfLogger)
{
}
public async Task<RemoteSupportSetting> GetRemoteSupportDemoVideoUrlAsync()
{
return await WithDataContextAsync(ctx =>
{
return (from row in ctx.RemoteSupportSettings
where row.ParamName == "DEMO_VIDEO"
select new RemoteSupportSetting
{
ParamName = row.ParamName
}).FirstOrDefault();
});
}
}
In another file, they have registered this class with UnityContainer. Now I want to call this GetRemoteSupportDemoVideoUrlAsync() method from my Activity. I know I cannot create an object using this constructor. I have no idea how I should I do this.
Registration Code
public class Registrar : IRegistrar {
protected virtual void OnApplyInitializedRegistrations(IUnityContainer container) {
container.RegisterType<IRemoteSupportSettingHandler, RemoteSupportHandler>(new ContainerControlledLifetimeManager());
}
}
public interface IRegistrar
{
void ApplySessionRegistrations(IUnityContainer container);
void ApplyInitializedRegistrations(IUnityContainer container);
}
UPDATE 2
public abstract class MyApplication : Application, IPlatformApplication {
public IUnityContainer AppUnityContainer => _container;
protected virtual void OnLaunched()
{
IActivityService activityService = new ActivityService(this);
AppUnityContainer.RegisterInstance(activityService);
AppUnityContainer.RegisterType<IRegistrar, PlatformRegistrar>();
}
}

Need to overide the navigation method

I am using autofac for dependency injection and I need to override the navigation function. in order to do that I did
Locator.cs(where contain the Cs files)
private readonly ContainerBuilder _builder;
public locator()
{
_builder = new ContainerBuilder();
register();
Container = _builder.Build();
}
public IContainer Container { get; set; }
private void register()
{
_builder.RegisterType<vm>().As<Ivm>();
_builder.RegisterType<Vm1>();
_builder.RegisterType < basevm>();
_builder.RegisterType<MainPage>();
_builder.RegisterType<xa>();
}
In my app.Xaml.cs
In constructor
public App()
{
InitializeComponent();
locator locator1 = new locator();
Container = locator1.Container;
MainPage = new NavigationPage(Container.Resolve<MainPage>());
}
public static IContainer Container;
then I tried to override the navigation func in my main page code behind it cannot be override. what I am missing and where i use this
public abstract void Navigate(SelectedItemChangedEventArgs e);
public override async void Navigate(SelectedItemChangedEventArgs e)
{
xa patientListViewPage = App.Container.Resolve<xa>();
await Navigation.PushAsync(patientListViewPage);
}
why this is not working. I occur this error
'MainPage.Navigate(SelectedItemChangedEventArgs)': no suitable method found to override
You can derive from NavigationPage reference.
public class CustomNavigationPage : NavigationPage
{
//You can define your container here.
public CustomNavigationPage()
{
//You can resolve here
}
}
and also you can look
here
I Can think of a better way You are using Autofac so you can have a generic method that can help the cause.
public static async Task NavigateAsync<TContentPage>(INavigation navigation ) where TContentPage : ContentPage
{
var contentPage = App.Container.Resolve<TContentPage>();
await navigation.PushAsync(contentPage, true);
}
Also If you need to pass a parameter You can modify it like this
public static async Task NavigateAsync<TContentPage, TNavigationParameter>(INavigation navigation,
TNavigationParameter navParam,
Action<TContentPage, TNavigationParameter> action = null) where TContentPage : ContentPage
{
var contentPage = App.Container.Resolve<TContentPage>();
action?.Invoke(contentPage, navParam);
await navigation.PushAsync(contentPage, true);
}

EF7 DbContext disposal

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

Categories