我有一个使用asp.net mvc的web应用程序,我在基本控制器中创建我的EF上下文,并让它可用于整个请求,然后在处理基本控制器时处理上下文。我希望能够将上下文的连接登记到存储在会话中的现有事务中。
使用以下方式开始新交易:
ObjectContext context = new MyEntities(myConnectionString);
DbTransaction transaction = context.Connection.BeginTransaction();
然后将其存储在当前会话中:
HttpContext.Current.Session["EFTransaction"] = transaction;
然后在新请求中从会话中检索事务并登记上下文:
context.Connection.EnlistTransaction(
(???)HttpContext.Current.Session["EFTransaction"]);
唯一的问题是我不知道要投什么(???)。 BeginTransaction()
方法返回System.Data.Common.DbTransaction
对象,EnlistTransaction(System.Transactions.Transaction transaction)
需要Transaction对象。
我的目标是在请求之间基本上创建和修改相同的实体,然后在用户准备保存时将所有更改提交给db。
答案 0 :(得分:2)
交易存储在会话中?天啊......
停止!在会话中存储交易是非常错误的想法。回到您的分析并更改架构现在。
为什么呢?假设您的应用程序由多个用户使用(是的,它通常发生在ASP.NET应用程序中)。现在,第一个用户在长时间运行的事务中对数据进行修改。事务确实与数据库通信,并在记录上使用锁。因此,在第一次未提交更改后,事务锁定了一些记录。现在,来自尝试访问同一记录的其他用户/事务的每个查询都将等到修改事务完成(除非您允许读取与未使用事务几乎相同的未提交数据)。如果修改事务需要10分钟,则访问同一记录的所有其他用户都将超时。
这只是一个例子,为什么它是完全错误的想法。还有更多像死锁概率,SQL服务器性能问题,事务超时等等。
更糟糕的是,您的事务是分布式的,因此我怀疑它是否可行,因为当您决定提交时,来自事务中使用的所有上下文实例的连接必须处于活动状态,直到您提交它们时,它们不得被处置或重置(返回到连接池)。
这是以完全不同的方式完成的:
在会话中存储对象并在用户完成所有更改时将其推送到数据库 - SaveChanges本身使用事务,因此如果将更改保存在一起,它们将处于事务中,直到用户完成更改,没有理由保存它们。请注意,这并不意味着在会话中存储上下文!
长时间运行的事务确实存在,但它们是“自定义实现的”。这意味着数据在没有事务的情况下持久保存到数据库,并且您有单独的代码称为补偿,可以还原更改。在这种情况下,这是您不需要的。
答案 1 :(得分:1)
不确定这是否是最佳方法,在多个页面请求之间保持事务处于打开状态,并且在会话中存储会打开一个很长的,可能是未提交的事务,这将继续获取对表的锁定。
解决这个问题的最佳方法可能是启动一个事务,只需要准备好提交更改即可。在那里你可以像这样使用TransactionScope ......
using (var tscope = new TransactionScope()
{
..... all changes here ...
tscope.Complete()
}
您不必在范围内明确注册任何连接,在操作期间打开的任何连接都会自动注册,如果您有任何打开的连接,它们将被关闭并再次与事务重新打开。
如果您担心并发问题,可以在表中使用基于时间戳的列。
还让我补充说,当调用save时,EF会隐式使用事务,只有在需要多次保存时,或者默认事务级别(在Sql Server上为READ COMMITTED)不足时,才需要外部作用域。