[英]How to define DbContext scope in WPF application similar to ASP.NET?
我想使用相同的技术从 ASP.NET MVC 和 WPF 访问我的数据库,并决定使用 EF Core。
在 ASP.NET 中,我可以将 DbContext(UnitOfWork、AppBLL 等)注入到 Controller 中,它的生命周期仅限于请求/响应操作。 据我了解,scope 是使用 ASP.NET 中间件管道在幕后创建的。
在 WPF 中,但是 scope 必须根据应用程序用例定义,这是完全合乎逻辑的,因为有时需要长时间操作(例如使用 DbSet.Local.ToObservableCollection() 和 DataGrid),有时操作更简单(例如更新一个实体)。
我想在 WPF 中实现与 ASP.NET MVC 有点相似的行为,基本上我想将 DbContext 注入 ViewModel 构造函数,然后每个方法都是它自己的 scope 因此每个方法调用都有不同的短暂 DbContext。
我将 Microsoft.Extensions.DependencyInjection 和 CommunityToolkit.MVVM 用于 DI,并使用 AddDbContext 添加 DbContext。 这使得它有范围但没有定义范围它实际上是 singleton。我不想在每次进行数据库操作时使用 using(var scope =...) 和 GetRequiredService 定义 scope,因为它会污染我的代码。
正如我所说,我希望它像 ASP.NET 中那样光滑。
因此,我尝试使用 PostSharp 进行面向方面的编程,但是向使用 DbContext 的每个方法添加属性感觉有点乏味,而且还存在对可测试性的担忧。
我尝试实现抽象 class,它具有将 lambda with operation 传递给的方法,并且 lambda 在 using(var scope =...) 表达式中创建,因此使用作用域 DbContext。 在处置 scope 时,DbContext 会自动处置。 然而,这些方法仍然与 ASP.NET 机制相去甚远,我希望 DbContext 生命周期得到更智能的管理。
如果我将 DbContext 定义为瞬态,那么它只会在注入 ViewModel 时创建,不会为方法重新创建。 顺便说一下,我是否正确理解在 ASP.NET 中,当 DbContext 注入 Controller 时,它会在构建后立即处理,因为 scope 已完成?
编辑:
这是示例项目 Github 存储库的链接
https://github.com/anguzo/EF-CORE-EXAMPLE
WpfApp 项目就是这个问题的例子——你不能更新同一个实体两次,因为它已经被跟踪了。
其他 WPF 项目演示了我提出的这个问题的不同解决方案。
最接近 ASP.NET 行为的是使用 PostSharp 自定义属性。 它需要将属性应用于 ViewModel class 并修改每个方法(不包括“特殊方法”:getters、setters 和 consturctors)。
我仍然迷路了,因为官方 Microsoft 文档和 Inte.net 上的其他材料没有描述这个问题的正确解决方案。
依赖注入容器是一个实例 class。
使用 mvc 有一个请求管道,它提供它从该容器实例化的任何东西的参数。
mvc 和 wpf 之间的区别在于,对于 wpf,您没有该管道,因此每当您实例化 class 时,您都需要以某种方式提供容器实例,您想要注入任何东西。
它在这里涵盖得很好:
https://learn.microsoft.com/en-us/windows/communitytoolkit/mvvm/ioc
您不希望所有内容都是 singleton,并且您不希望在启动应用程序时解析整个 object 图。
正如您所发现的,无论如何这都是不切实际的,因为您希望某些事情是短暂的。
传递 DI 容器很麻烦。 您可能有任意数量的层到您的 dbcontext。
通常的做法是让注入容器在整个应用程序中可用。
在那里的代码中,他们向 App 添加了一个属性以将该实例存储在:
public sealed partial class App : Application
{
public App()
{
Services = ConfigureServices();
this.InitializeComponent();
}
/// <summary>
/// Gets the current <see cref="App"/> instance in use
/// </summary>
public new static App Current => (App)Application.Current;
/// <summary>
/// Gets the <see cref="IServiceProvider"/> instance to resolve application services.
/// </summary>
public IServiceProvider Services { get; }
App.Services 是你的依赖注入容器。 您从中取出实例的魔法袋。
在这些服务注册的地方,它们都是单例的,但是你的 dbcontext 应该是
services.AddTransient<IMyDbContext, MyDbContext>();
并且您的存储库将在其构造函数中采用 IMyDbContext ,该构造函数将从容器中提供。
您需要引用 App 公开的容器来解析实例:
var repo = App.Current.Services.GetService<IRepository>();
因为它在 App.Current 上,所以您代码中的任何地方都可以引用它。 只要您有一个常规的 wpf 入口点。
您的存储库将通过注入获得 dbcontext 的实例,无论您对该存储库做什么,您都会这样做。 一旦它超出 scope,存储库和 dbconnection 将准备好进行垃圾回收,只要它们都是瞬态的。
瞬态将在 scope 中的 go 之后被处理。因此,如果您从 mvc controller 向下传递一个瞬态 dbcontext,那么它将保留在 scope 中,直到从 controller 发送响应。它只会在控制器返回后被拆除结果是 scope。
我要更改的代码方面是在 di 容器周围添加一个抽象外观。 如果您在任何类中使用它,您希望能够最小化从该容器中获取 class 实例的任何内容。 如果你在单元测试中使用 app.current 那么你需要实例化一个应用程序 object。
Andy 概述的内容与我使用的差不多,它通常被称为服务定位器模式,并且作为反模式享有一定声誉,但对于 WPF,如果您的团队中有一些纪律,它会非常有效以及如何使用它。
我最近写了一个关于在 Autofac 中使用 ServiceLocator 模式和惰性依赖属性的简短片段,它可以提供一些关于如何应用该模式的想法:
private readonly IContainerScope _scope;
private ISomeDependency? _someDependency = null;
public ISomeDependecy SomeDependency
{
get => _someDependency ??= _scope.Resolve<ISomeDependency>()
?? throw new ArgumentException("The SomeDependency dependency could not be resolved.");
set => _someDependency = value;
}
public SomeClass(IContainer container)
{
if (container == null) throw new ArgumentNullException(nameof(container));
_scope = container.BeginLifetimeScope();
}
public void Dispose()
{ // TODO: implement proper Dispose pattern.
_scope.Dispose();
}
这为您提供了一个模式,为您提供顶级依赖项的默认生命周期 scope,它可以是 MVVM ViewModel 和/或导航提供程序等,因此您的依赖项可以由 Transient、Singleton 或 PerLifeTimeScope 管理。 (适用于 EF DbContext 之类的东西)因此,例如,当加载表单时,可以在该表单的生命周期内解析 DbContext 实例。
我将该模式称为惰性属性模式,因为只有当/当通过属性访问它时才会解析实际的依赖关系。 这样做的好处是,当使用经典的构造函数注入编写单元测试时,您需要模拟每个测试的每个依赖项,而对于“惰性”属性,您只需将 Setter 用于所需的依赖项。 被测 class 的初始化接受一个模拟容器,如果它的Resolve<T>
方法被调用,该容器只会抛出一个异常使测试失败。 (无需为测试构建功能性模拟容器来解决模拟依赖项或其他此类复杂性)
这种模式的警告,以及与团队合作检查的一个建议是,_scope 引用应该只从依赖属性访问,而不是随意地通过代码来临时解决依赖关系。 例如,如果需要新的依赖项,请通过 scope 声明要解析的属性访问器,而不仅仅是在代码中编写_scope.Resolve<NewDependency>()
。 虽然您仍然可以围绕此类代码进行测试,但您需要设置测试以提供能够解析 Mock 的 _scope,这确实很混乱。 遵循属性模式使测试变得更加简单,以通过 Setter 提供预期的依赖关系,并在意外的依赖关系碰巧被访问时获得异常/失败。
它不是 proper-C#-Lazy,尽管我确实为注入的依赖项和 web 项目使用了适当的Lazy
依赖模式,其中生命周期 scope 得到了方便的管理:
private readonly Lazy<ISomeDependency>? _lazySomeDependency = null;
private ISomeDependency? _someDependency = null;
public ISomeDependency SomeDependency
{
get => _someDependency ??= _lazySomeDependency?.Value
?? throw new ArgumentException("SomeDependency dependency was not provided.");
set => _someDependency = value;
}
public SomeClass(Lazy<ISomeDependency>? someDependency = null)
{
_lazySomeDependency = someDependency;
}
Autofac 确实支持惰性初始化,因此这种模式意味着对于正常执行,Autofac 将属性作为惰性引用提供,如果/当它们被访问时将解析这些属性。 对于单元测试,您只需使用一个空的构造函数进行初始化,然后使用 Setters 来设置您期望实际使用的依赖项。 上面带有 1 个依赖项的示例并没有真正证明它的价值,但是想象一下 class 的测试套件有 8 或 12 个依赖项,其中被测方法可能只涉及其中几个。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.