一、什么是事务
处理以数据为中心的应用时,另一个重要的话题是事务管理。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模式管理事务的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。








