Entity Framework使用Code First模式管理事务

2022-04-17 09:38:49

一、什么是事务

处理以数据为中心的应用时,另一个重要的话题是事务管理。ADO.NET为事务管理提供了一个非常干净和有效的API。因为EF运行在ADO.NET之上,所以EF可以使用ADO.NET的事务管理功能。

当从数据库角度谈论事务时,它意味着一系列操作被当作一个不可分割的操作。所有的操作要么全部成功,要么全部失败。事务的概念是一个可靠的工作单元,事务中的所有数据库操作应该被看作是一个工作单元。

从应用程序的角度来看,如果我们有多个数据库操作被当作一个工作单元,那么应该将这些操作包裹在一个事务中。为了能够使用事务,应用程序需要执行下面的步骤:

1、开始事务。

2、执行所有的查询,执行所有的数据库操作,这些操作被视为一个工作单元。

3、如果所有的事务成功了,那么提交事务。

4、如果任何一个操作失败,就回滚事务。

二、创建测试环境

1、提到事务,最经典的例子莫过于银行转账了。我们这里也使用这个例子来理解一下和事务相关的概念。为了简单模拟银行转账的情景,假设银行为不同的账户使用了不同的表,对应地,我们创建了OutputAccount和InputAccount两个实体类,实体类定义如下:

OutputAccount实体类:

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实体类:

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、定义数据上下文类

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、使用数据迁移生成数据库,并填充种子数据

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 methodPbZOybj            //  to avoid creating duplicate seed data.            // 填充种子数据            context.InputAccounts.AddOrUpdate(                  new InputAccount()                  {                      Name = "李四",                      Balance = 0M                  }                );            context.OutputAccounts.AddOrUpdate(                 new OutputAccount()                 {                     Name="张三",                     Balance=10000M                 }                );        }    }}

4、运行程序

从应用程序的角度看,无论何时用户将钱从OutputAccount转入InputAccount,这个操作应该被视为一个工作单元,永远不应该发生OutputAccount的金额扣除了,而InputAccount的金额没有增加。接下来我们就看一下EF如何管理事务。

运行程序前,先查看数据库数据:

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()方法,因为我们需要将SqlConnection对象和SqlTransaction对象传给该函数,并把该函数放到我们的事务里。这样,我们就需要首先创建一个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                 PbZOybj   {                        var result = DebitOutputAccount(conn, trans, outputId, transferAmount);                        if (!result)                            throw new Exception("不能正常扣款!");                        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();                    }                }}

同时,需要修改数据上下文类,数据库上下文类代码修改如下:

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:表示上下文和数据库连接没有关系,上下文释放了,数据库连接还没释放;    //true:上下文释放了,数据库连接也就释放了。    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; }    }}

五、选择合适的事务管理

我们已经知道了好几种使用EF出来事务的方法,下面一一对号入座:

1、如果只有一个DbContext类,那么应该尽力使用EF的默认事务管理。我们总应该将所有的操作组成一个在相同的DbContext对象的作用域中执行的工作单元,SaveChanges()方法会提交处理事务。

2、如果使用了多个DbContext对象,那么管理事务的最佳方法可能就是把调用放到TransactionScope对象的作用域中了。

3、如果要执行原生的SQL命令,并想把这些操作和事务关联起来,那么应该使用EF提供的Database.BeginTransaction()方法。然而这种方法只支持EF6以后的版本,以前的版本不支持。

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

示例代码下载地址:点此下载

到此这篇关于Entity Framework使用Code First模式管理事务的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。