我一直在用这种方式敲打我好几天仍然无法确定哪种方法正确
这个问题主要针对WPF
,因为与网络应用相反,许多帖子和文章在线推荐context
每view-model
方法而不是context
每request
}}。
我有一个使用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
相关的方法。
在我的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.中找到以下段落:
使用Windows Presentation Foundation(WPF)或Windows窗体时,请为每个窗体使用上下文实例。这使您可以使用上下文提供的更改跟踪功能。
然而,它提出了一个问题,即我是否应该在DbContext
中创建view-model
对象,或者更好地拥有一个实用程序类,例如我的DAL
类并引用它。
答案 0 :(得分:1)
这就是依赖注入框架旨在解决的问题。是的,它是另一项添加到您项目中的技术,但是一旦您开始使用DI,您就永远不会回头。
这里真正的问题是,当你真的应该采用控制反转并使决策更高时,你正试图在你的视图模型中做出这个决定。 WPF / MVVM应用程序将需要每个表单的上下文,以便仅在用户完成编辑后提交更改,并且还允许用户取消更改。我知道您并未在Web应用程序中使用它,但设计良好的架构意味着您应该能够,在这种情况下,您将需要每个请求的上下文。您可能希望编写一个控制台应用程序实用程序,该实用程序使用静态数据填充数据库,在这种情况下,您可能需要全局/单一上下文来提高性能和易用性。最后,您的单元测试还需要模拟上下文,可能是基于每个测试。所有这四种情况都应该在您的注射框架中进行设置,您的视图模型既不应该知道也不关心它们。
这是一个例子。我个人使用Ninject,它是专门为.NET设计的。我也更喜欢NHibernate,尽管ORM的选择与此无关。我有会话对象具有不同的作用域要求,并且这在Ninject模块中设置,初始化我的ORM类:
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使用我设置的范围规则自动填充此字段。到目前为止,这一切都发生在我的域类中,但它也扩展到我的视图模型层。在我的范围规则中,我传递了一个名为&#34; ScreenScope&#34;的对象,虽然我不想在这里进入它,但它基本上意味着我在我的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:使用依赖注入和上下文的范围为每个表单一次。