简体   繁体   English

如何在WPF应用中定义DbContext scope 类似于ASP.NET?

[英]How to define DbContext scope in WPF application similar to ASP.NET?

I want to use same technology to access my database both from ASP.NET MVC and WPF and decided to use EF Core.我想使用相同的技术从 ASP.NET MVC 和 WPF 访问我的数据库,并决定使用 EF Core。

In ASP.NET I can inject DbContext(UnitOfWork, AppBLL etc) into Controller and its lifecycle is scoped to request/response operation.在 ASP.NET 中,我可以将 DbContext(UnitOfWork、AppBLL 等)注入到 Controller 中,它的生命周期仅限于请求/响应操作。 As I understood the scope is created behind the scenes using ASP.NET middleware pipeline.据我了解,scope 是使用 ASP.NET 中间件管道在幕后创建的。

In WPF however scope must be defined based on the app use case which is completely logical since some times there is need for long operation (for example using DbSet.Local.ToObservableCollection() and DataGrid) and sometimes operation is more trivial (for example update one entity).在 WPF 中,但是 scope 必须根据应用程序用例定义,这是完全合乎逻辑的,因为有时需要长时间操作(例如使用 DbSet.Local.ToObservableCollection() 和 DataGrid),有时操作更简单(例如更新一个实体)。

I want to achieve somewhat similar behavior to ASP.NET MVC in WPF, basically I want to inject DbContext into ViewModel constructor and then each method to be its own scope thus have different short-lived DbContext each method call.我想在 WPF 中实现与 ASP.NET MVC 有点相似的行为,基本上我想将 DbContext 注入 ViewModel 构造函数,然后每个方法都是它自己的 scope 因此每个方法调用都有不同的短暂 DbContext。

I use Microsoft.Extensions.DependencyInjection and CommunityToolkit.MVVM for DI and add DbContext using AddDbContext.我将 Microsoft.Extensions.DependencyInjection 和 CommunityToolkit.MVVM 用于 DI,并使用 AddDbContext 添加 DbContext。 This makes it scoped but without defining the scopes it is effectively singleton. I do not want to define scope with using(var scope =...) and GetRequiredService each time I make a db operation since it will pollute my code.这使得它有范围但没有定义范围它实际上是 singleton。我不想在每次进行数据库操作时使用 using(var scope =...) 和 GetRequiredService 定义 scope,因为它会污染我的代码。

As I said I want it to be sleek like in ASP.NET.正如我所说,我希望它像 ASP.NET 中那样光滑。

So I tried aspect oriented programming with PostSharp but feels kinda dull to add attributes to each method that uses DbContext also there are concerns about testability.因此,我尝试使用 PostSharp 进行面向方面的编程,但是向使用 DbContext 的每个方法添加属性感觉有点乏味,而且还存在对可测试性的担忧。

I tried implementing abstract class which has methods to which lambda with operation is passed and lambda is created inside a using(var scope =...) expression and thus uses scoped DbContext.我尝试实现抽象 class,它具有将 lambda with operation 传递给的方法,并且 lambda 在 using(var scope =...) 表达式中创建,因此使用作用域 DbContext。 At the disposal of the scope DbContext is automatically disposed with it.在处置 scope 时,DbContext 会自动处置。 These approaches however are still distant from ASP.NET mechanism and I want DbContext lifecycle to be managed smarter.然而,这些方法仍然与 ASP.NET 机制相去甚远,我希望 DbContext 生命周期得到更智能的管理。

If I define DbContext as transient then it is only created on injection into ViewModel and not re-created for methods.如果我将 DbContext 定义为瞬态,那么它只会在注入 ViewModel 时创建,不会为方法重新创建。 By the way, do I understand correctly that in ASP.NET when DbContext is injected into Controller it is disposed right after the construction, since the scope is finished?顺便说一下,我是否正确理解在 ASP.NET 中,当 DbContext 注入 Controller 时,它会在构建后立即处理,因为 scope 已完成?

EDIT:编辑:

Here is a link to example project Github repository这是示例项目 Github 存储库的链接

https://github.com/anguzo/EF-CORE-EXAMPLE https://github.com/anguzo/EF-CORE-EXAMPLE

WpfApp project is example of the problem - you can't update same entity twice since it's already being tracked. WpfApp 项目就是这个问题的例子——你不能更新同一个实体两次,因为它已经被跟踪了。

Other WPF projects demonstrate different solutions to this problem I came up with.其他 WPF 项目演示了我提出的这个问题的不同解决方案。

The closest to the ASP.NET behavior is using PostSharp custom attribute.最接近 ASP.NET 行为的是使用 PostSharp 自定义属性。 It requires attribute to be applied to ViewModel class and modifies each method(excluding "special methods": getters, setters and consturctors).它需要将属性应用于 ViewModel class 并修改每个方法(不包括“特殊方法”:getters、setters 和 consturctors)。

I am still lost since official Microsoft documentation and other materials on the Inte.net do not describe proper solution to this problem.我仍然迷路了,因为官方 Microsoft 文档和 Inte.net 上的其他材料没有描述这个问题的正确解决方案。

The dependency injection container is an instance class.依赖注入容器是一个实例 class。

With mvc there is a request pipeline that provides the parameters of anything instantiated by it from that container.使用 mvc 有一个请求管道,它提供它从该容器实例化的任何东西的参数。

The difference between mvc and wpf is that with wpf you don't have that pipeline so you need to provide the container instance somehow whenever you instantiate a class you want anything injected to. mvc 和 wpf 之间的区别在于,对于 wpf,您没有该管道,因此每当您实例化 class 时,您都需要以某种方式提供容器实例,您想要注入任何东西。

It's covered pretty well here:它在这里涵盖得很好:

https://learn.microsoft.com/en-us/windows/communitytoolkit/mvvm/ioc https://learn.microsoft.com/en-us/windows/communitytoolkit/mvvm/ioc

You do not want everything to be a singleton and you do not want to resolve the entire object graph as you crank up your application.您不希望所有内容都是 singleton,并且您不希望在启动应用程序时解析整个 object 图。

As you spotted, that's impractical anyhow because you want some things to be transient.正如您所发现的,无论如何这都是不切实际的,因为您希望某些事情是短暂的。

Passing the DI container around is a nuisance.传递 DI 容器很麻烦。 You might have any number of layers down to your dbcontext.您可能有任意数量的层到您的 dbcontext。

What is usual is to make the injection container available throughout the app.通常的做法是让注入容器在整个应用程序中可用。

In the code there, they've added a property to App to stash that instance in:在那里的代码中,他们向 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 is your dependency injection container. App.Services 是你的依赖注入容器。 The magic bag you grab your instances out of.您从中取出实例的魔法袋。

Where these services are registered they're all singletons there, but your dbcontext should be在这些服务注册的地方,它们都是单例的,但是你的 dbcontext 应该是

  services.AddTransient<IMyDbContext, MyDbContext>();

And your repository would take an IMyDbContext in it's constructor which would be supplied out the container.并且您的存储库将在其构造函数中采用 IMyDbContext ,该构造函数将从容器中提供。

You need to reference that container exposed by App to resolve an instance:您需要引用 App 公开的容器来解析实例:

  var repo = App.Current.Services.GetService<IRepository>();

Because it's on App.Current, everywhere in your code will be able to reference it.因为它在 App.Current 上,所以您代码中的任何地方都可以引用它。 So long as you have a regular wpf entry point.只要您有一个常规的 wpf 入口点。

Your repository would get an instance of dbcontext via injection and you'd so whatever you're doing with that repository.您的存储库将通过注入获得 dbcontext 的实例,无论您对该存储库做什么,您都会这样做。 Once it goes out of scope the repository and dbconnection would be ready for garbage collection so long as they're both Transient.一旦它超出 scope,存储库和 dbconnection 将准备好进行垃圾回收,只要它们都是瞬态的。

Transients will be disposed after they go out of scope. Hence if you pass a transient dbcontext down from a mvc controller then it will stay in scope until the response is sent from the controller. It's only going to be torn down after the controller's returned it's result and goes out of scope.瞬态将在 scope 中的 go 之后被处理。因此,如果您从 mvc controller 向下传递一个瞬态 dbcontext,那么它将保留在 scope 中,直到从 controller 发送响应。它只会在控制器返回后被拆除结果是 scope。

The aspect of the code which I would change is to add an abstracted facade around the di container.我要更改的代码方面是在 di 容器周围添加一个抽象外观。 You want to be able to moq whatever grabs a class instance out that container if you use it within any classs.如果您在任何类中使用它,您希望能够最小化从该容器中获取 class 实例的任何内容。 If you use app.current in unit testing then you need to instantiate an application object.如果你在单元测试中使用 app.current 那么你需要实例化一个应用程序 object。

What Andy outlines is pretty much what I use, it's commonly referred to as a Service Locator pattern, and has a bit of a reputation as an anti-pattern, but for WPF it is pretty effective if you have some discipline within your team about where and how it is allowed to be used. Andy 概述的内容与我使用的差不多,它通常被称为服务定位器模式,并且作为反模式享有一定声誉,但对于 WPF,如果您的团队中有一些纪律,它会非常有效以及如何使用它。

I'd recently written a quick snippet on using the ServiceLocator pattern and lazy dependency properties with Autofac which can provide some ideas on how the pattern can be applied:我最近写了一个关于在 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();
}

This gives you a pattern gives you a default lifetime scope for the life of a top-level dependency which could be a MVVM ViewModel, and/or navigation provider, etc. so your dependencies can be managed by Transient, Singleton, or PerLifeTimeScope.这为您提供了一个模式,为您提供顶级依赖项的默认生命周期 scope,它可以是 MVVM ViewModel 和/或导航提供程序等,因此您的依赖项可以由 Transient、Singleton 或 PerLifeTimeScope 管理。 (Suited to something like the EF DbContext) So for instance when a form is loaded the DbContext instance can be resolved for the lifetime of that form. (适用于 EF DbContext 之类的东西)因此,例如,当加载表单时,可以在该表单的生命周期内解析 DbContext 实例。

I call the pattern a lazy property pattern as the actual dependency is resolved only if/when it is accessed via the property.我将该模式称为惰性属性模式,因为只有当/当通过属性访问它时才会解析实际的依赖关系。 The advantage of this is when writing unit tests with classic constructor injection, you need to mock every dependency for every test, where with "lazy" properties you use the Setter for just the dependencies you need.这样做的好处是,当使用经典的构造函数注入编写单元测试时,您需要模拟每个测试的每个依赖项,而对于“惰性”属性,您只需将 Setter 用于所需的依赖项。 The initialization of the class under test accepts a mocked container that simply throws an exception to fail the test if it's Resolve<T> method is called.被测 class 的初始化接受一个模拟容器,如果它的Resolve<T>方法被调用,该容器只会抛出一个异常使测试失败。 (No need to build a functional mock container for tests to resolve mocked dependencies or other such complexity) (无需为测试构建功能性模拟容器来解决模拟依赖项或其他此类复杂性)

The caveat of this pattern, and one suggestion to work with the team on to check is that the _scope reference should only be accessed from the dependency properties, not willy-nilly through the code to resolve dependencies ad-hoc.这种模式的警告,以及与团队合作检查的一个建议是,_scope 引用应该只从依赖属性访问,而不是随意地通过代码来临时解决依赖关系。 For instance if a new dependency is needed, declare the property accessor to resolve via the scope rather than just writing a _scope.Resolve<NewDependency>() in the code.例如,如果需要新的依赖项,请通过 scope 声明要解析的属性访问器,而不仅仅是在代码中编写_scope.Resolve<NewDependency>() While you can still test around such code, you'd need to set up your tests to provide a _scope capable of resolving a Mock, which is honestly messy.虽然您仍然可以围绕此类代码进行测试,但您需要设置测试以提供能够解析 Mock 的 _scope,这确实很混乱。 Following the property pattern makes testing a lot simpler to provide the expected dependency via the Setter and getting an exception/failure if an unexpected dependency happens to get accessed.遵循属性模式使测试变得更加简单,以通过 Setter 提供预期的依赖关系,并在意外的依赖关系碰巧被访问时获得异常/失败。

It's not proper-C#-Lazy, though I do use a proper Lazy dependency pattern for injected dependencies, and web projects where the lifetime scope is conveniently managed:它不是 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 does support lazy initialization so this pattern means that for normal execution, Autofac provides the properties as Lazy references which will resolve if/when they are accessed. Autofac 确实支持惰性初始化,因此这种模式意味着对于正常执行,Autofac 将属性作为惰性引用提供,如果/当它们被访问时将解析这些属性。 For unit tests, you just initialize with an empty constructor and use the Setters to set the dependencies you expect to actually be used.对于单元测试,您只需使用一个空的构造函数进行初始化,然后使用 Setters 来设置您期望实际使用的依赖项。 The above example with 1 dependency doesn't really demonstrate the value of that, but picture a test suite for a class that has 8 or 12 dependencies where a method under test will likely only touch a couple of them.上面带有 1 个依赖项的示例并没有真正证明它的价值,但是想象一下 class 的测试套件有 8 或 12 个依赖项,其中被测方法可能只涉及其中几个。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在我的 ASP.NET 应用程序中为文件定义正确的路径 - How to define correct path for file in my ASP.NET application 如何使用 ASP.NET MVC 项目参考从控制台应用程序使用 ASP.NET MVC 的 MYSQL DbContext - How to use MYSQL DbContext of ASP.NET MVC from console application using ASP.NET MVC project reference 是否可以在分层 ASP.NET Core 应用程序中的 DAL 中使用 DbContext? - Is it possible to have DbContext in DAL in layered ASP.NET Core application? ASP.NET 5控制台应用程序(程序包)-如何从连接字符串创建DBContext? - ASP.NET 5 Console Application (package) - How to create DBContext from connection string? ASP.NET BoilerPlate:如何在应用程序服务中注入DbContext对象? - ASP.NET BoilerPlate: How do I inject object of DbContext in application service? 在asp.net应用程序中定义角色 - Define Roles in asp.net application 为ASP.NET应用程序定义globaly DisplayFormatString - Define globaly DisplayFormatString for ASP.NET application 如何在 WPF 和 ASP.NET 应用程序之间进行通信? - How do I communicate between a WPF and ASP.NET application? Asp.net身份DbContext - Asp.net Identity DbContext 将ASP.Net Core集成到WPF应用程序中 - Integrate ASP.Net Core into an WPF application
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM