溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Entity?Framework怎么使用Code?First模式管理事務(wù)

發(fā)布時(shí)間:2022-03-06 18:55:30 來源:億速云 閱讀:248 作者:iii 欄目:開發(fā)技術(shù)

今天小編給大家分享一下Entity Framework怎么使用Code First模式管理事務(wù)的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

一、什么是事務(wù)

處理以數(shù)據(jù)為中心的應(yīng)用時(shí),另一個(gè)重要的話題是事務(wù)管理。ADO.NET為事務(wù)管理提供了一個(gè)非常干凈和有效的API。因?yàn)镋F運(yùn)行在ADO.NET之上,所以EF可以使用ADO.NET的事務(wù)管理功能。

當(dāng)從數(shù)據(jù)庫角度談?wù)撌聞?wù)時(shí),它意味著一系列操作被當(dāng)作一個(gè)不可分割的操作。所有的操作要么全部成功,要么全部失敗。事務(wù)的概念是一個(gè)可靠的工作單元,事務(wù)中的所有數(shù)據(jù)庫操作應(yīng)該被看作是一個(gè)工作單元。

從應(yīng)用程序的角度來看,如果我們有多個(gè)數(shù)據(jù)庫操作被當(dāng)作一個(gè)工作單元,那么應(yīng)該將這些操作包裹在一個(gè)事務(wù)中。為了能夠使用事務(wù),應(yīng)用程序需要執(zhí)行下面的步驟:

1、開始事務(wù)。

2、執(zhí)行所有的查詢,執(zhí)行所有的數(shù)據(jù)庫操作,這些操作被視為一個(gè)工作單元。

3、如果所有的事務(wù)成功了,那么提交事務(wù)。

4、如果任何一個(gè)操作失敗,就回滾事務(wù)。

二、創(chuàng)建測(cè)試環(huán)境

1、提到事務(wù),最經(jīng)典的例子莫過于銀行轉(zhuǎn)賬了。我們這里也使用這個(gè)例子來理解一下和事務(wù)相關(guān)的概念。為了簡(jiǎn)單模擬銀行轉(zhuǎn)賬的情景,假設(shè)銀行為不同的賬戶使用了不同的表,對(duì)應(yīng)地,我們創(chuàng)建了OutputAccount和InputAccount兩個(gè)實(shí)體類,實(shí)體類定義如下:

OutputAccount實(shí)體類:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFTransactionApp.Model
{
    [Table("OutputAccounts")]
    public class OutputAccount
    {
        public int Id { get; set; }
        [StringLength(8)]
        public string Name { get; set; }
        public decimal Balance { get; set; }

    }
}

InputAccount實(shí)體類:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFTransactionApp.Model
{
    [Table("InputAccounts")]
    public class InputAccount
    {
        public int Id { get; set; }
        [StringLength(8)]
        public string Name { get; set; }
        public decimal Balance { get; set; }

    }
}

2、定義數(shù)據(jù)上下文類

using EFTransactionApp.Model;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFTransactionApp.EF
{
    public class EFDbContext:DbContext
    {
        public EFDbContext()
            : base("name=AppConnection")
        {

        }


        public DbSet<OutputAccount> OutputAccounts { get; set; }

        public DbSet<InputAccount> InputAccounts { get; set; }
    }
}

3、使用數(shù)據(jù)遷移生成數(shù)據(jù)庫,并填充種子數(shù)據(jù)

namespace EFTransactionApp.Migrations
{
    using EFTransactionApp.Model;
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;

    internal sealed class Configuration : DbMigrationsConfiguration<EFTransactionApp.EF.EFDbContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = false;
        }

        protected override void Seed(EFTransactionApp.EF.EFDbContext context)
        {
            //  This method will be called after migrating to the latest version.

            //  You can use the DbSet<T>.AddOrUpdate() helper extension method
            //  to avoid creating duplicate seed data.

            // 填充種子數(shù)據(jù)
            context.InputAccounts.AddOrUpdate(
                  new InputAccount()
                  {
                      Name = "李四",
                      Balance = 0M
                  }
                );

            context.OutputAccounts.AddOrUpdate(
                 new OutputAccount()
                 {
                     Name="張三",
                     Balance=10000M
                 }
                );
        }
    }
}

4、運(yùn)行程序

從應(yīng)用程序的角度看,無論何時(shí)用戶將錢從OutputAccount轉(zhuǎn)入InputAccount,這個(gè)操作應(yīng)該被視為一個(gè)工作單元,永遠(yuǎn)不應(yīng)該發(fā)生OutputAccount的金額扣除了,而InputAccount的金額沒有增加。接下來我們就看一下EF如何管理事務(wù)。

運(yùn)行程序前,先查看數(shù)據(jù)庫數(shù)據(jù):

Entity?Framework怎么使用Code?First模式管理事務(wù)

現(xiàn)在,我們嘗試使用EF的事務(wù)從OutputAccount的張三轉(zhuǎn)入1000給InputAccount的李四。

使用EF默認(rèn)的事務(wù)執(zhí)行

EF的默認(rèn)行為是:無論何時(shí)執(zhí)行任何涉及Create,Update或Delete的查詢,都會(huì)默認(rèn)創(chuàng)建事務(wù)。當(dāng)DbContext類上的SaveChanges()方法被調(diào)用時(shí),事務(wù)就會(huì)提交。

using EFTransactionApp.EF;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFTransactionApp
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var db = new EFDbContext())
            {
                int outputId = 1, inputId = 1;
                decimal transferAmount = 1000m;
                //1 檢索事務(wù)中涉及的賬戶
                var outputAccount = db.OutputAccounts.Find(outputId);
                var inputAccount = db.InputAccounts.Find(inputId);
                //2 從輸出賬戶上扣除1000
                outputAccount.Balance -= transferAmount;
                //3 從輸入賬戶上增加1000
                inputAccount.Balance += transferAmount;
                //4 提交事務(wù)
                db.SaveChanges();
            }

        }
    }
}

運(yùn)行程序后,會(huì)發(fā)現(xiàn)數(shù)據(jù)庫中數(shù)據(jù)發(fā)生了改變:

Entity?Framework怎么使用Code?First模式管理事務(wù)

可以看到,用戶李四的賬戶上面多了1000,用戶張三的賬戶上面少了1000。因此,這兩個(gè)操作有效地被包裹在了一個(gè)事務(wù)當(dāng)中,并作為一個(gè)工作單元執(zhí)行。如果任何一個(gè)操作失敗,數(shù)據(jù)就不會(huì)發(fā)生變化。

可能有人會(huì)疑惑:上面的程序執(zhí)行成功了,沒有看到事務(wù)的效果,能不能修改一下代碼讓上面的程序執(zhí)行失敗然后可以看到事務(wù)的效果呢?答案是肯定可以的,下面將上面的代碼進(jìn)行修改。

通過查看數(shù)據(jù)庫表結(jié)構(gòu)會(huì)發(fā)現(xiàn)Balance的數(shù)據(jù)類型是

Entity?Framework怎么使用Code?First模式管理事務(wù)

意味著Balance列的最大可輸入長度是16位(最大長度18位減去2位小數(shù)點(diǎn)),如果輸入的長度大于16位的話程序就會(huì)報(bào)錯(cuò),所以將上面的代碼進(jìn)行如下的修改:

using EFTransactionApp.EF;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFTransactionApp
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var db = new EFDbContext())
            {
                int outputId = 1, inputId = 1;
                decimal transferAmount = 1000m;
                //1 檢索事務(wù)中涉及的賬戶
                var outputAccount = db.OutputAccounts.Find(outputId);
                var inputAccount = db.InputAccounts.Find(inputId);
                //2 從輸出賬戶上扣除1000
                outputAccount.Balance -= transferAmount;
                //3 從輸入賬戶上增加1000 *3000000000000000倍
                inputAccount.Balance += transferAmount*3000000000000000;
                //4 提交事務(wù)
                db.SaveChanges();
            }

        }
    }
}

在次運(yùn)行程序,會(huì)發(fā)現(xiàn)程序報(bào)錯(cuò)了:

Entity?Framework怎么使用Code?First模式管理事務(wù)

這時(shí)在查看數(shù)據(jù)庫,發(fā)現(xiàn)用戶張三的余額還是9000沒有發(fā)生變化,說明事務(wù)起作用了。

5、使用TransactionScope處理事務(wù)

如果有一個(gè)場(chǎng)景具有多個(gè)DbContext對(duì)象,那么我們想將涉及多個(gè)DbContext對(duì)象的操作關(guān)聯(lián)為一個(gè)工作單元,這時(shí),我們需要在TransactionScope對(duì)象內(nèi)部包裹SaveChanges()方法的調(diào)用。為了描述這個(gè)場(chǎng)景,我們使用DbContext類的兩個(gè)不同實(shí)例來執(zhí)行扣款和收款,代碼如下:

int outputId = 1, inputId = 1;
decimal transferAmount = 1000m;
using (var ts = new TransactionScope(TransactionScopeOption.Required))
{
       var db1 = new EFDbContext();
       var db2 = new EFDbContext();
       //1 檢索事務(wù)中涉及的賬戶
       var outputAccount = db1.OutputAccounts.Find(outputId);
       var inputAccount = db2.InputAccounts.Find(inputId);
       //2 從輸出賬戶上扣除1000
       outputAccount.Balance -= transferAmount;
       //3 從輸入賬戶上增加1000
       inputAccount.Balance += transferAmount;
       db1.SaveChanges();
       db2.SaveChanges();
       ts.Complete();
}

在上面的代碼中,我們使用了兩個(gè)不同的DbContext實(shí)例來執(zhí)行扣款和收款操作。因此,默認(rèn)的EF行為不會(huì)工作。在調(diào)用各自的SaveChanges()方法時(shí),和上下文相關(guān)的各個(gè)事務(wù)不會(huì)提交。相反,因?yàn)樗鼈兌荚?nbsp;TransactionScope對(duì)象的內(nèi)部,所以,當(dāng)TransactionScope對(duì)象的Complete()方法調(diào)用時(shí),事務(wù)才會(huì)提交。如果任何一個(gè)操作失敗,就會(huì)發(fā)生異常,TransactionScope就不會(huì)調(diào)用Complete()方法,從而回滾更改。事務(wù)執(zhí)行失敗的案例也可以按照上面的方式進(jìn)行修改,使Balance列的長度超過最大長度,這里就不在演示了。

三、使用EF6管理事務(wù)

從EF6開始,EF在DbContext對(duì)象上提供了Database.BeginTransaction()方法,當(dāng)使用上下文類在事務(wù)中執(zhí)行原生SQL命令時(shí),這個(gè)方法特別有用。

接下來看一下如何使用這個(gè)新方法管理事務(wù)。這里我們使用原生SQL從OutputAccount賬戶中扣款,使用模型類給InputAccount收款,代碼如下:

int outputId = 1, inputId = 1; decimal transferAmount = 1000m;
using (var db = new EFDbContext())
{
                using (var trans = db.Database.BeginTransaction())
                {
                    try
                    {
                        var sql = "Update OutputAccounts set Balance=Balance-@amountToDebit where id=@outputId";
                        db.Database.ExecuteSqlCommand(sql,
                        new SqlParameter("@amountToDebit", transferAmount),
                        new SqlParameter("@outputId", outputId));
                        var inputAccount = db.InputAccounts.Find(inputId);
                        inputAccount.Balance += transferAmount;
                        db.SaveChanges();
                        trans.Commit();
                    }
                    catch (Exception ex)
                    {
                        trans.Rollback();
                    }
                }
}

 對(duì)上面的代碼稍作解釋:首先創(chuàng)建了一個(gè)DbContext類的實(shí)例,然后使用這個(gè)實(shí)例通過調(diào)用Database.BeginTransaction()方法開啟了一個(gè)事務(wù)。該方法給我們返回了一個(gè)DbContextTransaction對(duì)象的句柄,使用該句柄可以提交或者回滾事務(wù)。然后使用原生SQL從OutputAccount賬戶中扣款,使用模型類給InputAccount收款。調(diào)用SaveChanges()方法只會(huì)影響第二個(gè)操作(在事務(wù)提交之后影響),但不會(huì)提交事務(wù)。如果兩個(gè)操作都成功了,那么就調(diào)用DbContextTransaction對(duì)象的Commit()方法,否則,我們就處理異常并調(diào)用DbContextTransaction對(duì)象的Rollback()方法回滾事務(wù)。

四、使用已經(jīng)存在的事務(wù)

有時(shí),我們想在EF的DbContext類中使用一個(gè)已經(jīng)存在的事務(wù)。原因可能有這么幾個(gè):

1、一些操作可能在應(yīng)用的不同部分完成。

2、對(duì)老項(xiàng)目使用了EF,并且這個(gè)老項(xiàng)目使用了一個(gè)類庫,這個(gè)類庫給我們提供了事務(wù)或者數(shù)據(jù)庫鏈接的句柄。

對(duì)于這些場(chǎng)景,EF允許我們?cè)贒bContext類中使用一個(gè)和事務(wù)相關(guān)聯(lián)的已存在連接。接下來,寫一個(gè)簡(jiǎn)單的函數(shù)來模擬老項(xiàng)目的類庫提供句柄,該函數(shù)使用純粹的ADO.NET執(zhí)行扣款操作,函數(shù)定義如下:

static bool DebitOutputAccount(SqlConnection conn, SqlTransaction trans, int accountId, decimal amountToDebit)
{
            int affectedRows = 0;
            var command = conn.CreateCommand();
            command.Transaction = trans;
            command.CommandType = CommandType.Text;
            command.CommandText = "Update OutputAccounts set Balance=Balance-@amountToDebit where id=@accountId";
            command.Parameters.AddRange(new SqlParameter[]
                            {  new SqlParameter("@amountToDebit",amountToDebit),
                               new SqlParameter("@accountId",accountId)
                            });
            try
            {
                affectedRows = command.ExecuteNonQuery();
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return affectedRows == 1;
}

 這種情況,我們不能使用Database.BeginTransaction()方法,因?yàn)槲覀冃枰獙qlConnection對(duì)象和SqlTransaction對(duì)象傳給該函數(shù),并把該函數(shù)放到我們的事務(wù)里。這樣,我們就需要首先創(chuàng)建一個(gè)SqlConnection,然后開始SqlTransaction,代碼如下:

int outputId = 2, inputId = 1; decimal transferAmount = 1000m;
var connectionString = ConfigurationManager.ConnectionStrings["AppConnection"].ConnectionString;
using (var conn = new SqlConnection(connectionString))
{
                conn.Open();
                using (var trans = conn.BeginTransaction())
                {
                    try
                    {
                        var result = DebitOutputAccount(conn, trans, outputId, transferAmount);
                        if (!result)
                            throw new Exception("不能正??劭睿?quot;);
                        using (var db = new EFDbContext(conn, contextOwnsConnection: false))
                        {
                            db.Database.UseTransaction(trans);
                            var inputAccount = db.InputAccounts.Find(inputId);
                            inputAccount.Balance += transferAmount;
                            db.SaveChanges();
                        }
                        trans.Commit();
                    }
                    catch (Exception ex)
                    {
                        trans.Rollback();
                    }
                }
}

同時(shí),需要修改數(shù)據(jù)上下文類,數(shù)據(jù)庫上下文類代碼修改如下:

using EFTransactionApp.Model;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFTransactionApp.EF
{
    //contextOwnsConnection
    //false:表示上下文和數(shù)據(jù)庫連接沒有關(guān)系,上下文釋放了,數(shù)據(jù)庫連接還沒釋放;
    //true:上下文釋放了,數(shù)據(jù)庫連接也就釋放了。
    public class EFDbContext:DbContext
    {
        //public EFDbContext()
        //    : base("name=AppConnection")
        //{

        //}

        public EFDbContext(DbConnection conn, bool contextOwnsConnection)
            : base(conn, contextOwnsConnection)
        {

        }


        public DbSet<OutputAccount> OutputAccounts { get; set; }

        public DbSet<InputAccount> InputAccounts { get; set; }
    }
}

五、選擇合適的事務(wù)管理

我們已經(jīng)知道了好幾種使用EF出來事務(wù)的方法,下面一一對(duì)號(hào)入座:

1、如果只有一個(gè)DbContext類,那么應(yīng)該盡力使用EF的默認(rèn)事務(wù)管理。我們總應(yīng)該將所有的操作組成一個(gè)在相同的DbContext對(duì)象的作用域中執(zhí)行的工作單元,SaveChanges()方法會(huì)提交處理事務(wù)。

2、如果使用了多個(gè)DbContext對(duì)象,那么管理事務(wù)的最佳方法可能就是把調(diào)用放到TransactionScope對(duì)象的作用域中了。

3、如果要執(zhí)行原生的SQL命令,并想把這些操作和事務(wù)關(guān)聯(lián)起來,那么應(yīng)該使用EF提供的Database.BeginTransaction()方法。然而這種方法只支持EF6以后的版本,以前的版本不支持。

4、如果想為要求SqlTransaction的老項(xiàng)目使用EF,那么可以使用Database.UseTransaction()方法,在EF6中可用。

以上就是“Entity Framework怎么使用Code First模式管理事務(wù)”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識(shí),如果還想學(xué)習(xí)更多的知識(shí),請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI