簡體   English   中英

在 WPF MVVM 應用程序中管理 DbContext

[英]Managing DbContext in WPF MVVM application

幾天來我一直在思考這個問題,但仍然無法決定哪種方法是正確的。
這個問題是專門針對WPF的,因為與網絡應用程序相反,許多在線帖子和文章推薦 a context per view-model approach 而不是 a context per request
我有一個WPF MVVM應用程序,它首先使用Entity-Framework DB first model。
這是我的應用程序中使用的兩個模型的示例(由EF Designer 創建):

public partial class User
{
    public User()
    {
        this.Role = new HashSet<Role>();
    }

    public string ID { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Role> Role { get; set; }
}

public class Role
{
    public Role()
    {
        this.User = new HashSet<User>();
    }

    public int ID { get; set; }
    public string Name { get; set; }

    public virtual ICollection<User> User { get; set; }
}

我已將處理此問題的選項縮小到以下范圍:

1)創建一個DataAccess class,它在每次方法調用時創建和處理DbContext

public class Dal
{
    public User GetUserById(object userId)
    {
        using (var db = new DbEntities())
        {
            return db.User.Find(userId);
            db.SaveChanges();
        }
    }

    public void RemoveUser(User userToRemove)
    {
        using (var db = new DbEntities())
        {
            db.User.Remove(userToRemove);
            db.SaveChanges();
        }
    }
}

我可以在我的ViewModel中使用它,如下所示:

public class UserManagerViewModel : ObservableObject
{
    private readonly Dal dal = new Dal();

    // models...
    //commands...
}

2)與方法 1 類似但沒有Using語句:

public class Dal : IDisposable
{
    private readonly DbEntities db = new DbEntities();
    public User GetUserById(object userId)
    {
        return db.User.Find(userId);
        db.SaveChanges();

    }

    public void RemoveUser(User userToRemove)
    {
        db.User.Remove(userToRemove);
        db.SaveChanges();
    }

    public void Dispose()
    {
        db.SaveChanges();
    }
}

ViewModel內部的使用是一樣的

3)為每個entity創建一個repository 看起來與上述選項相同(也有使用或不using的困境),但是每個存儲庫僅包含與其相關的方法entity
Afaik 在我的ViewModel中使用與上面相同。

4)創建一個Unit-Of-Work class,它將按需傳遞適當的Repository

public class UnitOfWork : IDisposable
{
    private DbEntities db = new DbEntities();

    private IUserRepository userRepository;
    public IUserRepository UserRepository
    {
        get
        {
            return userRepository ?? new UsersRepository(db);
        }
    }

    public void Save()
    {
        db.SaveChanges();
    }

    public void Dispose()
    {
        db.Dispose();
    }
}

並在我的ViewModel中使用它,如下所示:

public class UserManagerViewModel : ObservableObject
{
    private readonly UnitOfWork unit = new UnitOfWork();

    // models...
    //commands...
}

就數據並發性、更好的抽象和分層以及整體性能而言,上述哪種方法(如果有)是首選?
編輯 -本文中找到以下段落。 :

使用 Windows Presentation Foundation (WPF) 或 Windows Forms 時,請為每個表單使用上下文實例。 這使您可以使用上下文提供的更改跟蹤功能。

但是,它提出了一個問題,我是否應該在我的view-model中創建一個DbContext object,或者最好有一個實用程序 class,例如我的DAL class 並引用它。

這就是依賴注入框架旨在解決的問題。 是的,這是添加到您的項目中的另一項技術,但是一旦您開始使用DI,您就永遠不會回頭。

這里真正的問題是,當你真的應該采用控制反轉並使決策更高時,你試圖在你的視圖模型中做出這個決定。 WPF / MVVM應用程序將需要每個表單的上下文,以便僅在用戶完成編輯后提交更改,並且還允許用戶取消更改。 我知道你沒有在Web應用程序中使用它,但是設計良好的架構意味着你應該能夠,在這種情況下你需要每個請求一個上下文。 您可能希望編寫一個控制台應用程序實用程序,該實用程序使用靜態數據填充數據庫,在這種情況下,您可能需要全局/單一上下文來提高性能和易用性。 最后,您的單元測試還需要模擬上下文,可能是基於每個測試。 所有這四種情況都應該在您的注射框架中進行設置,您的視圖模型不應該知道或關心它們中的任何一種。

這是一個例子。 我個人使用Ninject,它是專門為.NET設計的。 我也更喜歡NHibernate,盡管ORM的選擇與此無關。 我有會話對象具有不同的作用域要求,這在初始化我的ORM類的Ninject模塊中設置:

var sessionBinding = Bind<ISession>().ToMethod(ctx =>
{
    var session = ctx.Kernel.Get<INHibernateSessionFactoryBuilder>()
        .GetSessionFactory()
        .OpenSession();
    return session;
});

if (this.SingleSession)
    sessionBinding.InSingletonScope();
else if (this.WebSession)
    sessionBinding.InRequestScope();
else
    sessionBinding.InScope(ScreenScope);

這為ISession設置了范圍,ISession是您的上下文類的NHibernate等價物。 我的存儲庫類管理內存中的數據庫對象,包含對與之關聯的會話的引用:

public class RepositoryManager : IRepositoryManager
{
    [Inject]
    public ISession Session { get; set; }

    ... etc...
{

[Inject]屬性告訴Ninject使用我設置的作用域規則自動填充此字段。 到目前為止,這一切都發生在我的域類中,但它也擴展到我的視圖模型層。 在我的范圍規則中,我傳遞了一個名為“ScreenScope”的對象,雖然我不會在這里進入它,但它基本上意味着我在我的ScreenViewModel中請求會話對象,或者它作為成員的任何視圖模型(包括他們自己的孩子)自動創建相同的ISession對象並將其傳遞給所有這些對象。 通過使用DI范圍我甚至不必考慮它,我只是使用[Inject]屬性聲明成員並且它發生:

public class ScreenViewModel
{
    [Inject] public CustomerService CustomerService { get; set; }
    [Inject] public SalesService SalesService { get; set; }
    [Inject] public BillService BillService { get; set; }
    ...etc...
}

這些服務類都包含一個已注入的RepositoryManager,並且因為它們都在ScreenViewModel中,所以ISession對象將是相同的,至少在我的WPF構建中。 如果我切換到我的MVC構建,它們對於為給定請求創建的所有視圖模型都是相同的,如果我切換到控制台構建,它會對整個程序中的所有內容使用相同的ISession。

TL; DR:使用依賴注入和上下文的范圍為每個表單一個。

在我之前在 WPF 中使用 MVVM 時,我在每個 VM 中使用一個開放的上下文,但是一旦應用程序發展到可以更好地利用異步,我很快就遇到了 DBContext 的線程安全問題。

雖然開發開銷更大,但我現在利用依賴注入來提供 DBContextFactory(而不是 DBContext 本身)。 我在使用 VM 的 using 語句中啟動上下文,以通過 EF 使用 plinq 調用填充 observableCollections。 此方法的另一個性能優勢是使用 AsNoTracking() 運行查詢。 煩人的部分是管理將新的或修改的對象重新附加到短暫的上下文中:

shortDBContext.Attach(myEntity).State = EntityState.Added; // or modified
await shortDBContext.SaveChangesAsync();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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