簡體   English   中英

如何在WPF應用中定義DbContext scope 類似於ASP.NET?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM