Entity Framework交易處理

by Vivid 18. 十一月 2015 13:31

.NET Magazine國際中文電子雜誌
者:許薰尹
稿:張智凱
文章編號:N151116502
出刊日期:2015/11/18
開發工具:Visual Studio 2013 Ultimate Update 4
版本:.NET Framework 4.5.xEntity Framework 6.1.3

交易管理的主要功能是將多個資料異動動作包裝成一個單位,以便能在交易中任一個動作執行發生錯誤時,藉由交易復原的機制,將資料回復到交易發生之前的狀態。Entity Framework 6版新增一個交易的處理功能,在這篇文章中,我們將介紹Entity Framework的交易處理機制。

Entity Framework預設就有提供交易處理機制,當你叫用DbContext類別的SaveChanges()方法時,就會自動起始一個內部交易,把你進行的新增、刪除、修改資料的異動寫到資料庫,以確保資料的完整性。

首先我們來看一個例子,這個例子使用Entity Framework Code First方式來建立資料庫,並且新增兩筆資料到資料庫之中:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace ConsoleApplication4 {
  class Program {
 
    static void Main( string[ ] args ) {
      OperaContext context = new OperaContext( );
 
      context.Operas.Add( new Opera( ) {
        Title = "Cosi Fan Tutte" ,
        Year = 1790 ,
        Composer = "Mozart"
      } );
 
      context.Operas.Add( new Opera( ) {
        Title = "Carmen" ,
        Year = 1875 ,
        Composer = "Bizet"
      } );
       context.SaveChanges( );
      Console.WriteLine( "完成!" );

       Console.ReadLine( );
    }
  }
 
  public class OperaContext : DbContext {
    public DbSet<Opera> Operas { get; set; }
  }
 
  public class Opera {
    [DisplayName( "編號" )]
    public int OperaID { get; set; }
    [Required( ErrorMessage = "歌劇名稱不可以為空白" )]
    [StringLength( 200 )]
    [Index( IsUnique = true )]
    [DisplayName( "歌劇名稱" )]
    public string Title { get; set; }
    [DisplayName( "年代" )]
    public int? Year { get; set; }
    [Required( ErrorMessage = "作者名稱不可以為空白" )]
    [DisplayName( "作者" )]
    public string Composer { get; set; }
  }
}

 

 

這個範例執行之後之後,將顯示「完成!」訊息,資料庫資料表內容如下圖所示,包含兩筆資料,請參考下圖所示:

clip_image002

圖 1

範例中Opera類別的Title 屬性上方,套用Index Attribute,並將IsUnique 屬性設定為「true」,表示未來資料庫對應到此屬性的欄位內容不可以重複,因此讓我們故意修改Main方法程式碼如下,將Title名稱設為相同的「Cosi Fan Tutte」:

class Program {

  static void Main( string[ ] args ) {
    OperaContext context = new OperaContext( );

    context.Operas.Add( new Opera( ) {
      Title = "Cosi Fan Tutte" ,
      Year = 1790 ,
      Composer = "Mozart"
    } );

    context.Operas.Add( new Opera( ) {
      Title = "Cosi Fan Tutte" ,
      Year = 1875 ,
      Composer = "Bizet"
    } );
    context.SaveChanges( );

    Console.WriteLine( "完成!" );
    Console.ReadLine( );
  }
}

執行時將會發生DbUpdateException例外錯誤,得到錯誤訊息如下:

Unhandled Exception: System.Data.Entity.Infrastructure.DbUpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.Entity.Core.UpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.SqlClient.SqlException: Cannot insert duplicate key row in object 'dbo.Operas' with unique index 'IX_Title'. The duplicate key value is (Cosi Fan Tutte).

The statement has been terminated.

此範例執行結果,請參考下圖所示:

clip_image004

圖 2

此時資料庫中資料表的內容如下,沒有任一筆資料被新增到資料庫,請參考下圖所示:

clip_image006

圖 3

由此可得知,預設當你叫用SaveChanges()方法時,Entity Framework預設便提供交易的管理動作。

使用ExecuteSqlCommand執行SQL命令

雖然Entity Framework預設就有提供交易處理機制,自動起始一個內部交易,將新增、刪除、修改資料的異動寫到資料庫,但若你呼叫SaveChanges()方法多次,Entity Framework會在叫用SaveChanges()方法時,起始一個新交易。

另外,Entity Framework的 Database類別提供ExecuteSqlCommand方法,可以讓你執行SQL命令,若我們在上述的範例之中,修改程式碼如下,叫用Database.ExecuteSqlCommand方法將第二筆資料的Title設定為「Carousel」:

public class Program {

  static void Main( string[ ] args ) {
    OperaContext context = new OperaContext( );

    context.Operas.Add( new Opera( ) {
      Title = "Cosi Fan Tutte" ,
      Year = 1790 ,
      Composer = "Mozart"
    } );
    context.Operas.Add( new Opera( ) {
      Title = "Carmen" ,
      Year = 1875 ,
      Composer = "Bizet"
    } );
    context.SaveChanges( );
    int r = context.Database.ExecuteSqlCommand(
                         @"UPDATE Operas SET Title = 'Carousel'" +
                             " WHERE OperaID=2"
                         );
    Console.WriteLine( "Result:" +r.ToString());
    Console.WriteLine( "完成!" );

    Console.ReadLine( );
  }
}

由於目前的兩筆資料Title值不重複,因此能夠順利將兩筆資料寫到資料庫,請參考下圖所示:

clip_image008

圖 4

資料庫目前的結果,不僅新增了兩筆資料,也將第二筆資料的Title屬性修改成「Carousel」,請參考下圖所示:

clip_image010

圖 5

我們再修改程式碼,新增兩筆Title不衝突的資料到資料表,然後再叫用Database類別ExecuteSqlCommand方法,修改第二筆資料,故意修改第二筆資料的Title成「Cosi Fan Tutte」,以便和第一筆資料的Title重複,如此將違反資料欄位的唯一條件約束(Unique Constraint):

public class Program {

  static void Main( string[ ] args ) {
    OperaContext context = new OperaContext( );

    context.Operas.Add( new Opera( ) {
      Title = "Cosi Fan Tutte" ,
      Year = 1790 ,
      Composer = "Mozart"
    } );
    context.Operas.Add( new Opera( ) {
      Title = "Carmen" ,
      Year = 1875 ,
      Composer = "Bizet"
    } );
    context.SaveChanges( );
    int r = context.Database.ExecuteSqlCommand(
                         @"UPDATE Operas SET Title = 'Cosi Fan Tutte'" +
                             " WHERE OperaID=2"
                         );
    Console.WriteLine( "Result:" +r.ToString());
    Console.WriteLine( "完成!" );

    Console.ReadLine( );
  }
}

 

這個程式的執行結果,在ExecuteSqlCommand那行,程式將發生例外錯誤, 例外錯誤為:

Unhandled Exception: System.Data.SqlClient.SqlException: Cannot insert duplicate key row in object 'dbo.Operas' with unique index 'IX_Title'. The duplicate key value is (Cosi Fan Tutte).

The statement has been terminated.

此範例執行結果,請參考下圖所示:

clip_image012

圖 6

而資料庫將會有兩筆資料,第二筆資料無法修改為「Cosi Fan Tutte」,請參考下圖所示:

clip_image014

圖 7

使用DbContextTransaction執行交易

Entity Framework 6的System.Data.Entity.Database類別新增兩個方法來進行交易處理:BeginTransaction()與UseTransaction()方法。BeginTransaction()用來起始一個新交易;UseTransaction()方法允許你使用一個從Entity Framework Database物件之外起始的交易。

假若我們希望上個例子中,新增兩筆資料的動作,與修改第二筆資料的動作能包裝在一個交易之中,成功的話,新增兩筆資料到資料庫,並修改第二筆資料的Title欄位,若失敗的話,沒有任一筆資料能新增到資料庫,也無法修改資料的Title值,此時可以利用DbContextTransaction類別來執行交易。DbContextTransaction類別是在Entity Framework 6版時新增,因此你要確認專案中的Entity Framework版本。

讓我們修改程式碼如下,使用BeginTransaction ()方法回傳DbContextTransaction物件,並在必要時建立實體資料庫連線:

public class Program {

  static void Main( string[ ] args ) {
    OperaContext context = new OperaContext( );
    using ( System.Data.Entity.DbContextTransaction ts = context.Database.BeginTransaction( ) ) {
      try {

        context.Operas.Add( new Opera( ) {
          Title = "Cosi Fan Tutte" ,
          Year = 1790 ,
          Composer = "Mozart"
        } );

        context.Operas.Add( new Opera( ) {
          Title = "Carmen" ,
          Year = 1875 ,
          Composer = "Bizet"
        } );


        context.SaveChanges( );

        int r = context.Database.ExecuteSqlCommand(
                             @"UPDATE Operas SET Title = 'Cosi Fan Tutte'" +
                                 " WHERE OperaID=2"
                                 );
        Console.WriteLine( "Result:" + r.ToString( ) );

        ts.Commit( );
        Console.WriteLine( "交易成功完成!" );
      } catch ( Exception ) {
        ts.Rollback( );
        Console.WriteLine( "交易失敗,復原交易!" );

      } finally {
        if ( context != null ) {
          context.Dispose( );
        }
      }
    }
    Console.ReadLine( );
  }
}

 

BeginTransaction ()方法有兩個多載方法,其中一個允許你額外設定交易隔離等級(IsolationLevel)。BeginTransaction ()方法在必要時建立實體資料庫連線,而在Dispose()方法被呼叫時,關閉資料庫連線。DbContextTransaction類別則提供Commit( )與Rollback( )方法執行交易的確認與取消的動作。

程式執行後,將顯示「交易失敗,復原交易!」訊息,請參考下圖所示:

clip_image016

圖 8

此時資料庫中資料表的內容如下,沒有任一筆資料會被寫到資料庫,請參考下圖所示:

clip_image018

圖 9

 

Tags:

.NET Magazine國際中文電子雜誌 | Entity Framework | 許薰尹Vivid Hsu

新增評論




  Country flag
biuquote
  • 評論
  • 線上預覽
Loading






NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List