事务简单用法
文章一:https://www.cnblogs.com/wujingtao/p/5407821.html
1EF事务
事务就是确保一次数据库操作,所有步骤都成功,如果哪一步出错了,整个操作都将回滚。
在EF使用事务有两种方案,一种是EF自带的.BeginTransaction()方法,另一种是使用TransactionScope类。
使用.BeginTransaction()
使用.BeginTransaction()实现事务
class Program { static void Main(string[] args) { using (var db = new DBModel()) { var tran = db.Database.BeginTransaction(); //开启事务 try { var student = db.students.FirstOrDefault(s => s.name == "萝莉"); db.students.Remove(student); //删除萝莉 db.SaveChanges(); tran.Commit(); //必须调用Commit(),不然数据不会保存 } catch (Exception ex) { tran.Rollback(); //出错就回滚 } } } }
使用TransactionScope类
使用之前记得引入System.Transactions.dll
使用TransactionScope
class Program { static void Main(string[] args) { using (var db = new DBModel()) { using (var tran = new TransactionScope()) //开启事务 { var student = db.students.FirstOrDefault(s => s.name == "萝莉"); db.students.Remove(student); //删除萝莉 db.SaveChanges(); tran.Complete(); //必须调用.Complete(),不然数据不会保存 } //出了using代码块如果还没调用Complete(),所有操作就会自动回滚 } } }
文章二:https://www.cnblogs.com/CreateMyself/p/4787856.html#!comments
事务复杂用法
1概念
在开始学习事务之前我们先了解两个概念:
- Database.BeginTransaction():它是在一个已存在的DbContext上下文中对于我们去启动和完成transactions的一种简单方式,它允许多个操作组合存在在相同的transaction中,所以要么提交要么全部作为一体回滚,同时它也允许我们更加容易的去显示指定transaction的隔离级别。
- Dtabase.UseTransaction():它允许DbContext上下文使用一个在EF实体框架之外启动的transaction。
2默认事务
当我们调用SaveChanges方法来执行增、删、改时其操作内部都用一个transaction包裹着(自动完成的)。如下图,当添加数据时:
- 对于上下文中的 ExecuteSqlCommand() 方法默认情况下也是用transaction包裹着命令(Command),其有重载我们可以显示指定执行事务还是不确定执行事务。
- 在此上两种情况下,事务的隔离级别是数据库提供者认为的默认设置的任何隔离级别,例如在SQL Server上默认是READ COMMITED(读提交)。
- EF对于任何查询都不会用transaction来进行包裹。
在EF 6.0版本以上,EF一直保持数据库连接打开,因为要启动一个transaction必须是在数据库连接打开的前提下,同时这也就意味着我们执行多个操作在一个transaction的唯一方式是要么使用 TransactionScope 要么使用 ObjectContext.Connection 属性并且启动调用Open()方法以及BeginTransaction()方法直接返回EntityConnection对象。如果你在底层数据库连接上启动了transaction,再调用API连接数据库可能会失败。
3同一上下文中使用transaction
Database.BeginTransaction有两种重载——一种是显示指定隔离级别,一种是无参数使用来自于底层数据库提供的默认隔离级别,两种都是返回一个DbContextTransaction对象,该对象提供了事务提交(Commint)以及回滚(RollBack)方法直接表现在底层数据库上的事务提交以及事务回滚上。
DbContextTransaction一旦被提交或者回滚就会被Disposed,所以我们使用它的简单的方式就是使用using(){}语法,当using构造块完成时会自动调用Dispose()方法。
根据上述我们现在通过两个步骤来对学生进行操作,并在同一transaction上提交。如下:
using (var ctx = new EntityDbContext()) { using (var ctxTransaction = ctx.Database.BeginTransaction()) { try { ctx.Database.Log = Console.WriteLine; ctx.Database.ExecuteSqlCommand("update student set name='xpy0929'"); var list = ctx.Set<Student>().Where(p => p.Name == "xpy0929").ToList(); list.ForEach(d => { d.Name = "xpy0928"; }); ctx.SaveChanges(); ctxTransaction.Commit(); } catch (Exception) { ctxTransaction.Rollback(); } } }
我们通过控制台输出SQL日志查看提交事务成功如下:
【注意】 要开始一个事务必须保持底层数据库连接是打开的,如果数据库不总是打开的我们可以通过 BeginTransaction() 方法将打开数据库连接,如果 DbContextTransaction 打开了数据库,当调用Disposed()方法时将会关闭数据库连接。
4注意事项
1一定要用using包裹,保证每次上下文会新建和释放,这个上下文不可与其它方法共用。
2当用EF上下文中的 Database.ExecuteSqlCommand 方法来对数据库进行如下操作时
using (var ctx = new EntityDbContext()) { var sqlCommand = String.Format("ALTER DATABASE {0} SET SINGLE_USER WITH ROLLBACK IMMEDIATE", "DBConnectionString"); ctx.Database.ExecuteSqlCommand(sqlCommand); }
此时将会报错如下:
上述已经讲过此方法会被Transaction包裹着,所以导致出错,但是此方法有重载,我们进行如下设置即可
ctx.Database.ExecuteSqlCommand(TransactionalBehavior.DoNotEnsureTransaction,sqlCommand);
5将一个已存在的事务添加到上下文中
有时候我们可能需要事务的作用域更加广一点,当然是在同一数据库上但是是在EF之外完全进行操作。基于此,此时我们必须手动打开底层的数据库连接来启动事务,同时通知EF使用我们手动打开的连接来使现有的事务连接在此连接上,这样就达到了在EF之外使用事务的目的。
为了实现上述在EF之外使用事务我们必须在DbContext上下文中的派生类的构造器中关闭自身的连接而使用我们传入的连接。
第一步
上下文中关闭EF连接使用底层连接。
代码如下:
public EntityDbContext(DbConnection con) : base(con, contextOwnsConnection: false) { }
第二步
启动Transcation(如果我们想避免默认设置我们可以手动设置隔离级别),通知EF一个已存在的Transaction已经在我们手动的设置的底层连接上启动。
using (var con = new SqlConnection("ConnectionString")) { using (var SqlTransaction = con.BeginTransaction()) { using (var ctx = new EntityDbContext(con)) {
} } }
第三步
因为此时是在EF实体框架外部执行事务,此时则需要用到上述所讲的 Database.UseTransaction 将我们的事务对象传递进去。
ctx.Database.UseTransaction(SqlTransaction);
此时我们将能通过SqlConnection实例来自由执行数据库操作或者说是在上下文中,执行的所有操作都是在一个Transaction上,而我们只负责提交和回滚事务并调用Dispose方法以及关闭数据库连接即可。
至此给出完整代码如下:
using (var con = new SqlConnection("ConnectionString")) {
con.Open(); using (var SqlTransaction = con.BeginTransaction()) { try { var sqlCommand = new SqlCommand(); sqlCommand.Connection = con; sqlCommand.Transaction = SqlTransaction; sqlCommand.CommandText = @"update student set name = 'xpy0929'"; sqlCommand.ExecuteNonQuery(); using (var ctx = new EntityDbContext(con)) { ctx.Database.UseTransaction(SqlTransaction); var list = ctx.Set<Student>().Where(d => d.Name == "xpy0929").ToList(); list.ForEach(d => { d.Name = "xpy0928"; }); ctx.SaveChanges(); } SqlTransaction.Commit(); } catch (Exception) { SqlTransaction.Rollback(); } } }
【注意】你可以设置 ctx.Database.UseTransaction(null); 为空来清除当前EF中的事务,如果你这样做了,那么此时EF既不会提交事务也不会回滚现有的事务,除非你清楚这是你想做的 ,否则请谨慎使用。
6 TransactionScope Transactions
在msdn上对 TransactionScope 类定义为是:类中的代码称为事务性代码。
我们将上述代码包含在如下代码中,则此作用域里的代码为事务性代码
using ( var scope = new TransactionScope(TransactionScopeOption.Required)) { }
【注意】此时SqlConnection和EF实体框架都使用 TransactionScope ,因此此时将被会一起提交。
在.NET 4.5.1中 TransactionScope 能够和异步方法一起使用通过TransactionScopeAsyncFlowOption的枚举来启动。
通过如下实现:
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) {}
接着就是将数据库连接的打开方法(Open)、查询方法(ExecuteNonQuery)、以及上下文中保存的方法(SaveChanges)都换为对应的异步方法(OpenAsync)、(ExecuteNonQueryAsync)以及(SaveChangesAsync)即可