作为一个非常简陋的场景,我们来看看这两个操作:
UserManager.UpdateFirstName(int userId, string firstName)
{
User user = userRepository.GetById(userId);
user.FirstName = firstName;
userRepository.SaveChanges();
}
InventoryManager.InsertOrder(Order newOrder)
{
orderRepository.Add(newOrder);
orderRepository.SaveChanges();
}
我在网络项目中只使用了EF,并且在很大程度上依赖于网络的无状态特性。对于每个请求,我都会获得注入我的业务层外观对象(服务,管理器,无论你想要调用它们)的上下文的全新副本,所有业务经理共享同一个EF上下文实例。目前我正在开发一个WPF项目,我正在注入业务经理,然后将他们直接使用的存储库注入View模型。
假设用户处于复杂的屏幕上并且他的第一个按钮单击调用UpdateFirstName()方法。我们假设SaveChanges()因任何原因失败。第二次按下按钮将调用InsertOrder()方法。
在Web上,这不是问题,因为操作#2的上下文与操作#1使用的上下文无关(新的http请求)。但是,在桌面上,两个操作的上下文相同。问题出现在用户的名字已被修改并因此被上下文跟踪的事实中。即使原始的SaveChanges()没有采取(比如当时db不可用),调用SaveChanges()的第二个操作不仅会插入新订单,还会更新用户的名字。几乎每一个原因都是不可取的,因为用户很早就忘记了他们的第一个失败的行动。
这显然是一个愚蠢的例子,但我总是倾向于遇到这样的场景,我希望我可以从每个用户操作的新上下文开始,而不是长寿(对于生命周期)例如WPF窗口)context。
你们如何处理这些情况?
答案 0 :(得分:3)
来自直接点击数据库的临时桌面应用程序时的非常真实的查询。
似乎与ORM的理念一致的答案是拥有与屏幕具有相同生命周期的上下文。 实质上,数据上下文是一个工作单元实现。对于一个Web应用程序,它显然是请求,它运行良好。
对于桌面应用,您可以将上下文绑定到屏幕,或者可能与编辑操作相关联(在加载和按下“保存”之间)。只读操作可以具有一次性上下文(使用ID在必要时重新加载对象,例如在网格中按下“删除按钮”时)。 如果您想要了解其他用户的修改(在首次加载时缓存关系集合),请忘记跨越整个应用程序生命周期的上下文。 还要忘记在不同的窗口之间直接共享EF实体,因为这有效地使它成为应用程序范围内的长期上下文。
在我看来,由于这个原因,ORM倾向于在桌面应用程序上强制执行类似Web的设计。我害怕,没有真正的方法。 并不是说它本身就是一件坏事。如果要在多个实例之间共享数据库,通常不应该在桌面级别攻击数据库。在这种情况下,您将使用服务层,EF将在其元素中,您的问题将转移到“服务上下文”......
答案 1 :(得分:2)
我们有一个大型WPF应用程序,它调用WCF服务来执行CRUD操作,如'UpdateFirstName()'或'InsertOrder()'。每次调用服务都会创建一个新的ObjectContext,因此我们不必担心会出现不一致的ObjectContexts。
完成后立即丢弃ObjectContext。在运行中生成新的ObjectContext没有任何问题。创建一个新的ObjectContext有一个不明显的开销,你将为自己节省许多未来的bug和头痛。
假设每次在存储库上调用方法时都会创建一个新的ObjectContext,下面的代码片段仍会通过使用TransactionScope类为您提供事务支持。例如,
using (TransactionScope scope = new TransactionScope())
{
UserManager.UpdateFirstName(userid,firstName);
InventoryManager.InsertOrder(newOrder);
scope.Complete();
}
答案 2 :(得分:0)
一种完全不同的方法是直接将所有更改写入数据库(使用短期数据库上下文)。然后在其上添加一些草稿/版本。这样你仍然可以提供ok / undo / cancel功能。