一、关于事务回滚数据模型和数据库不对应问题

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中事务操作实例



本文转载:CSDN博客