默認情況當你執行SaveChanges()的時候(insert update delete)來操作數據庫時,Entity Framework會把這個操作包裝在一個事務里,當操作結束后,事務也結束了。
EF6中的 Database.ExecuteSqlCommand()也會啟用一個事務,事務的隔離級別是默認級別(Read Commited)。
雖然這種框架默認的事務處理機制對於大多數情況下已經夠用了,但是是EF6為我們提供了API,讓我們能夠更加靈活地對自己代碼中的事務進行控制。
一、在EF中使用事務
- using System;
- using System.Collections.Generic;
- using System.Data.Entity;
- using System.Data.SqlClient;
- using System.linq;
- using System.Transactions;
- namespace TransactionsExamples
- {
- class TransactionsExample
- {
- static void StartOwnTransactionWithinContext()
- {
- using (var context = new BloggingContext())
- {
- using (var dbContextTransaction = context.Database.BeginTransaction())
- {
- try
- {
- context.Database.ExecuteSqlCommand(
- @"UPDATE Blogs SET Rating = 5" +
- " WHERE Name LIKE '%Entity Framework%'"
- );
- var query = context.Posts.Where(p => p.Blog.Rating >= 5);
- foreach (var post in query)
- {
- post.Title += "[Cool Blog]";
- }
- context.SaveChanges();
- dbContextTransaction.Commit();
- }
- catch (Exception)
- {
- dbContextTransaction.Rollback();
- }
- }
- }
- }
- }
- }
上面通過一個using語句塊把要使用事務的操作起來,調用context.Database.BeginTransaction()啟動一個事務,最后分別通過dbContextTransaction.Commit();和dbContextTransaction.Rollback();來提交或回滾事務。
注意:開啟一個事務需要事務所有在的數據庫鏈接是打開的,因此當所在數據庫鏈接沒有打開時Database.BeginTransaction()就會打開一個數據庫鏈接。上面調用context.Database.BeginTransaction()是沒有參數的,你可以調用這個BeginTransaction()其它重載函數來控制事務的隔離級別,如果沒傳參數將會使用默認的隔離級別。
二、傳遞一個已存在的事務
EF中可以傳遞一個存在的事務給context:
- using System;
- using System.Collections.Generic;
- using System.Data.Entity;
- using System.Data.SqlClient;
- using System.Linq;
- sing System.Transactions;
- namespace TransactionsExamples
- {
- class TransactionsExample
- {
- static void UsingExternalTransaction()
- {
- using (var conn = new SqlConnection("..."))
- {
- conn.Open();
- using (var sqlTxn = conn.BeginTransaction(System.Data.IsolationLevel.Snapshot))
- {
- try
- {
- var sqlCommand = new SqlCommand();
- sqlCommand.Connection = conn;
- sqlCommand.Transaction = sqlTxn;
- sqlCommand.CommandText =
- @"UPDATE Blogs SET Rating = 5" +
- " WHERE Name LIKE '%Entity Framework%'";
- sqlCommand.ExecuteNonQuery();
- using (var context =
- new BloggingContext(conn, contextOwnsConnection: false))
- {
- context.Database.UseTransaction(sqlTxn);
- var query = context.Posts.Where(p => p.Blog.Rating >= 5);
- foreach (var post in query)
- {
- post.Title += "[Cool Blog]";
- }
- context.SaveChanges();
- }
- sqlTxn.Commit();
- }
- catch (Exception)
- {
- sqlTxn.Rollback();
- }
- }
- }
- }
- }
- }
三、TransactionScope事務
EF中讓多個數據庫操作作為一個整體的事務來處理除了上面使用事務傳遞來實現外,還可以利用TransactionScope對象來處理,如下:
- using System.Collections.Generic;
- using System.Data.Entity;
- using System.Data.SqlClient;
- using System.Linq;
- using System.Transactions;
- namespace TransactionsExamples
- {
- class TransactionsExample
- {
- static void UsingTransactionScope()
- {
- using (var scope = new TransactionScope(TransactionScopeOption.Required))
- {
- using (var conn = new SqlConnection("..."))
- {
- conn.Open();
- var sqlCommand = new SqlCommand();
- sqlCommand.Connection = conn;
- sqlCommand.CommandText =
- @"UPDATE Blogs SET Rating = 5" +
- " WHERE Name LIKE '%Entity Framework%'";
- sqlCommand.ExecuteNonQuery();
- using (var context =
- new BloggingContext(conn, contextOwnsConnection: false))
- {
- var query = context.Posts.Where(p => p.Blog.Rating > 5);
- foreach (var post in query)
- {
- post.Title += "[Cool Blog]";
- }
- context.SaveChanges();
- }
- }
- scope.Complete();
- }
- }
- }
- }
注意:上面提交事務是通過TransactionScope實例的Complete方法來提交的。
可以看到使用TransactionScope使我們代碼簡化了不少,但是要注意的是TransactionScope也有一些限制:
1、需要NET 4.5.1及以上
2、不能和Database.UseTransaction()方式結合起來使用
3、sql語句中不能有DLL操作
4、不能在雲場景中使用,除非你保證只有一個數據庫連接。(支場景中不支持分布式事務)