天天看點

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中事務操作執行個體