c# - 在 WPF MVVM 应用程序中管理 DbContext

标签 c# wpf entity-framework mvvm architecture

几天来我一直在思考这个问题,但仍然无法决定哪种方法是正确的。
这个问题针对 WPF特别是因为与网络应用程序相反,许多在线帖子和文章推荐 contextview-model方法而不是 contextrequest .
我有一个 WPF MVVM使用 Entity-Framework DB first 的应用程序模型。
这是我的应用程序中使用的两个模型的示例(由 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创建和处理 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) 创建一个 repository对于每个 entity .看起来与上面的选项相同(也有有或没有 using 的困境),但是每个存储库只包含与其 entity 相关的方法.
Afaik 在我的 ViewModel 中使用与上面相同.

4) 创建 Unit-Of-Work将通过适当的类 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...
}

就数据并发性、更好的抽象和分层以及整体性能而言,以上哪种方法(如果有)是首选?
编辑 -this article. 中找到以下段落:

When working with Windows Presentation Foundation (WPF) or Windows Forms, use a context instance per form. This lets you use change-tracking functionality that context provides.

但是,它提出了我是否应该创建一个 DbContext 的问题我的对象 view-model还是有一个实用类更好,比如我的 DAL类并引用它。

最佳答案

这就是依赖注入(inject)框架旨在解决的问题。是的,这是添加到您的项目中的另一种技术,但是一旦您开始使用 DI,您就再也不会回头了。

这里真正的问题是,当您真的应该使用控制反转并在更高的位置做出决定时,您却试图在 View 模型中做出这个决定。 WPF/MVVM 应用程序将需要每个表单的上下文,以便仅在用户完成编辑后才提交更改,并为用户提供取消更改的机会。我知道您不会在 Web 应用程序中使用它,但设计良好的架构意味着您应该能够这样做,在这种情况下,您需要每个请求都有一个上下文。您可能想要编写一个控制台应用程序实用程序,用静态数据填充数据库,在这种情况下,您可能需要一个全局/单例上下文以提高性能和易用性。最后,您的单元测试还需要模拟上下文,可能是在每次测试的基础上。所有这四种情况都应该在你的注入(inject)框架中设置,你的 View 模型不应该知道或关心它们中的任何一个。

这是一个例子。我个人使用专为 .NET 设计的 Ninject。我也更喜欢 NHibernate,尽管 ORM 的选择在这里无关紧要。我有具有不同范围要求的 session 对象,这在初始化我的 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 等价物。我的存储库类管理内存中的数据库对象,包含对它们关联的 session 的引用:

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

    ... etc...
{

[Inject] 属性告诉 Ninject 使用我设置的范围规则自动填充此字段。到目前为止,这一切都发生在我的域类中,但它也扩展到我的 View 模型层。在我的范围规则中,我传入了一个名为“ScreenScope”的对象,虽然我不会在这里深入探讨,但它基本上意味着任何时候我在我的 ScreenViewModel 或它作为成员的任何 View 模型中请求 session 对象(包括他们自己的 child )相同的 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...
}

这些服务类都包含一个已注入(inject)的 RepositoryManager,并且由于它们都在 ScreenViewModel 中,所以 ISession 对象将是相同的,至少在我的 WPF 构建中是这样。如果我切换到我的 MVC 构建,它们对于为给定请求创建的所有 View 模型都是相同的,如果我切换到控制台构建,它对整个程序中的所有内容都使用相同的 ISession。

TL;DR:使用依赖注入(inject)并将上下文范围限定为一个表单。

关于c# - 在 WPF MVVM 应用程序中管理 DbContext,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26571274/

相关文章:

mysql - vb net、mysql 和 Entity Framework 6 sbyte 类型的问题

c# - 强制 Entity Framework 5 使用 datetime2 数据类型

c# - 当使用 C# 使用 SOAP Web 服务时,获取为 null,提供程序为 Ladon/Python

C# 列表到 ICollection

c# - 跨多个用户使用 'dynamic' 静态属性?

c# - WinForms C#中自定义对象类型的跨进程拖放

c# - 使用 WPF C# 中的多个控件组合创建自定义控件

c# - 组合框触发空选择

c# - 从 wpf datagrid 获取隐藏值

c# - 如何修改自动生成的 Entity Framework DbContext 属性?