EF6,Mysql,开启事务时数据库发生提交错误不能自动Rollback怎么办?

圣殿骑士18 2017-11-25 10:44:08
以下这么一段代码:
public void DeleteItem(long itemId)
{
using (var context = new AppDbContext())
using (var trans = context.Database.BeginTransaction(IsolationLevel.ReadCommitted))
{
//使用中的单件不允许删除
var detail = context.in_changedetail.FirstOrDefault(c => c.ItemId == itemId);
if (detail != null) throw new ShowErrorException("此单件已有出入库记录,不能删除!");

//删除
context.ba_item.Where(c => c.ItemId == itemId).Delete();
context.ba_itemcustom.Where(c => c.ItemId == itemId).Delete();

trans.Commit();
}
}

其中事务使用的是 using 包含了BeginTransaction,理论上来说,使用了using,在异常时会自动调用trans.Dispose(),而Dispose应该默认回滚。
但我发现并不是这样,在第一次保存失败后,因为没回滚,我在第二次点击保存时,就产生了嵌套事务,反馈了不一样的错误提示:System.InvalidOperationException: Nested transactions are not supported.
然后这个时候,事务才被回滚。所以第三次点击保存时,就能正常反馈出第一次的数据库错误了。

我网上查询了资料,找到这个话题:
https://stackoverflow.com/questions/22486489/entity-framework-6-transaction-rollback
其中,讲到了对于sqlserver 数据库是能自动回滚的

但对mysql确实无效。

采用土方法,就是每个用事务的地方,用try catch包裹起来:


但这样太啰嗦了,有没有更好的方法?
...全文
197 点赞 收藏 13
写回复
13 条回复
圣殿骑士18 2017年11月25日
引用 7 楼 xuzuning 的回复:
你在反编译的代码中没有发现 Rollback 方法,是正常的!谁也没有规定回滚方法一定要写作 Rollback 但 SQL 命令字 Rollback 是一定会有的,虽然他可能是拼装而成,或放在资源中的
没太理解你的意思,Rollback()是DbContextTransaction的一个已有方法,那么回滚应该就是调用它吧,否则用Rollback命令难道还用于其他用途?讲不通。
回复 点赞
圣殿骑士18 2017年11月25日
引用 6 楼 xuzuning 的回复:
显式的执行 Rollback 是必须的,因为 Rollback 是 SQL 指令,而不是什么 C# 类的方法(虽然同名方法使你产生了误解)
你是说,我只能手动调用trans.Rollback()吗?最多自己封装一下?
回复 点赞
xuzuning 2017年11月25日
你在反编译的代码中没有发现 Rollback 方法,是正常的!谁也没有规定回滚方法一定要写作 Rollback 但 SQL 命令字 Rollback 是一定会有的,虽然他可能是拼装而成,或放在资源中的
回复 点赞
xuzuning 2017年11月25日
显式的执行 Rollback 是必须的,因为 Rollback 是 SQL 指令,而不是什么 C# 类的方法(虽然同名方法使你产生了误解)
回复 点赞
圣殿骑士18 2017年11月25日
引用 2 楼 sp1234 的回复:
如果只是 dispose 方法中没有执行 rollback 语句,那么你可以自己封装一个 DbTransaction 类来替代它,例如
            using (var context = new AppDbContext())
            using (var trans = new MyTransaction( context.Database.BeginTransaction(IsolationLevel.ReadCommitted)))
            {
                //使用中的单件不允许删除
                var detail = context.in_changedetail.FirstOrDefault(c => c.ItemId == itemId);
                if (detail != null) throw new ShowErrorException("此单件已有出入库记录,不能删除!");
 
                //删除
                context.ba_item.Where(c => c.ItemId == itemId).Delete();
                context.ba_itemcustom.Where(c => c.ItemId == itemId).Delete();
 
                trans.Commit();
            }
自己封装是可以,我想还有没有更好的原生方法,是我不知道的。
回复 点赞
圣殿骑士18 2017年11月25日
我从反编译的代码上,也没看出有Rollback的调用,不知道有没有找对地方。
这个对象在System.Data.Entity 命名空间下,在EentityFramework.dll里,也跟不同的数据库Provider无关吧,没搞懂,不知道链接上的那个源码中的Rollback()调用是哪里去找。

回复 点赞
圣殿骑士18 2017年11月25日
引用 1 楼 sp1234 的回复:
你说的是 dispose 没有自动执行 rollback、非得你手动写代码来执行 rollback?我简直不敢相信还有这种事情,那么你直接把提供这个 dll 驱动程序的告上法庭算了。我相信他们不可能不知道在 dispose 方法中应该执行一下 rollback语句。

我认为你测试有误,自己的代码有其它逻辑 bug,没有搞明白出错的机制。


我的代码,我是看不出bug,代码也很简单。p哥能看出问题么?就文中哪个链接中,有位老兄也讲到说 EF不能保证其他数据库接口提供者会做自动回滚。不知真假。
回复 点赞
以专业开发人员为伍 2017年11月25日
如果只是 dispose 方法中没有执行 rollback 语句,那么你可以自己封装一个 DbTransaction 类来替代它,例如
            using (var context = new AppDbContext())
            using (var trans = new MyTransaction( context.Database.BeginTransaction(IsolationLevel.ReadCommitted)))
            {
                //使用中的单件不允许删除
                var detail = context.in_changedetail.FirstOrDefault(c => c.ItemId == itemId);
                if (detail != null) throw new ShowErrorException("此单件已有出入库记录,不能删除!");
 
                //删除
                context.ba_item.Where(c => c.ItemId == itemId).Delete();
                context.ba_itemcustom.Where(c => c.ItemId == itemId).Delete();
 
                trans.Commit();
            }
回复 点赞
以专业开发人员为伍 2017年11月25日
你说的是 dispose 没有自动执行 rollback、非得你手动写代码来执行 rollback?我简直不敢相信还有这种事情,那么你直接把提供这个 dll 驱动程序的告上法庭算了。我相信他们不可能不知道在 dispose 方法中应该执行一下 rollback语句。 我认为你测试有误,自己的代码有其它逻辑 bug,没有搞明白出错的机制。
回复 点赞
圣殿骑士18 2017年11月25日
引用 12 楼 xuzuning 的回复:
真是写代码写傻了!orm 也是转换成 SQL 指令去执行的 是的 在mysql提供的类库的某处,有rollback指令的代码,只是我没找到!
orm 也是转换成 SQL 指令去执行的,这个我当然知道了,这是小白问题好吧。我也不知道什么原因,让你们两个大佬觉得我是个小白呢,是我太低调了吗。
回复 点赞
xuzuning 2017年11月25日
真是写代码写傻了!orm 也是转换成 SQL 指令去执行的 是的 在mysql提供的类库的某处,有rollback指令的代码,只是我没找到!
回复 点赞
圣殿骑士18 2017年11月25日
引用 10 楼 xuzuning 的回复:
MySQL 的事务流程是
SET AUTOCOMMIT=0 # 关闭自动提交
START TRANSACTION | BEGIN [WORK] #开始一个事务
....... # 可回滚的 SQL 指令集合
COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE] #提交事务
ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE] #回滚
开始事务后,只要执行 ROLLBACK 指令,就是回滚了 所以并不一定需要你定义的什么 Rollback() 方法,其实 Rollback() 方法中也只是一些执行 ROLLBACK 指令 的代码而已 在其他方法中不去调用 Rollback() 方法,而是直接 执行 ROLLBACK 指令,并不是大逆不道的事情,反而可以减少调用的开销
额,还是不知道啥意思,不知道是不是我的理解还达不到你的层次。 所谓ROLLBACK指令,是指数据库层面的吧?我现在使用orm,怎么能在c#代码里,直达数据库层面呢,c#作为对象化编程,不都是对象和方法吗。我也没写存储过程, 没法直接用指令把。 你是为了说明这句话吗?
但 SQL 命令字 Rollback 是一定会有的,虽然他可能是拼装而成,或放在资源中的
是说,在mysql提供的类库的某处,有rollback指令的代码,只是我没找到?
回复 点赞
xuzuning 2017年11月25日
MySQL 的事务流程是
SET AUTOCOMMIT=0 # 关闭自动提交
START TRANSACTION | BEGIN [WORK] #开始一个事务
....... # 可回滚的 SQL 指令集合
COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE] #提交事务
ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE] #回滚
开始事务后,只要执行 ROLLBACK 指令,就是回滚了 所以并不一定需要你定义的什么 Rollback() 方法,其实 Rollback() 方法中也只是一些执行 ROLLBACK 指令 的代码而已 在其他方法中不去调用 Rollback() 方法,而是直接 执行 ROLLBACK 指令,并不是大逆不道的事情,反而可以减少调用的开销
回复 点赞
发动态
发帖子
C#
创建于2007-09-28

8.5w+

社区成员

64.0w+

社区内容

.NET技术 C#
社区公告
暂无公告