【问题标题】:Managing DbContext in WPF MVVM application在 WPF MVVM 应用程序中管理 DbContext
【发布时间】:2014-10-26 08:44:33
【问题描述】:

我已经为此苦苦思索好几天了,但仍然无法确定哪种方法是正确的。
这个问题的目标是WPF,因为与网络应用程序相反,许多在线帖子和文章推荐contextview-model,而不是contextrequest
我有一个使用Entity-Framework DB first 模型的WPF MVVM 应用程序。
这是我的应用程序中使用的两个模型的示例(由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) 为每个entity 创建一个repository。看起来与上述选项相同(也有带或不带 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...
}

上述哪种方法(如果有)在数据并发性、更好的抽象和分层以及整体性能方面更受欢迎?
编辑 - 在 @987654321 中找到以下段落@:

使用 Windows Presentation Foundation (WPF) 或 Windows 窗体时,请为每个窗体使用一个上下文实例。这使您可以使用上下文提供的更改跟踪功能。

但是,它提出了一个问题,即我应该在我的view-model 中创建一个DbContext 对象还是拥有一个实用程序类(例如我的DAL 类并引用它)更好。

【问题讨论】:

  • EF 拥有出色的 UoW(上下文)+ 存储库(DbSet)。为什么要创建自己的?这些额外的层几乎没有帮助。它们倾向于导致以数据为中心的应用程序将业务逻辑拉入客户端,而不是以任务为中心的应用程序将用例封装在接近 EF 模型的服务中。它是抽象数据与抽象任务(抽象意味着隐藏持久性实现)。
  • @GertArnold 感谢您的回复。由于我特别询问WPF MVVM,我想知道是否应该使用长寿的DbContextcontextview-model)或者我应该限制我的context 的寿命,如选项1(使用using :))
  • 两种方法我们都试过了,各有优缺点。没有万能的解决方案,您只需要找出最适合您的解决方案。也就是说,我们决定采用第二种方法并尽可能缩短上下文的生命周期,因为这样可以避免当您有多个 View(想想选项卡或窗口)和多个 ViewModel 并因此同时打开多个上下文时出现问题。
  • 通常中间有某种业务逻辑层,看起来像你的 Dal,由 ViewModels 调用。每个业务逻辑操作都会创建一个新的上下文,并使用该上下文完成所有必须做的事情。

标签: c# wpf entity-framework mvvm architecture


【解决方案1】:

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

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

这是一个例子。我个人使用专为 .NET 设计的 Ninject。我也更喜欢 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 设置了范围,它是您的上下文类的 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:使用依赖注入并将上下文范围限定为一个表单。

【讨论】:

  • 我正在研究 UI 主题中的 DI,我想为每个事件/命令创建上下文(在 WPF 的情况下)。不幸的是我不知道如何实现这个:(我能看到的唯一解决方案是将注入作为方法参数传递,但这很糟糕
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-06
  • 2014-10-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多