为了论证,假设我有一个允许用户编辑订单详细信息的webform。用户可以执行以下功能:
产品和附件存储在单独的数据库表中,并带有订单的外键。
Entity Framework(4.0)用作ORM。
我希望允许用户对订单进行任何他们想要的更改,并且只有当他们点击“保存”时,我才想将更改提交到数据库。这不是文本框/复选框等问题,因为我可以依赖ViewState来获取所需信息。然而,网格对我来说是一个更大的问题,因为我无法找到一种简单明了的方法来持久保存用户所做的更改,而无需将更改提交给数据库。在Session / ViewState中存储Order对象树实际上并不是我想要的选项,因为对象可能变得非常大。
所以问题是 - 我怎样才能保留用户所做的更改,直到准备好“保存”。
快速注释 - 我搜索过SO试图找到一个解决方案,但是我找到的只是建议使用Session和/或ViewState - 由于我的对象树的潜在大小,我不想使用它们
答案 0 :(得分:4)
如果您可以控制数据库的模式以及利用订单数据的其他应用程序,则可以在orders表中添加标志或状态列,以区分临时订单和已完成订单。然后,您可以将中间更改存储到数据库中。还有其他好处;例如,发生浏览器崩溃的用户可以返回应用程序并能够恢复订单流程。
我认为坚持使用数据库存储数据是唯一可靠的方法来保存数据,甚至是临时数据。使用会话状态,控制状态,cookie,临时文件等可能会引入许多可能出错的事情,尤其是当您的应用程序驻留在Web场中时。
答案 1 :(得分:2)
如果使用Session不是您首选的解决方案,这可能是明智的,那么最好的解决方案是创建自己的临时数据库表(或者正如其他人提到的那样,在现有数据库表中添加临时标志)并保持不变那里的数据,在Session(或cookie)中存储单个标识符以供以后检索。
答案 2 :(得分:2)
首先,您可能希望将特定的状态管理实现隔离到它自己的类中,这样您就不必在整个系统中复制它。
其次,您可能需要考虑混合方法 - 在短时间内使用会话状态(或缓存)以避免不必要的访问数据库或其他外部存储。经过一定程度的不活动后,将缓存状态写入磁盘或数据库。最简单的方法是将对象序列化为文本(使用序列化或类似proto-buffers的库)。这有助于您避免创建冗余或重复的数据结构以相关地捕获正在进行的数据。如果您不需要查询此数据的内容 - 这是一种合理的方法。
另外,在数据库世界中,您描述的问题称为long running transaction。在您到达用户定义的提交点之前,您基本上希望避免对数据进行更改。您可以在数据库层中使用某些技术,例如假设视图和instead-of triggers来封装您实际上未提交更改的行为。数据位于数据库中(在实际表中),但仅对在其上运行的用户可见。这可能是您可能愿意进行的更复杂的实现,并且需要对持久层和数据模型进行侵入式更改 - 但允许应用程序忽略该问题。
答案 3 :(得分:1)
您是否考虑过将信息存储在JavaScript对象中,然后在用户点击保存后将该信息发送到您的服务器?
答案 4 :(得分:1)
使用域事件捕获用户操作,然后通过订单模型的快照重放这些操作(在用户开始更改订单之前,实际上是订单的当前状态)。
将每个更改存储为一系列事件,例如UserChangedShippingAddress,UserAlteredLineItem,UserDeletedLineItem,UserAddedLineItem。
这些事件可以在每次回发后保存,只需要指向相关订单的链接。然后,重建订单的当前状态就像在当前存储的订单对象上重放事件一样简单。
当用户点击“保存”时,您可以重播事件并将更新的订单模型保留到数据库中。
您正在使用数据库 - 不需要会话或视图状态,因此您可以以某些页面性能为代价显着减少页面权重和服务器内存负载(如果您选择在每个回发时重建模型)。
维护非常简单,因为您可以轻松实现域对象,因此可以轻松使用自动化测试来确保系统按预期运行(同时还记录您对其他开发人员的意图)。
由于您正在利用数据库,因此该解决方案可以跨多个Web服务器进行扩展。
使用此方法不需要对现有域模型进行任何更改,因此对现有代码的影响很小。最大的缺点是了解领域事件的概念以及它们如何被使用和滥用=)
这实际上与Freddy Rios描述的方法相同,稍微详细一点,关于如何以及使用=)
进行搜索的一些好的关键字http://jasondentler.com/blog/2009/11/simple-domain-events/和http://www.udidahan.com/2009/06/14/domain-events-salvation/是关于域事件的一些很好的背景阅读。您可能还想阅读事件源,因为这基本上就是您要做的事情(快照对象,记录事件,重放事件,快照对象)。
答案 5 :(得分:1)
如何将您的域对象(网格/购物车的内容)序列化为JSON并将其存储在隐藏变量中? Scottgu有一个关于如何将对象序列化为JSON的nice article。可跨服务器场进行扩展,并猜测它不会为页面添加太多负载。也许你可以编写自己的JSON序列化程序来做一个“紧凑的序列化”(你不需要产品名称,产品ID,SKU id等,可能只是“序列化”产品ID和数量)
答案 6 :(得分:1)
您是否考虑过使用用户个人资料? .Net自带开箱即用的SqlProfileProvider。这将允许您为每个用户获取其配置文件并将临时数据作为变量保存在配置文件中。不幸的是,我认为这确实需要您的“订单”可序列化,但我相信除了Session之外的所有选项都需要相同的。
这样做的好处是它可以在崩溃,会话,服务器停机等情况下持续存在,并且设置相当容易。 Here's a site that runs through an example.设置完成后,您可能还会发现它可用于存储其他用户信息,例如偏好设置,收藏夹,观看的项目等。
答案 7 :(得分:0)
您应该能够创建临时文件并将对象序列化为该文件,然后仅将临时文件名保存到视图状态。一旦他们成功将记录保存回数据库,您就可以删除临时文件。
答案 8 :(得分:0)
单个服务器:序列化到文件系统。这也允许您让用户稍后恢复。 多服务器:序列化它,但将序列化值存储在数据库中。
这是针对该特定用户的内容,因此当您将其持久保存到数据库时,您并不需要所有关系内容。
或者,如果数据集是v。大且更改量通常很小,则可以存储用户完成的更改历史记录。有了这个,您还可以显示更改历史记录+支持撤消。
答案 9 :(得分:0)
2种方法 - 创建一个复杂的AJAX应用程序,它将所有内容存储在客户端上,并且只将整个更改包提交给服务器。几年前我做过这次,取得了一定的成功。申请不是我想要维护的东西。您很难将客户端代码与服务器代码同步,并且传递添加/删除/更改的字段是噩梦。
第二种方法是将数据库中的更改存储在临时表或“挂起”模式中。优点是您的代码更易于维护。缺点是您必须有办法清理因会话超时,电源故障和其他崩溃而导致的放弃更改。我会采用这种方法进行任何新的开发。您可以为“待处理”和“已提交”更改创建单独的表,从而打开可添加的全新功能级别。如果?改变了什么?等
答案 10 :(得分:0)
无论你之前说过什么,我都会去观点。如果你只存储你需要的东西,比如{ id: XX, numberOfProducts: 3 }
,那么就丢弃用户此时未选择的每个项目;只要您没有存储整个对象树,viewstate大小就不会成为问题。
存储附件时,将它们放在临时存储位置,并在视图状态中引用文件名。您可以拥有一个计划任务,清除上一天保存的每个文件的临时文件夹或其他内容。
这基本上是用户在我们的后端添加平面布置图信息和附件时用于存储信息的方法。
答案 11 :(得分:0)
最终用户是内部客户还是外部客户?如果您的客户是内部用户,那么查看另一组技术可能是值得的。而不是webforms,考虑使用像Silverlight这样的平台并在那里实现丰富的GUI。
然后,您可以在applet中存储复杂的业务对象,通过离线存储在多个会话中提供持续的“进行中”编辑跟踪,并轻松地与提供最终订单的保存/处理的后端服务集成。同时保持通过网络访问(虽然关闭了大多数* nix客户端)。
替代方案包括Adobe Flex或AJAX,具体取决于资源和需求。
答案 12 :(得分:0)
你认为大到多大?如果你正在谈论会话状态(因此它不会回到实际用户,比如视图状态),那么状态通常是一个非常好的选择。除进程内状态提供程序之外的所有内容都使用序列化,但您可以影响 序列化的方式。例如,我倾向于创建一个本地模型,表示只是我关心的状态(加上任何id / rowversion信息)(而不是完整的域实体,这可能会产生额外的开销) )。
为了进一步减少序列化开销,我会考虑使用protobuf-net;这可以用作ISerializable
的实现,允许非常轻量级序列化对象(通常远小于BinaryFormatter
,XmlSerializer
等),这是在页面请求中重建便宜。
当页面最终保存时,我会从本地模型更新我的域实体并提交更改。
有关信息,要将protobuf-net属性对象与状态序列化器(通常为BinaryFormatter
)一起使用,您可以使用:
// a simple, sessions-state friendly light-weight UI model object
[ProtoContract]
public class MyType {
[ProtoMember(1)]
public int Id {get;set;}
[ProtoMember(2)]
public string Name {get;set;}
[ProtoMember(3)]
public double Value {get;set;}
// etc
void ISerializable.GetObjectData(
SerializationInfo info,StreamingContext context)
{
Serializer.Serialize(info, this);
}
public MyType() {} // default constructor
protected MyType(SerializationInfo info, StreamingContext context)
{
Serializer.Merge(info, this);
}
}