本文主要是介绍EntiryFramework中事务操作(三)事务回滚数据模型和数据库不对应问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
一、关于事务回滚数据模型和数据库不对应问题
1.在使用事务时,无论是使用DbContextTransaction,还是使用TransactionScope,如果在事务中出现异常而回滚,都有可能出现这种情况,数据库数据已经回滚,但是实体模型缓存没有回滚。出现数据的不一致行。
2.这种情况出现的原因:
1.EF中对于查询的实体对象在内存中有缓存,用于数据的状态跟踪,提升性能。
2.在程序中使用相同的数据上下文。
二、解决方案
解决方案1.在程序中不使用同一个数据库上下文
也就是说事务回滚后,放弃当前使用上下文。
这种方式需要注意,对于事务最好使用TransactionScope,因为在同一DbContextTransaction事务中有两个上线文实例会出现死锁。
代码实例:
Test1 _context = new Test1();
Test1 _context2 = new Test1();
using (TransactionScope tran = new TransactionScope())
{try{//1.修改省Area province = _context.Areas.FirstOrDefault(q => q.AreaLevel == 1);province.AreaName = province.AreaName + "1";_context.SaveChanges();Console.WriteLine(_context2.Areas.FirstOrDefault(q => q.AreaLevel == 1).AreaName);//2.修改市Area city = _context2.Areas.FirstOrDefault(q => q.AreaLevel == 2);city.AreaName = city.AreaName + "1";_context2.SaveChanges();//抛出异常throw new Exception("测试事务异常");tran.Complete();}catch (Exception ex){Console.WriteLine("执行出错:" + ex.Message);}
}
//此位置获取的数据和数据库对应
Test1 _context3 = new Test1();
Console.WriteLine(_context3.Areas.FirstOrDefault(q => q.AreaLevel == 1).AreaName);
对于目前版本6.0,在事务执行失败后,放弃当前上下文,是EF官方给的一个方案,不知道将来是否会有更好的解决方案。
具体查看:https://msdn.microsoft.com/en-us/library/dn456833(v=vs.113).aspx
解决方案2:如果在事务异常后还要使用当前上下文,并且程序中数据量不是很大或者不需要太考虑性能的情况下,可以手动遍历所有的对象,设置为未跟踪来处理。
//手动修改所有缓存模型对象的状态为未跟踪
foreach (var item in _context.ChangeTracker.Entries())
{item.State = System.Data.Entity.EntityState.Deleted;
}
实例代码:
Test1 _context = new Test1();
try
{using (TransactionScope tran = new TransactionScope()){Area province = _context.Areas.FirstOrDefault(q => q.AreaLevel == 1);province.AreaName = province.AreaName + "1";_context.SaveChanges();Console.WriteLine(_context.Areas.FirstOrDefault(q => q.AreaLevel == 1).AreaName);throw new Exception("测试");tran.Complete();}
}
catch (Exception)
{
}
//手动修改所有缓存模型对象的状态为未跟踪
foreach (var item in _context.ChangeTracker.Entries())
{item.State = System.Data.Entity.EntityState.Deleted;
}
Console.WriteLine(_context.Areas.AsNoTracking().FirstOrDefault(q => q.AreaLevel == 1).AreaName);
三、对于只有一个数据库上下文的解决方案实例
1.定义处理提交和回滚类,在回滚时手动清空跟踪状态
public class EFTransaction : IDisposable
{/// <summary>/// 当前事务对象/// </summary>private DbContextTransaction tran = null;public EFTransaction(DbContextTransaction tran){this.tran = tran;}/// <summary>/// 提交/// </summary>public void Commit(){tran.Commit();}/// <summary>/// 混滚操作/// </summary>public void Rollback(){Console.WriteLine("执行回滚操作");tran.Rollback();//执行其他处理foreach (var item in Program._Context.ChangeTracker.Entries()){item.State = System.Data.Entity.EntityState.Detached;}}public void Dispose(){Console.WriteLine("正在释放资源");tran.Dispose();}
}
2.在使用事务时,操作自定义类的对象
public static MenuModel _Context = new MenuModel();
static void TestSix()
{//使用事务扩展using (var tran = new EFTransaction(_Context.Database.BeginTransaction())){try{Menu.Menu first = _Context.Menus.First();Console.WriteLine(first.MenuName);first.MenuName = "abc";first.Model.ModelName = "123";_Context.SaveChanges();throw new Exception("测试异常");tran.Commit();}catch (Exception ex){tran.Rollback();}}Console.WriteLine(_Context.Menus.First().MenuName);//如果不清除跟踪状态,返回abc,和数据库不一致
}
四、在目前EF6.0的版本中,对于事务异常官方说明:
更新日期:2016年10月23日
一般来说,当连接失败时,回滚当前事务.。但是,如果在事务提交时连接被删除,则事务的结果状态未知.。看到这个博客张贴更多的细节。
目前EF不提供任何特殊的工具来处理这种情况。的sqlazureexecutionstrategy不会重试操作如果这样失败。
有几种方法来处理这个问题:
选项1 -什么都不做
在事务提交过程中连接失败的可能性很低,所以如果实际发生这种情况,您的应用程序可能会失败,这可能是可以接受的.。
选项2 -使用数据库重置状态
放弃当前的DbContext
创建一个新的DbContext和从数据库恢复应用程序的状态。
通知用户最近的操作可能没有成功地完成
选项3 -手动跟踪事务
将一个非跟踪表添加到用于跟踪事务状态的数据库中.。
在每个事务开始时将一行插入到表中.。
如果在提交过程中连接失败,请检查数据库中相应行的存在.。
如果该行存在,则继续正常运行,因为事务已成功提交.
如果行不存在,请使用执行策略重试当前操作.。
如果提交成功,请删除相应的行以避免表的增长.。
更多:
EntiryFramework中事务操作(二)
EntiryFramework中事务操作实例
这篇关于EntiryFramework中事务操作(三)事务回滚数据模型和数据库不对应问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!