简体   繁体   English

在 WPF MVVM 应用程序中管理 DbContext

[英]Managing DbContext in WPF MVVM application

I've been banging my head with this for days and still can't decide on which is the correct approach.几天来我一直在思考这个问题,但仍然无法决定哪种方法是正确的。
This question is targeting WPF specifically since as opposed to a web-application, many posts and articles online recommends a context per view-model approach and not a context per request .这个问题是专门针对WPF的,因为与网络应用程序相反,许多在线帖子和文章推荐 a context per view-model approach 而不是 a context per request
I have a WPF MVVM application which is using an Entity-Framework DB first model.我有一个WPF MVVM应用程序,它首先使用Entity-Framework DB first model。
here is an example of two models used in my app (created by EF Designer):这是我的应用程序中使用的两个模型的示例(由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; }
}

I've narrowed my options on how to handle this to the following:我已将处理此问题的选项缩小到以下范围:

1) Creating a DataAccess class which creates and disposes of the DbContext on each method call: 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();
        }
    }
}

which I can use in my ViewModel as follows:我可以在我的ViewModel中使用它,如下所示:

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

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

2) Similar to approach 1 but without the Using statements: 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();
    }
}

The use is the same inside the ViewModel ViewModel内部的使用是一样的

3) Create a repository for each entity . 3)为每个entity创建一个repository Looks the same as the above options (also has the with or without the using dilemma), however every repository contains only methods related to its entity .看起来与上述选项相同(也有使用或不using的困境),但是每个存储库仅包含与其相关的方法entity
Afaik the use is the same as above inside my ViewModel . Afaik 在我的ViewModel中使用与上面相同。

4) Create a Unit-Of-Work class that will pass the appropriate Repository on demand: 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();
    }
}

and use it inside my ViewModel as follows:并在我的ViewModel中使用它,如下所示:

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

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

Which of the above approach (if any) is preferred in terms of in terms of data concurrency, better abstraction and layering and overall performance?就数据并发性、更好的抽象和分层以及整体性能而言,上述哪种方法(如果有)是首选?
EDIT - Found the following paragraph in this article.编辑 -本文中找到以下段落。 : :

When working with Windows Presentation Foundation (WPF) or Windows Forms, use a context instance per form.使用 Windows Presentation Foundation (WPF) 或 Windows Forms 时,请为每个表单使用上下文实例。 This lets you use change-tracking functionality that context provides.这使您可以使用上下文提供的更改跟踪功能。

However, it raises the question of whether I should create a DbContext object in my view-model or is it better to have a utility class such as my DAL class and reference it.但是,它提出了一个问题,我是否应该在我的view-model中创建一个DbContext object,或者最好有一个实用程序 class,例如我的DAL class 并引用它。

This is what dependency injection frameworks are designed to solve. 这就是依赖注入框架旨在解决的问题。 Yes, it's yet another technology to add to your project, but once you start using DI you never look back. 是的,这是添加到您的项目中的另一项技术,但是一旦您开始使用DI,您就永远不会回头。

The real problem here is that you're trying to make this decision in your view models when you really should be employing inversion of control and making the decision higher up. 这里真正的问题是,当你真的应该采用控制反转并使决策更高时,你试图在你的视图模型中做出这个决定。 A WPF/MVVM application will want a context per-form so that changes are only submitted once a user is finished editing, and also to give the user the opportunity to cancel the changes. WPF / MVVM应用程序将需要每个表单的上下文,以便仅在用户完成编辑后提交更改,并且还允许用户取消更改。 I know you're not using this in a web application but a well-designed architecture means you should be able to, in which case you'll want a context per request. 我知道你没有在Web应用程序中使用它,但是设计良好的架构意味着你应该能够,在这种情况下你需要每个请求一个上下文。 You may want to write a console-app utility that populates the database with static data, in this case you may want a global/singleton context for performance and ease-of-use. 您可能希望编写一个控制台应用程序实用程序,该实用程序使用静态数据填充数据库,在这种情况下,您可能需要全局/单一上下文来提高性能和易用性。 Lastly, your unit tests also need to mock the context, probably on a per-test basis. 最后,您的单元测试还需要模拟上下文,可能是基于每个测试。 All four of these cases should be set up in your injection framework and your view models should neither know or care about any of them. 所有这四种情况都应该在您的注射框架中进行设置,您的视图模型不应该知道或关心它们中的任何一种。

Here's an example. 这是一个例子。 I personally use Ninject, which is specifically designed for .NET. 我个人使用Ninject,它是专门为.NET设计的。 I also prefer NHibernate, although the choice of ORM is irrelevant here. 我也更喜欢NHibernate,尽管ORM的选择与此无关。 I have session objects that have different scoping requirements, and this gets set up in a Ninject module that initializes my ORM classes: 我有会话对象具有不同的作用域要求,这在初始化我的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);

This sets up the scoping for an ISession, which is the NHibernate equivalent of your context class. 这为ISession设置了范围,ISession是您的上下文类的NHibernate等价物。 My repository classes, which manage the database objects in memory, contain a reference to the session they are associated with: 我的存储库类管理内存中的数据库对象,包含对与之关联的会话的引用:

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

    ... etc...
{

The [Inject] attribute tells Ninject to populate this field automatically using the scoping rules I've set up. [Inject]属性告诉Ninject使用我设置的作用域规则自动填充此字段。 So far this is all happening in my domain classes, but it extends to my view model layer as well. 到目前为止,这一切都发生在我的域类中,但它也扩展到我的视图模型层。 In my scoping rules I pass in an object called "ScreenScope", and while I won't go into it here it basically means that any time I ask for a session object in my ScreenViewModel, or any view models that it has as members (including their own children) the same ISession object gets automatically created and passed in to all of them. 在我的范围规则中,我传递了一个名为“ScreenScope”的对象,虽然我不会在这里进入它,但它基本上意味着我在我的ScreenViewModel中请求会话对象,或者它作为成员的任何视图模型(包括他们自己的孩子)自动创建相同的ISession对象并将其传递给所有这些对象。 By using DI scoping I don't even have to think about it, I just declare the members with the [Inject] attribute and it happens: 通过使用DI范围我甚至不必考虑它,我只是使用[Inject]属性声明成员并且它发生:

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

These service classes all contains a RepositoryManager that has been injected, and since they're all in ScreenViewModel the ISession object will be the same, at least in my WPF build. 这些服务类都包含一个已注入的RepositoryManager,并且因为它们都在ScreenViewModel中,所以ISession对象将是相同的,至少在我的WPF构建中。 if I switch to my MVC build they're the same for all view models created for a given request, and if I switch to a console build it uses the same ISession for everything in the entire program. 如果我切换到我的MVC构建,它们对于为给定请求创建的所有视图模型都是相同的,如果我切换到控制台构建,它会对整个程序中的所有内容使用相同的ISession。

TL;DR: Use dependency injection and a scope your contexts to one-per-form. TL; DR:使用依赖注入和上下文的范围为每个表单一个。

In my earlier usage of MVVM within WPF I was utilising an open context per VM but I quickly ran into issues with thread safety of DBContexts once applications evolved to make better use of Async.在我之前在 WPF 中使用 MVVM 时,我在每个 VM 中使用一个开放的上下文,但是一旦应用程序发展到可以更好地利用异步,我很快就遇到了 DBContext 的线程安全问题。

Whilst there is a greater development overhead, I now utilise dependency injection to provide a DBContextFactory (not the DBContext itself).虽然开发开销更大,但我现在利用依赖注入来提供 DBContextFactory(而不是 DBContext 本身)。 I spin up a context in a using statement witihn the VM to fill observableCollections with plinq calls via EF.我在使用 VM 的 using 语句中启动上下文,以通过 EF 使用 plinq 调用填充 observableCollections。 Another performance benefit of this method is running queries with AsNoTracking().此方法的另一个性能优势是使用 AsNoTracking() 运行查询。 The annoying part is managing the reattachment of new or modified objects to the short lived context:烦人的部分是管理将新的或修改的对象重新附加到短暂的上下文中:

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