Object reference exception – Dependency Injection with Entity Framework WPF

huangapple go评论64阅读模式
英文:

Object reference exeption - Dependency Injection with Entity Framework WPF

问题

以下是您提供的代码部分的翻译:

我尝试使用Entity Framework和依赖注入来实现与数据库的连接<br> 我想在App.xaml.cs中创建Host

public partial class App : Application
{
    public static IHost? AppHost { get; private set; }
    public App()
    {
        AppHost = Host.CreateDefaultBuilder()
            .ConfigureServices((hostContext, services) =>
            {
                services.AddSingleton<LoginWindow>();
                services.AddSingleton<LoginViewModel>();

                services.AddDbContext<KnitterNotebookContext>(
                    options =>
                    {
                        string appSettingsPath = Path.Combine(ProjectDirectory.ProjectDirectoryFullPath, "appsettings.json");
                        string appSettingsString = File.ReadAllText(appSettingsPath);
                        AppSettings AppSettings = JsonConvert.DeserializeObject<AppSettings>(appSettingsString)!;
                        options.UseSqlServer(AppSettings.KnitterNotebookConnectionString);
                    });
            })
            .Build();
    }

    protected override async void OnStartup(StartupEventArgs e)
    {
        await AppHost!.StartAsync();
        var startupWindow = AppHost.Services.GetRequiredService<LoginWindow>();
        startupWindow.Show();
        base.OnStartup(e);
    }

    protected override async void OnExit(ExitEventArgs e)
    {
        await AppHost!.StopAsync();
        base.OnExit(e);
    }     
}

我想将DbContext作为参数传递给ViewModel,但这样做时会引发异常。

public class LoginViewModel : BaseViewModel
{
    public LoginViewModel(KnitterNotebookContext knitterNotebookContext)
    //public LoginViewModel()
    {
        KnitterNotebookContext = knitterNotebookContext;
        ShowRegistrationWindowCommand = new RelayCommand(ShowRegisterWindow);
        LogInCommandAsync = new AsyncRelayCommand(LogIn);
    }

    private KnitterNotebookContext KnitterNotebookContext { get; set; } 
}

在代码中提到的问题是,您想将DbContext作为参数传递给LoginViewModel,但出现了异常。如果使用无参数构造函数的LoginViewModel并使用new()创建新的KnitterNotebookContext实例,就不会出现问题。您想知道如何解决这个问题。

英文:

I tried to implement connection to database using Entity Framework and Dependency Injection. <br> I want to create Host in App.xaml.cs.

public partial class App : Application
{
  
    public static IHost? AppHost { get; private set; }
    public App()
    {
        AppHost = Host.CreateDefaultBuilder()
             .ConfigureServices((hostContext, services) =&gt;
             {
                 services.AddSingleton&lt;LoginWindow&gt;();
                 services.AddSingleton&lt;LoginViewModel&gt;();

                 services.AddDbContext&lt;KnitterNotebookContext&gt;(
                     options =&gt;
                     {
                         string appSettingsPath = Path.Combine(ProjectDirectory.ProjectDirectoryFullPath, &quot;appsettings.json&quot;);
                         string appSettingsString = File.ReadAllText(appSettingsPath);
                         AppSettings AppSettings = JsonConvert.DeserializeObject&lt;AppSettings&gt;(appSettingsString)!;
                         options.UseSqlServer(AppSettings.KnitterNotebookConnectionString);
                     });
             })
             .Build();
    }

    protected override async void OnStartup(StartupEventArgs e)
    {
        await AppHost!.StartAsync();
        var startupWindow = AppHost.Services.GetRequiredService&lt;LoginWindow&gt;();
        startupWindow.Show();
        base.OnStartup(e);
    }

    protected override async void OnExit(ExitEventArgs e)
    {
        await AppHost!.StopAsync();
        base.OnExit(e);
    }     

I want to pass DbContext as parameter to ViewModel, but when I do, it throws exception.

 public class LoginViewModel : BaseViewModel
{
    public LoginViewModel(KnitterNotebookContext knitterNotebookContext)
   //public LoginViewModel()
    {
        KnitterNotebookContext = knitterNotebookContext;
        ShowRegistrationWindowCommand = new RelayCommand(ShowRegisterWindow);
        LogInCommandAsync = new AsyncRelayCommand(LogIn);
    }

    private KnitterNotebookContext KnitterNotebookContext { get; set; } 
 }

Object reference exception – Dependency Injection with Entity Framework WPF

Object reference exception – Dependency Injection with Entity Framework WPF

There is no problem if I use parameterless constructor of LoginViewModel and create new instances of KnitterNotebookContext with new(), but I want to pass it as a parameter. How to solve it?

答案1

得分: 1

我碰巧在某个时候遇到了相同的问题 - 我猜想你正在XAML中为MainWindow设置DataContext,就像这样:

<Window.DataContext>
    <local:LoginViewModel/>
</Window.DataContext>

以这种方式执行时,当创建LoginWindow时,会尝试创建其DataContext的新实例(即LoginViewModel),但它不知道如何解析构造函数 - 因为您通过服务进行了注入,它不再是无参数的。

您必须提供一种方法,以强制DataContextServiceProvider(所有已注入服务的容器)实例化。

您可以通过使用可在XAML中使用的帮助类(继承自MarkupExtension)来实现这一点,就像下面的示例一样:

public class ServiceDispatcher : MarkupExtension
{
    public static Func<Type, object> Resolver { get; set; }
    public Type Type { get; set; }
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return Resolver?.Invoke(Type);
    }
}

并在App.xaml.cs中分配其服务分发Resolver委托:

public App()
{
    AppHost = Host.CreateDefaultBuilder()
         .ConfigureServices((hostContext, services) =>
         {
             services.AddSingleton<LoginWindow>();
             services.AddSingleton<LoginViewModel>();

             services.AddDbContext<KnitterNotebookContext>(
                 options =>
                 {
                     string appSettingsPath = Path.Combine(ProjectDirectory.ProjectDirectoryFullPath, "appsettings.json");
                     string appSettingsString = File.ReadAllText(appSettingsPath);
                     AppSettings AppSettings = JsonConvert.DeserializeObject<AppSettings>(appSettingsString)!;
                     options.UseSqlServer(AppSettings.KnitterNotebookConnectionString);
                 });
         })
         .Build();
         
    ConfigureServiceProvider(AppHost.Services);
}

private static void ConfigureServiceProvider(IServiceProvider appHost)
{
    ServiceDispatcher.Resolver = (type) =>
    {
        return appHost.GetRequiredService(type);
    };
}

现在,您可以使用您的ServiceDispatcher以以下语法动态提供DataContext实例给您的Views

<Window.DataContext>
    <base:ServiceDispatcher Type="{x:Type local:LoginViewModel}"/>
</Window.DataContext>
英文:

I happened to encounter the same problem at one time - i'm guessing that you are setting DataContext for MainWindow in xaml, like this:

&lt;Window.DataContext&gt;
    &lt;local:LoginViewModel/&gt;
&lt;Window.DataContext&gt;

Doing it this way, when your LoginWindow is created, tries to create new instance of its DataContext (your LoginViewModel), but it does not know how to resolve constructor - since you injected it with service, it's not parameterless anymore.

You have to provide a way, to enforce DataContext to be instantiated by ServiceProvider (a container for all injected services).

You could achieve this through use of a helper class, which is usable in xaml (inherits from MarkupExtension), like this one below.

public class ServiceDispatcher : MarkupExtension
{
	public static Func&lt;Type, object&gt; Resolver { get; set; }
	public Type Type { get; set; }
	public override object ProvideValue(IServiceProvider serviceProvider)
	{
		return Resolver?.Invoke(Type);
	}
}

And assigning it's service dispatching Resolver delegate in your App.xaml.cs

public App()
{
	AppHost = Host.CreateDefaultBuilder()
		 .ConfigureServices((hostContext, services) =&gt;
		 {
			 services.AddSingleton&lt;LoginWindow&gt;();
			 services.AddSingleton&lt;LoginViewModel&gt;();

			 services.AddDbContext&lt;KnitterNotebookContext&gt;(
				 options =&gt;
				 {
					 string appSettingsPath = Path.Combine(ProjectDirectory.ProjectDirectoryFullPath, &quot;appsettings.json&quot;);
					 string appSettingsString = File.ReadAllText(appSettingsPath);
					 AppSettings AppSettings = JsonConvert.DeserializeObject&lt;AppSettings&gt;(appSettingsString)!;
					 options.UseSqlServer(AppSettings.KnitterNotebookConnectionString);
				 });
		 })
		 .Build();
		 
	ConfigureServiceProvider(AppHost.Services);
}

private static void ConfigureServiceProvider(IServiceProvider appHost)
{
	ServiceDispatcher.Resolver = (type) =&gt;
	{
		return appHost.GetRequiredService(type);
	};
}

Now you can use your ServiceDispatcher to dynamically provide DataContext instance for your Views using syntax below.

&lt;Window.DataContext&gt;
	&lt;base:ServiceDispatcher Type=&quot;{x:Type local:LoginViewModel}&quot;/&gt;
&lt;/Window.DataContext&gt;

huangapple
  • 本文由 发表于 2023年2月10日 06:05:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/75404921.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定