繁体   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