日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > C# >内容正文

C#

C# 从代码入门 Mysql 数据库事务

發布時間:2023/12/24 C# 62 coder
生活随笔 收集整理的這篇文章主要介紹了 C# 从代码入门 Mysql 数据库事务 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄
  • 生成數據庫數據
  • Mysql 數據庫事務基礎
    • 數據庫的并發一致性問題
    • 數據庫事務的隔離級別
  • BeginTransaction() 和 TransactionScope 的區別
    • BeginTransaction()
    • 可以不手動撤銷
    • TransactionScope
    • 總結
  • DML 是否可以使用事務
  • 順序多操作
  • 嵌套事務
  • 事務范圍
  • 封裝 DbContext
    • TransactionScope
    • BeginTransaction()

在業務開發中,使用數據庫事務是必不可少的。而開發中往往會使用各種 ORM 執行數據庫操作,簡化代碼復雜度,不過,由于各種 ORM 的封裝特性,開發者的使用方式也不一樣,開發者想要了解 ORM 對事務做了什么處理是比較難的。因此,本文介紹數據庫事務基礎、Ado.net 事務、如何封裝 DbContext ,讀者掌握以后,可以加深對 C# 使用事務的理解,使用各種 ORM 時也會更應手。

生成數據庫數據

為了演示各種事務操作,我們想要先創建 demo 數據,打開 filldb 官網,根據操作提示生成模擬數據。

filldb 地址: https://filldb.info/dummy/step1

FillDB 是一款免費工具,可快速生成大量 MySql 格式的自定義數據,用于測試軟件和使用隨機數據填充數據庫。

然后按照 authors、posts 的順序,點擊 Generate ,生成數據庫數據。

因為 posts 有 authors 的外鍵,因此生成數據的順序是 authors、posts。

最后點擊 Export database 導出 SQL 即可。

然后在數據庫中導入數據。

為了連接 Mysql 數據庫,這里使用 MySqlConnector 驅動,請在創建控制臺項目之后,通過 nuget 引入此包。

MySqlConnector 的主要部件和 API 如下:

ADO.NET 類型 說明 異步方法 同步方法
DbConnection 連接器 OpenAsync Open
DbConnection BeginTransactionAsync BeginTransaction
DbCommand 執行命令 ExecuteNonQueryAsync ExecuteNonQuery
DbCommand ExecuteReaderAsync ExecuteReader
DbCommand ExecuteScalarAsync ExecuteScalar
DbDataReader 讀取數據 NextResultAsync NextResult
DbDataReader ReadAsync Read
DbTransaction 數據庫事務 CommitAsync Commit
DbTransaction RollbackAsync Rollback

使用同步方法可能會對托管線程池產生不利影響,如果沒有正確調優,還會導致速度減慢或鎖定。

Mysql 連接字符串配置示例:

const string connectionString = "Server=localhost;Port=3306;User ID=mysqltest;Password=Password123;Database=mysqldb";

或使用 MySqlConnectionStringBuilder 構建連接字符串:

var connectionBuilder = new MySqlConnectionStringBuilder()
	{
		Server = "localhost",
		Port = 3306,
		UserID = "mysqltest",
		Password = "Password123",
		Database = "mysqldb"
};
var connectionString = connectionBuilder.ConnectionString;

詳細連接字符串配置可以在 https://mysqlconnector.net/connection-options/ 中找到。

為了讓 MysqlConnetor 可以記錄日志,需要手動配置日志程序。

完整的 nuget 包如下:

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
    <PackageReference Include="MySqlConnector" Version="2.3.1" />
    <PackageReference Include="MySqlConnector.Logging.Microsoft.Extensions.Logging" Version="2.1.0" />
  </ItemGroup>

配置連接字符串、配置日志、創建數據庫連接,完整代碼示例如下:

var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
var logger = loggerFactory.CreateLogger<Program>();
var dataSourceBuilder = new MySqlDataSourceBuilder(connectionString);
dataSourceBuilder.UseLoggerFactory(loggerFactory);
await using var dataSource = dataSourceBuilder.Build();

using var connection = dataSource.CreateConnection();

經過以上配置之后,我們擁有了模擬數據庫以及基礎代碼,下面我們來正式學習 MysqlConnetor 和數據庫事務相關的知識。

Mysql 數據庫事務基礎

百度百科:數據庫事務( transaction)是訪問并可能操作各種數據項的一個數據庫操作序列,這些操作要么全部執行,要么全部不執行,是一個不可分割的工作單位。事務由事務開始與事務結束之間執行的全部數據庫操作組成。

數據庫事務有四個特性:

  • 原子性:原子性是指包含事務的操作要么全部執行成功,要么全部失敗回滾。
  • 一致性:一致性指事務在執行前后狀態是一致的。
  • 隔離性:一個事務所進行的修改在最終提交之前,對其他事務是不可見的。
  • 持久性:數據一旦提交,其所作的修改將永久地保存到數據庫中。

相信大家對數據庫事務都不陌生,因此這里就不扯淡了,下面來講解不同數據庫事務的特征。

數據庫的并發一致性問題

雖然數據庫事務可以幫助我們執行數據庫操作、回滾操作,但是數據庫事務并發執行時,事務之間可能會相互干擾,比如臟讀、幻讀等現象,我們使用數據庫事務時,要根據嚴格程度和性能之間相互平衡選擇事務隔離級別。

當多個事務并發執行時,可能會出現以下問題:

臟讀

? 事務 A 更新了數據,但還沒有提交,這時事務 B 讀取到事務 A 更新后的數據,然后事務 A 回滾了,事務 B 讀取到的數據就成為臟數據了。

不可重復讀

? 事務 A 對數據進行多次讀取,事務 B 在事務 A 多次讀取的過程中執行了更新操作并提交了,導致事務 A 多次讀取到的數據并不一致。

不可重復讀,特征是相同的數據,在事務 A 的不同階段讀取的數據不一樣。

幻讀

? 事務 A 在讀取數據后,事務 B 向事務A讀取的數據中插入了幾條數據,事務 A 再次讀取數據時發現多了幾條數據,和之前讀取的數據不一致。

幻讀,前后數據量不一樣。

丟失修改

? 事務 A 和事務 B 都對同一個數據進行修改,事務 A 先修改,事務 B 隨后修改,事務 B 的修改覆蓋了事務 A 的修改。

不可重復度和幻讀看起來比較像,它們主要的區別是:在不可重復讀中,發現數據不一致主要是數據被更新了。在幻讀中,發現數據不一致主要是數據增多或者減少了。

數據庫事務的隔離級別

數據庫事務的隔離級別有以下四種,按隔離級別從低到高:

  • 未提交讀:一個事務在提交前,它的修改對其他事務也是可見的。
  • 提交讀:一個事務提交之后,它的修改才能被其他事務看到。
  • 可重復讀:在同一個事務中多次讀取到的數據是一致的。
  • 串行化:需要加鎖實現,會強制事務串行執行。

Ado.net 中使用 System.Data.IsolationLevel 枚舉表示以上幾種數據庫事務隔離級別:

	public enum IsolationLevel
	{
        // 未指定
		Unspecified = -1,
        // 不能覆蓋來自更高度隔離的事務的掛起的更改。
		Chaos = 16,
        // 未提交讀,臟讀是可能的,這意味著不會發出共享鎖,也不會使用獨占鎖。
		ReadUncommitted = 256,
        // 提交讀,在讀取數據時持有共享鎖,以避免臟讀,但是數據可以在事務結束之前更改,從而導致不可重復讀取或幻像數據。
		ReadCommitted = 4096,
        // 可重復讀,鎖被放置在查詢中使用的所有數據上,防止其他用戶更新數據。防止不可重復讀取,但仍然可以使用幻像行。
		RepeatableRead = 65536,
        // 串行化,將在 DataSet 上放置一個范圍鎖,以防止其他用戶在事務完成之前更新數據集或將行插入數據集。
		Serializable = 1048576,
        // 通過存儲一個應用程序可以讀取而另一個應用程序正在修改相同數據的數據版本來減少阻塞。
        // 指示即使重新查詢,也無法從一個事務中看到在其他事務中所做的更改。
		Snapshot = 16777216
	}

數據庫的隔離級別分別可以解決數據庫的臟讀、不可重復讀、幻讀等問題。

隔離級別 臟讀 不可重復讀 幻讀
未提交讀 允許 允許 允許
提交讀 不允許 允許 允許
可重復讀 不允許 不允許 允許
串行化 不允許 不允許 不允許

其實也不必糾結這些問題,可以按照讀寫鎖的情況來理解。

編程中由于多個線程并發操作兩個字典:

Dictionary<string, string> a;
Dictionary<string, string> b;

第一個問題時,并發操作一個字典時,會出現線程并發異常。

所以,我們想要使用并發字典:

	ConcurrentDictionary<string, string> a;
	ConcurrentDictionary<string, string> b;

可是,當 T1 線程修改 a 完成,接著修改 b 時,線程 T2 把字典 a 修改了。這就導致了數據不一致。

使用讀寫鎖優化,將 a、b 兩個數據包在一起:

	ConcurrentDictionary<string, string> a;
	ConcurrentDictionary<string, string> b;

	private static ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
	// 讀
	private void Read()
	{
		try
		{
			_lock.EnterReadLock(); 
			// 讀
		}
		catch { }
		finally
		{
			_lock.ExitReadLock();            // 釋放讀取鎖
		}
	}

	// 寫
	public void Write(int key, int value)
	{
		try
		{
			_lock.EnterUpgradeableReadLock();
			_lock.EnterWriteLock();
			// 寫
			_lock.ExitWriteLock();
		}
		catch { }
		finally
		{
			_lock.ExitUpgradeableReadLock();
		}
	}

讀寫鎖的原理很簡單,讀和寫是兩個沖突的操作。當沒有線程 時,多個線程可以并發 ,此時不會有任何問題。當有一個線程 時,既不允許有其它線程同時在 ,也不允許其它線程同時在 。也就是說, 是可以并發的,但是寫是獨占的。

串行化

當然對于數據庫事務就復雜了很多。如果要按照讀寫鎖的形式去做,那么其隔離級別相當于 串行化,整個表都被鎖住,不允許事務并發執行,此時不會有 臟讀不可重復讀幻讀 這些情況。

可是,這樣對于數據庫來說壓力是很大的,會嚴重拖垮數據庫的性能,以及嚴重降低了業務程序的并發量。

當事務 A 只需要修改 id=1,2,3 的數據時,使用 串行化 級別,會鎖住整個表。這樣似乎有點太浪費了。

可重復讀

那么,我們只需要鎖住事務 A 正在修改的那幾行記錄不就行了嗎?那么我們把數據庫事務下降一個級別,使用 可重復讀

使用 可重復讀 事務級別,其被鎖住的數據,依然保持安全,也就是不會被其它事務所修改。所以,不會出現 臟讀不可重復讀。但是因為不是鎖住整個表,因此其它事務是可以插入數據的,這就導致了會出現 幻讀。當然,可重復讀 出現的問題,一般來說只需要保證事務中只處理自己想要的數據即可。

可重復讀 導致的 幻讀 問題,比如 A 事務在 筆記本 分類下給聯想筆記本型號都打 9 折優惠,可是此時 B 事務從 筆記本 分類下,增加了幾個理想筆記本型號。結果,事務 A 最后一查詢,把 B 事務插入的數據查詢出來了。那么事務 A 查詢的數據就包含了打折和未打折的數據了。

InnoDB 使用 MVCC 來實現高并發性,并實現了所有 4 個SQL標準隔離級別。InnoDB 默認為 REPEATABLE READ (可重復讀)隔離級別,并且通過間隙鎖(next-key locking)策略來防止在這個隔離級別上的幻讀。InnoDB 不只鎖定在查詢中涉及的行,還會對索引結構中的間隙進行鎖定,以防止幻行被插入。

提交讀

使用示例:

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
UPDATE  pet SET NAME = 'A';
SELECT SLEEP(5);
SELECT * from pet;
COMMIT;

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
UPDATE  pet SET NAME = 'B';
SELECT SLEEP(5);
SELECT * from pet;
COMMIT;

A 事務和 B 事務運行時,大家都對 name 做了修改,但是事務只能看到自己做出的修改,也就是說,B 事務未提交之前,A、B 都修改了數據,但是是隔離的。

A 事務修改了 name = A ,B 事務修改了 name = B ,未提交之前,A、B 事務讀到的分別是 A、B,這沒問題,不會干擾。

但是如果 A 先提交了事務,那么數據庫的 name 值就為 A,此時 B 事務還沒有提交,B 查詢到的 name = A,這就是不可重復讀。

提交讀 只能保證事務未提交前的數據隔離。當另一個事務提交后,會導致當前事務看到的數據前后不一樣。

未提交讀

這就離譜了。啥也不能保證。

對于數據庫事務的理解,大家倒序建議就比較容易理解了。

BeginTransaction() 和 TransactionScope 的區別

在 C# Ado.net 中,主要有兩種事務使用方式:

// 方式 1:
using var tran = await connection.BeginTransactionAsync();

// 方式 2:
using (TransactionScope transactionScope = new TransactionScope())
{

}

BeginTransaction() 由 IDbConnection 連接對象開啟,只能作用于當前 IDbConnection 。通過調用數據庫連接對象的 BeginTransaction() 方法,顯式地啟動了一個數據庫事務,因此與同步方法異步方法不沖突。

TransactionScope 內部封裝了一些 API,在TransactionScope設置的范圍內,不需要顯式地調用 Commit()Rollback() 方法,可以跨 IDbConnection 使用,在異步方法下使用需要做額外配置。

主要區別在于 BeginTransaction() 是顯式地管理事務,而 TransactionScope 則是在編程模型上提供了更為方便的自動事務管理機制。

在 System.Transactions 命名空間中存在很多與事務相關的代碼封裝。讀者可以自行了解:

https://learn.microsoft.com/en-us/dotnet/api/system.transactions?view=net-8.0

下面來詳細說明兩種事務開啟方式的使用區別。

BeginTransaction()

先說 BeginTransaction() ,其返回的是 DbTransaction 類型。

BeginTransaction() 開啟事務比較簡單,不過需要手動給 IDbCommand 設置事務屬性。

			await connection.OpenAsync();
           // 先開啟事務,再創建命令
			using var tran = await connection.BeginTransactionAsync();
			using var command = new MySqlCommand()
			{
				Connection = connection,
                // 注意這里
				Transaction = tran
			};

			try
			{
				command.CommandText = "... ...";
				await command.ExecuteNonQueryAsync();

				if(...)
				{
					await tran.CommitAsync();
				}else
				{
					await tran.RollbackAsync();
				}
			}
			catch (Exception ex)
			{
				await tran.RollbackAsync();
                logger.LogError(ex, "Tran error");
			}

BeginTransaction() 定義如下:

ValueTask<MySqlTransaction> BeginTransactionAsync(IsolationLevel isolationLevel, 
                                                  CancellationToken cancellationToken = default)

DbTransaction 還可以設置保存點。

			using var tran = await connection.BeginTransactionAsync();
			try
			{
				command.CommandText = "... ...";
				await command.ExecuteNonQueryAsync();

				// 保存點
				await tran.SaveAsync("stepa");

				// 釋放保存點、回滾到該保存點
				if(...)
				{
					await tran.ReleaseAsync("stepa");
				}
			}

BeginTransaction() 的使用比較簡單,也不太容易出錯。

可以不手動撤銷

很多時候我們會在 catch{} 回滾事務,如下代碼所示。

			try
			{
                ... ...
				await tran.CommitAsync();
			}
			catch (Exception ex)
			{
				logger.LogError(ex, "Tran error");
				await tran.RollbackAsync();
			}

實際上是當一個事務在 IDbConnection 中或者在此 IDbCommand 中沒有主動提交時,當對象生命周期結束或主動斷開連接時、被回收到連接池時,事務會自動回滾。只要沒有主動提交,則之前的操作皆無效。

比如,我們執行下面的 SQL 時,posts 表會被插入一條新的數據,id 為 101。

-- 開啟事務
BEGIN; -- 或者使用 START TRANSACTION;
INSERT INTO demo.posts (id, author_id, title, description, content, date)
VALUES (101, 1, '測試', '測試', '測試', '2023-12-08');
COMMIT ;

而執行以下代碼時,因為沒有調用 CommitAsync() 方法提交事務,因此程序結束后,插入數據庫的數據并不會起效。

			using var connection = dataSource.CreateConnection();
			await connection.OpenAsync();
			using var tran = await connection.BeginTransactionAsync();
			using var command = new MySqlCommand()
			{
				Connection = connection,
				Transaction = tran
			};

			try
			{
				command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (102, 1, '測試', '測試', '測試', '2023-12-08');
				""";
				await command.ExecuteNonQueryAsync();
                // await tran.CommitAsync();
			}
			catch (Exception ex)
			{
				logger.LogError(ex, "Tran error");
			}

TransactionScope

如以下代碼所示,雖然代碼執行不會報錯,但是其不受事務所控制,也就是說,雖然沒有提交,但是數據庫實實在在的插入了一條新的數據。

這是因為事務完全沒有起效,因為只有在 TransactionScope 中打開的數據庫連接,才會起效

			using var connection = dataSource.CreateConnection();
			await connection.OpenAsync();

			using (TransactionScope transactionScope = new TransactionScope())
			{
				var command = connection.CreateCommand();
				try
				{
					command.CommandText = 
                        """
                        INSERT INTO demo.posts (id, author_id, title, description, content, date) 
                        VALUES (103, 1, '測試', '測試', '測試', '2023-12-08');
                        """;
					await command.ExecuteNonQueryAsync();
					//transactionScope.Complete();
				}
				catch (Exception ex)
				{
					logger.LogError(ex, "Tran error");
				}
			}

修正之后:

			using (TransactionScope transactionScope = new TransactionScope())
			{
				using var connection = dataSource.CreateConnection();
				await connection.OpenAsync();

				var command = connection.CreateCommand();
				try
				{
					command.CommandText = 
                        """
                        INSERT INTO demo.posts (id, author_id, title, description, content, date) 
                        VALUES (104, 1, '測試', '測試', '測試', '2023-12-08');
                        """;
					await command.ExecuteNonQueryAsync();
					//transactionScope.Complete();
				}
				catch (Exception ex)
				{
					logger.LogError(ex, "Tran error");
				}
			}

但是,上面的代碼還是會報錯。這是因為 TransactionScope 默認不支持異步方法,而該代碼使用了異步,導致釋放時沒有使用相同的線程。

System.InvalidOperationException:“A TransactionScope must be disposed on the same thread that it was created.”

當然,TransactionScope 是支持異步的,我們只需要啟用配置即可。

			using (TransactionScope transactionScope = 
			new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
			{
				using var connection = dataSource.CreateConnection();
				await connection.OpenAsync();

				var command = connection.CreateCommand();
				try
				{
					command.CommandText = 
                        """
                        INSERT INTO demo.posts (id, author_id, title, description, content, date) 
                        VALUES (104, 1, '測試', '測試', '測試', '2023-12-08');
                        """;
					await command.ExecuteNonQueryAsync();
					//transactionScope.Complete();
				}
				catch (Exception ex)
				{
					logger.LogError(ex, "Tran error");
				}
			}

如下代碼所示,當執行代碼之后,因為我們沒有主動提交事務,因此,數據庫中不會真的插入數據。

			using (TransactionScope transactionScope = 
                   // 使其支持異步
                   new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
			{
				using var connection = dataSource.CreateConnection();
				await connection.OpenAsync();

				var command = connection.CreateCommand();

				command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (105, 1, '測試', '測試', '測試', '2023-12-08');
				""";
                    
				await command.ExecuteNonQueryAsync();
				//transactionScope.Complete();
			}

有了經驗之后,我們發現,如果我們不調用 Complete() 方法,那么數據庫中不會真的插入數據。

可是問題來了,因為是在 TransactionScope 中創建 IDbConnection 并打開連接,也就是說 TransactionScope 作用域范圍大于 IDbConnection ,那么 IDbConnection 釋放之后,再提交 TransactionScope ,是否可以?

			using (TransactionScope transactionScope = new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
			{
				using (var connection = dataSource.CreateConnection())
				{
					await connection.OpenAsync();
					var command = connection.CreateCommand();
					command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (105, 1, '測試', '測試', '測試', '2023-12-08');
				""";
					await command.ExecuteNonQueryAsync();
				}

				transactionScope.Complete();
			}

答案是一切正常。

簡化代碼如下所示:

			using (TransactionScope transactionScope = ...)
			{
				using (var connection = dataSource.CreateConnection())
				{
					await connection.OpenAsync();
					await command.ExecuteNonQueryAsync();
				}

				transactionScope.Complete();
			}

雖然, IDbConnection 在 using 中,transactionScope.Complete() 在 using 之外,但是事務依然可以起效。如果調用 .Complete(),則事務提交。如果不調用 .Complete() 則事務不會提交。

回到本小節第一個代碼示例中,事務不起效的問題。我們已經知道了是因為 IDbConnection 沒有在 TransactionScope 內創建,所以導致事務不能作用。

但是,對于 ASP.NET Core 程序、Context 形式的 ORM、倉儲形式的 ORM 等,由于其封裝在上下文內,不太可能在開發者使用 TransactionScope 時,再手動打開 IDbConnection.Open() 。不過這些 ORM 框架大多數都做了封裝,而本文末尾也介紹了幾種封裝方式。

總結

通過 BeginTransaction() 創建的事務,不會因為異步等出現問題,因為其是明確在一個 IDbCommand 、IDbConnection 中起效。

			using var tran = await connection.BeginTransactionAsync();
			using var command = new MySqlCommand()
			{
				Connection = connection,
                // 注意這里
				Transaction = tran
			};

所以說,通過 .BeginTransactionAsync() 使用事務,是最簡單、最不容易出錯的,而且其明確在哪個 IDbCommand 中使用事情,出現問題時,排除起來也相對簡單。

而對于 TransactionScope 來說,筆者花費了比較多的篇幅去實驗和解釋,TransactionScope 是使用事務作用域實現隱式事務的,使用起來有一定難度,也容易出錯。

DML 是否可以使用事務

開始的時候,筆者并沒有想到這個事情,在跟同事偶然吹水時,提到了這個事情。

Mysql 的事務對刪除表、創建表這些 DML 命令,其事務是無效的,起效的是表數據相關的操作,即 insert、update、delete 語句。

如下 SQL 所示,雖然回滾了事務,但是最后還是創建了視圖。

-- 開啟事務
use  demo;
BEGIN;
create view v_posts AS  SELECT * FROM posts;
ROLLBACK;
-- COMMIT ;

順序多操作

先從 TransactionScope 說起,情況如下代碼所示:

TransactionScope 中包含、創建了兩個 IDbConnection ,并且兩個 IDbConnection 都插入了數據。

也就是說使用 TransactionScope 同時管理多個 IDbConnection 。

			using (TransactionScope transactionScope = new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
			{
				using (var connection = dataSource.CreateConnection())
				{
					await connection.OpenAsync();
					var command = connection.CreateCommand();
					command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (108, 1, '測試', '測試', '測試', '2023-12-08');
				""";
					await command.ExecuteNonQueryAsync();
				}

				using (var connection = dataSource.CreateConnection())
				{
					await connection.OpenAsync();
					var command = connection.CreateCommand();
					command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (109, 1, '測試', '測試', '測試', '2023-12-08');
				""";
					await command.ExecuteNonQueryAsync();
				}

				//transactionScope.Complete();
			}

這樣是可以的,TransactionScope 管理在期內的所有 IDbConnection,讓他們在當前的事務中保持一致。

但是 BeginTransaction() 是使用 IDbConnection.BeginTransaction() 創建的,不能跨 IDbConnection 使用。

比如,以下代碼會報錯:

			using var connection1 = dataSource.CreateConnection();
			using var connection2 = dataSource.CreateConnection();
			await connection1.OpenAsync();
			await connection2.OpenAsync();

			try
			{
				var tran1 = connection1.BeginTransaction();

				var command1 = connection1.CreateCommand();
				command1.Transaction = tran1;
				var command2 = connection2.CreateCommand();
				command2.Transaction = tran1;

				command1.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (108, 1, '測試', '測試', '測試', '2023-12-08');
				""";
				await command1.ExecuteNonQueryAsync();
				command2.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (108, 1, '測試', '測試', '測試', '2023-12-08');
				""";
				await command2.ExecuteNonQueryAsync();
				tran1.Commit();
			}
			catch (Exception ex)
			{
				logger.LogError(ex, "Tran error");
			}

所以,這里又有一個區別。

嵌套事務

.BeginTransaction() 不支持嵌套事務,代碼如下所示:

		static async Task Main(string[] args)
        {
            using var connection = dataSource.CreateConnection();
			await connection.OpenAsync();
			var tran = connection.BeginTransaction();

			try
			{
				var command = connection.CreateCommand();
				command.Transaction = tran;
				command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (110, 1, '測試', '測試', '測試', '2023-12-08');
				""";
				await command.ExecuteNonQueryAsync();

				// 嵌套事務
				try
				{
					await InsertAsync(connection);
				}
				catch (Exception ex)
				{
					logger.LogError(ex, "Tran error.");
					await tran.RollbackAsync();
					return;
				}

				await tran.RollbackAsync();
			}
			catch (Exception ex)
			{
				logger.LogError(ex, "Tran error");
			}
		}

		// 嵌套的子事務
		private static async Task InsertAsync(MySqlConnection connection)
		{
			var tran = connection.BeginTransaction();
			var command = connection.CreateCommand();
			command.Transaction = tran;
			command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (112, 1, '測試', '測試', '測試', '2023-12-08');
				""";
			await command.ExecuteNonQueryAsync();
			await tran.CommitAsync();
		}

當一個 IDbConnection 調用兩次 .BeginTransaction() 時,代碼會報錯。

 System.InvalidOperationException: Transactions may not be nested.

所以,我們只能寄望于 TransactionScope。

使用 TransactionScope 做嵌套事務,可以做到靈活的邏輯定制,每個嵌套子事務都有自己的邏輯。

每個子事務只需要正常編寫自己的 TransactionScope 即可,即使子事務的 TransactionScope 已提交,如果最外層的 TransactionScope 事務沒有提交,則所有的事務都不會提交。

如下代碼所示:

	static async Task Main(string[] args)
	{
		using var connection = dataSource.CreateConnection();
		using (TransactionScope transactionScope = new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
		{
			await connection.OpenAsync();
			var command = connection.CreateCommand();
			command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (110, 1, '測試', '測試', '測試', '2023-12-08');
				""";
			await command.ExecuteNonQueryAsync();

			// 嵌套事務
			try
			{
				await InsertAsync(connection);
			}
			catch (Exception ex)
			{
				logger.LogError(ex, "Tran error.");
				return;
			}
			// transactionScope.Complete();
		}
	}

	// 嵌套的子事務
	private static async Task InsertAsync(MySqlConnection connection)
	{
		using (TransactionScope transactionScope = new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
		{
			var command = connection.CreateCommand();
			command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (112, 1, '測試', '測試', '測試', '2023-12-08');
				""";
			await command.ExecuteNonQueryAsync();
			transactionScope.Complete();
		}
	}

雖然 InsertAsync() 中的事務已經提交,但是由于其受到外層 TransactionScope 事務的影響,因此當外層事務不提交時,子事務也不會提交。

當然,即使不是同一個 IDbConnection 也是可以的。

	static async Task Main(string[] args)
	{
		using var connection = dataSource.CreateConnection();
		using (TransactionScope transactionScope = new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
		{
			await connection.OpenAsync();
			var command = connection.CreateCommand();
			command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (110, 1, '測試', '測試', '測試', '2023-12-08');
				""";
			await command.ExecuteNonQueryAsync();

			// 嵌套事務
			try
			{
				await InsertAsync();
			}
			catch (Exception ex)
			{
				logger.LogError(ex, "Tran error.");
				return;
			}
			// transactionScope.Complete();
		}
	}

	// 嵌套的子事務
	private static async Task InsertAsync()
	{
		using var connection = dataSource.CreateConnection();
		using (TransactionScope transactionScope = new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
		{
			await connection.OpenAsync();
			var command = connection.CreateCommand();
			command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (112, 1, '測試', '測試', '測試', '2023-12-08');
				""";
			await command.ExecuteNonQueryAsync();
			transactionScope.Complete();
		}
	}

所以,每個方法的代碼,只需要關注自己的邏輯即可。對于模塊分離、職責分離的代碼很有用。

事務范圍

前面我們提到了 TransactionScope 的嵌套事務。

TransactionScope 對于嵌套事務的處理,有一個 TransactionScopeOption 枚舉配置。

	public enum TransactionScopeOption
	{
        // 該范圍需要一個事務。 如果已經存在環境事務,則使用該環境事務。 否則,在進入范圍之前創建新的事務。 這是默認值。
		Required = 0,
        
        // 總是為該范圍創建新事務。
		RequiresNew = 1,
        
        // 如果使用 Suppress 實例化范圍,則無論是否存在環境事務,該范圍都不會參與事務。使用此值實例化的范圍始終將 null 作為其環境事務。
		Suppress = 2
	}

使用示例:

using(TransactionScope scope1 = new TransactionScope())
{
    // 默認支持嵌套
    using(TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Required))
    {
        //...
    }
    
    // 不受 scope1 的影響
    using(TransactionScope scope3 = new TransactionScope(TransactionScopeOption.RequiresNew))
    {
        //...  
    }
  
    // 如果使用 Suppress 實例化范圍,則無論是否存在環境事務,該范圍都不會參與事務。
    using(TransactionScope scope4 = new TransactionScope(TransactionScopeOption.Suppress))
    {
        //...  
    }
}

對于嵌套事務作用域范圍,讀者可以從這篇文章中了解更多:https://learn.microsoft.com/en-us/previous-versions/ms172152(v=vs.90)?redirectedfrom=MSDN#Y1642

封裝 DbContext

前面提到過,IDbConnection 需要在 TransactionScope 中打開連接,TransactionScope 才能管控其連接的事務。

不過,有一些數據庫驅動已經支持了 TransactionScope ,即使不在其內打開鏈接也可以。比如 EFCore 框架,EFCore 自動管理 IDbConnection 的生命周期,因此我們往往不會手動管理連接,因此事務事務時,我們不太可能這樣做:

MyContext _context;

using (TransactionScope transactionScope = ...)
{
    _context.Connection.Open()
}

在使用數據庫事務之前,往往連接早就已經打開了。

MyContext _context;
_context.SelectAsync()....
_context.User.SectAsync()....
using (TransactionScope transactionScope = ...)
{
}

所以,我們需要封裝一個上下文類型,能夠在連接打開后,自動使用上下文的事務。

TransactionScope

封裝一個數據庫上下文,執行命令時,如果發現其在事務范圍內,則主動使用上下文事務。

	public class DbContext
	{
		private readonly DbConnection _connection;

		public DbContext(DbConnection connection)
		{
			_connection = connection;
		}

		public async Task ExecuteAsync(string sql)
		{
			var command = _connection.CreateCommand();
            // 獲取當前事務
			var tran = Transaction.Current;
			if (tran != null)
			{
                // 注意這里。
				_connection.EnlistTransaction(tran);
			}

			command.CommandText = sql;
            
			await command.ExecuteNonQueryAsync();
		}
	}

使用示例:

		using var connection = dataSource.CreateConnection();
// 在之外打開
		await connection.OpenAsync();
		var context = new DbContext(connection);

		using (TransactionScope transactionScope = new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
		{
			var sql = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (111, 1, '測試', '測試', '測試', '2023-12-08');
				""";

			await context.ExecuteAsync(sql);
		}

BeginTransaction()

使用上下文的形式封裝 BeginTransaction() 開啟的事務比較簡單,只需要手動維護 DbTransaction 即可。


	public class DbContext
	{
		private readonly DbConnection _connection;
		private DbTransaction? _tran;
		public DbContext(MySqlConnection connection)
		{
			_connection = connection;
		}

		public async Task OpenTran()
		{
			if (_tran != null) throw new Exception("請勿重復開啟事務");
			_tran = await _connection.BeginTransactionAsync();
		}

		public async Task ExecuteAsync(string sql)
		{
			var command = _connection.CreateCommand();
			command.CommandText = sql;

			if (_tran != null)
			{
				command.Transaction = _tran;
			}
			await command.ExecuteNonQueryAsync();
		}

		public async Task EndTran()
		{
			if (_tran == null) throw new Exception("未開啟事務");
			await _tran.CommitAsync();
			_tran.Dispose();
			_tran = null;
		}
	}

使用方法:

		using var connection = dataSource.CreateConnection();
		await connection.OpenAsync();
		DbContext context = new DbContext(connection);

		await context.OpenTran();
		var sql = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (111, 1, '測試', '測試', '測試', '2023-12-08');
				""";
		await context.ExecuteAsync(sql);

當然,由于不同的 ORM 封裝的數據庫事務方法不一樣,因此 ORM 的差異比較大。

總結

以上是生活随笔為你收集整理的C# 从代码入门 Mysql 数据库事务的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

夜色成人网 | 九草视频在线 | 国产短视频在线播放 | 中文字幕视频一区 | 一本一道久久a久久精品蜜桃 | 国产手机视频精品 | 久久看毛片 | 久久伊99综合婷婷久久伊 | 九九九九免费视频 | 中文字幕在线影视资源 | 免费欧美精品 | 色网站在线观看 | 成人久久精品 | 天天干天天射天天爽 | 成人国产精品一区 | 日免费视频 | 久草免费新视频 | 97成人在线免费视频 | 国产亚洲欧美一区 | 成人av在线一区二区 | 91日韩精品视频 | 亚洲午夜久久久综合37日本 | 日韩精品91偷拍在线观看 | 久久国产视频网 | 国产91免费在线 | 亚洲一级片在线看 | 综合久久久久久久久 | 久久不射网站 | 91人人视频在线观看 | 一区二区三区在线免费播放 | 婷婷丁香花 | 国产精品久久久久久久妇 | 久久96国产精品久久99漫画 | 综合久久久久久久 | 99精品一区 | 国产精品2018 | 人人添人人澡人人澡人人人爽 | 狠狠色伊人亚洲综合网站野外 | 亚洲黄色小说网 | 久久精品99国产精品酒店日本 | 亚洲精品777 | 天天爱天天色 | 狠狠干综合网 | www.888av | 黄色大全在线观看 | 狠狠操狠狠操 | 国产精品美女在线观看 | 欧美一区二区在线免费看 | 欧美va在线观看 | 在线午夜电影神马影院 | 手机在线免费av | 人人舔人人干 | 91精选在线 | 黄www在线观看 | 色综合久久五月天 | 色妞久久福利网 | 久久这里只有精品视频首页 | 免费看av在线 | 久久久久久毛片精品免费不卡 | 色婷婷伊人| 91免费观看国产 | 日韩乱理 | 欧美日本高清视频 | 国产视频69 | 亚洲精品国产精品乱码在线观看 | 在线日本v二区不卡 | 99中文字幕在线观看 | 国产精华国产精品 | 久久精品官网 | 97夜夜澡人人双人人人喊 | 中文字幕区 | 亚洲专区在线播放 | 亚洲激情 欧美激情 | 亚洲精品国产第一综合99久久 | 九九久久久久久久久激情 | 高清久久久久久 | 久久久国际精品 | 国产午夜麻豆影院在线观看 | 成人福利av | 中文字幕文字幕一区二区 | 一级淫片在线观看 | 九九视频这里只有精品 | 91久久国产精品 | 6080yy精品一区二区三区 | 午夜精品麻豆 | 99久久99久国产黄毛片 | 久久久www | www天天干| 精品国产一区二区在线 | 精品国产一区二区三区久久影院 | 顶级bbw搡bbbb搡bbbb | 91高清在线 | 在线播放你懂 | 亚洲永久av | 2023亚洲精品国偷拍自产在线 | 五月婷婷在线视频观看 | 日韩综合一区二区三区 | 国产成人av网 | 国产xxxxx在线观看 | 亚洲国产精品va在线看黑人 | 久久在线精品 | 免费看片网页 | 香蕉视频在线看 | 在线看黄网站 | 欧美精品九九99久久 | 日韩久久久久久久久久久久 | av观看免费在线 | 久久艹久久 | 麻豆精品在线 | 中文字幕一区二区三区在线视频 | 日日干夜夜草 | 久久 在线 | 亚洲黄色小说网 | 日本少妇高清做爰视频 | 中文字幕91| 日韩视频www | 综合久久网 | av亚洲产国偷v产偷v自拍小说 | 免费亚洲成人 | 超碰97成人 | 黄色资源在线观看 | www.com.黄 | 免费99视频 | 婷婷国产在线观看 | 成人小视频在线 | 亚洲 中文字幕av | 在线观看蜜桃视频 | 免费看污在线观看 | 国产美女精彩久久 | 欧美午夜激情网 | 日韩精品视频在线免费观看 | 欧美了一区在线观看 | 天天激情天天干 | 久久久www成人免费毛片麻豆 | 精品96久久久久久中文字幕无 | 日韩国产精品久久久久久亚洲 | 99在线精品视频在线观看 | 中国精品少妇 | www色av| 毛片网站免费在线观看 | 国产成人精品一区二区在线 | 久久久久免费网站 | 91在线视频免费91 | 狠狠干网| 亚洲精品国偷拍自产在线观看蜜桃 | 91免费在线播放 | 久久视频国产 | 在线视频日韩 | 国产黄免费在线观看 | 国产亚洲精品久久久久久大师 | 中文字幕一区二 | 黄色天堂在线观看 | 福利二区视频 | 91视频麻豆视频 | 一色屋精品视频在线观看 | 国产精品久久久久av福利动漫 | 国产精品国产精品 | 99久高清在线观看视频99精品热在线观看视频 | 日本黄色免费在线观看 | 亚洲一级黄色av | 天天操综合网 | 一区二区三区免费在线观看视频 | 不卡视频在线看 | 久久综合狠狠综合久久狠狠色综合 | 国产精品美女免费 | 91高清视频在线 | 91男人影院 | 中文字幕有码在线播放 | 激情欧美丁香 | 中文一区在线观看 | 国产99视频在线观看 | 在线观看岛国av | 日本最新高清不卡中文字幕 | 狠狠狠色丁香综合久久天下网 | 精品国产乱码久久久久久浪潮 | 久久国产免费 | 国产精品6999成人免费视频 | 91视频啪| 99视频99 | 最近免费中文字幕大全高清10 | 一本一道久久a久久精品蜜桃 | 99久久精品免费看国产一区二区三区 | 四虎影视久久久 | 国产精品久免费的黄网站 | 久久中文字幕导航 | 免费看色视频 | 国产爽视频 | 久久久久久久久久久免费av | 欧美成人亚洲成人 | 国产精品av在线免费观看 | 亚洲天堂网在线观看视频 | 9999在线视频 | 亚洲视频六区 | 色婷婷88av视频一二三区 | 亚洲综合网站在线观看 | 最新久久久 | 成人av高清在线观看 | 成av在线 | 国产精品刺激对白麻豆99 | 成人久久久久久久久 | 免费看的视频 | 久久在线免费观看视频 | 成人av直播 | 亚洲精品乱码久久久久久蜜桃91 | 久久精品国产亚洲aⅴ | av电影在线免费 | 天天爽天天搞 | 久草在线视频首页 | 黄色亚洲在线 | 天堂黄色片 | 欧美一二区在线 | 久久99亚洲精品久久久久 | 色综合久久久久网 | 日韩欧美国产激情在线播放 | 亚洲精品乱码久久久久久蜜桃欧美 | 欧美9999| 国产视频在线播放 | 午夜精品久久久久久久99水蜜桃 | 人人擦 | 伊人电影天堂 | 欧美一区二区三区在线看 | 人人爽人人射 | 在线观看亚洲免费视频 | 国产精品99久久久精品免费观看 | 69精品视频| 五月激情丁香婷婷 | 久久久综合精品 | 国产成人av在线影院 | 97成人精品视频在线观看 | 久久天堂亚洲 | 安徽妇搡bbbb搡bbbb | 探花视频免费在线观看 | 国产高清不卡一区二区三区 | 成人av午夜 | 天天操天天舔天天爽 | 99久久久久免费精品国产 | 亚洲一级国产 | 麻豆国产精品永久免费视频 | 夜夜躁日日躁狠狠躁 | 日韩色一区二区三区 | 亚洲精品免费在线观看 | av电影 一区二区 | 免费试看一区 | 美女视频免费精品 | 99精品色| 九九热视频在线免费观看 | 日韩网站免费观看 | av天天在线观看 | a天堂免费 | 久草在在线视频 | 亚洲国产资源 | 国精产品一二三线999 | av不卡中文字幕 | 国产精品99久久久久 | 亚洲精品视频在线观看免费 | 在线视频你懂得 | 精品天堂av| 国产精品美女免费 | 一级免费看视频 | 成人丝袜 | 国产综合在线观看视频 | 亚洲电影成人 | 久久久久久片 | 国产在线精品二区 | 久久成人视屏 | 日本中文字幕视频 | 国产日产在线观看 | 国产精品欧美在线 | www.av在线.com | 在线精品在线 | 精品一区二区精品 | 日韩高清av在线 | 91精品国产99久久久久久久 | 亚洲综合成人专区片 | 天天干天天做 | 色偷偷男人的天堂av | 国产一区二区三区网站 | 中文成人字幕 | 国产九九九视频 | 在线观看完整版免费 | 国产999免费视频 | 在线成人免费电影 | 精品久久国产 | 在线视频观看成人 | av片无限看| 久久久久观看 | 精品1区2区3区 | 国产精品一区二区三区在线播放 | 91粉色视频| 夜夜夜夜夜夜操 | 在线一区观看 | 国产精品久久久久久久久久ktv | 国产成人高清 | 国产精品久久在线观看 | 久久都是精品 | 伊人国产在线观看 | 中文字幕在线看视频国产 | 日韩中文字幕在线不卡 | 一区二区三区在线视频111 | 国产精品99久久久久的智能播放 | 一级免费片 | 香蕉影视在线观看 | 亚洲91网站| 69夜色精品国产69乱 | 国产色女 | 亚洲精品久久久久中文字幕二区 | 亚洲精品综合一区二区 | 免费视频 三区 | 国产精品一区二 | 日日操天天射 | 人人干狠狠操 | 91在线区 | www.国产视频 | 欧美性生活久久 | 91精品国产欧美一区二区 | 国产亚洲精品久久久久久久久久久久 | 天天伊人网 | 日日夜色 | 色婷婷亚洲婷婷 | 天天做天天爽 | 国产精品女教师 | 久久久免费| 亚洲高清视频一区二区三区 | 91探花国产综合在线精品 | 天天操网 | 涩五月婷婷| 成人毛片在线观看视频 | 久久亚洲欧美日韩精品专区 | 国产精品网站一区二区三区 | 91精品爽啪蜜夜国产在线播放 | 国产中文自拍 | 久草在线中文视频 | 日韩最新av | 96视频在线 | 天天超碰 | 久久久精品免费观看 | 国产精品久久三 | 一区二区视频在线播放 | 日批网站免费观看 | 日本精品久久久久影院 | 亚洲女欲精品久久久久久久18 | 国产网站av | 久久精品视频在线播放 | 久久av福利 | 三级黄色片子 | 不卡av免费在线观看 | 久久久久久久av麻豆果冻 | 国产福利在线 | 99精品视频精品精品视频 | 在线观看精品视频 | 欧美 高跟鞋交 xxxxhd | 国产在线 一区二区三区 | 久久成人久久 | 午夜精品一区二区三区视频免费看 | 国产成人免费观看 | 日韩激情小视频 | 西西人体4444www高清视频 | 四虎在线视频 | 国产一区二区影院 | 亚洲天堂视频在线 | 亚洲成熟女人毛片在线 | 在线日韩精品视频 | 青青河边草免费观看 | 四虎影视8848dvd | 91亚洲影院| 日本一区二区三区免费观看 | 亚洲片在线资源 | a级国产片 | 91av视频在线免费观看 | 狠狠色丁香久久婷婷综合五月 | 免费不卡中文字幕视频 | 国产精品人人做人人爽人人添 | 色婷婷av一区 | 夜添久久精品亚洲国产精品 | 蜜臀av免费一区二区三区 | 中文在线a天堂 | 免费视频成人 | 99久久爱| 精品国产一区二区三区不卡 | 日本狠狠干 | 超碰在线网 | 98涩涩国产露脸精品国产网 | 久艹视频在线观看 | 97超碰中文字幕 | 久久免费视频99 | 91免费的视频在线播放 | 伊人夜夜| 成人性生活大片 | 成人蜜桃视频 | 国产成人在线一区 | 超碰97人人在线 | 91久久久久久国产精品 | 国产亚洲无 | 国产精品美乳一区二区免费 | 国产精品18久久久久久久网站 | 国产精品18毛片一区二区 | 精品久久片 | 亚洲激情在线 | av网站免费看 | 在线成人性视频 | 日韩二区三区在线观看 | 国产福利91精品张津瑜 | 天天摸天天舔天天操 | 久久精品9 | 欧美久久久久久久久久久 | 91久色蝌蚪 | 黄色亚洲在线 | 国产香蕉97碰碰久久人人 | 日韩一区二区免费播放 | 黄色亚洲 | 美州a亚洲一视本频v色道 | 黄色a大片 | 在线看国产日韩 | 国产精品视频永久免费播放 | 黄色免费在线视频 | 日本最新中文字幕 | 狠狠躁18三区二区一区ai明星 | 国产中文字幕在线播放 | 国产91成人在在线播放 | 日韩精品中文字幕在线不卡尤物 | 四虎永久国产精品 | 国产亚洲激情视频在线 | 狠狠干在线播放 | 欧美精品乱码99久久影院 | 中文字幕一区二区三区四区视频 | 福利久久久 | 肉色欧美久久久久久久免费看 | 9999免费视频| 亚洲视屏一区 | 久久影视网 | 中文乱码视频在线观看 | 国产精品久久久久久久久免费 | 日本资源中文字幕在线 | 国产伦精品一区二区三区照片91 | 操操操干干干 | 激情在线网站 | 亚洲日本va在线观看 | 成人av免费在线观看 | 中文字幕在线日 | 97香蕉久久超级碰碰高清版 | 麻豆91精品91久久久 | 中文免费 | 91av在线视频免费观看 | 亚洲精品一区二区三区新线路 | 亚洲精选视频免费看 | 五月婷婷.com | 91传媒激情理伦片 | 国产免费专区 | 精品视频免费久久久看 | 美女久久久久久久久久久 | 高清不卡免费视频 | 欧美夫妻生活视频 | www.激情五月.com | 亚洲电影黄色 | 91视频a| 干狠狠| 免费高清在线观看成人 | 制服丝袜一区二区 | 超碰在线人 | 国产精品美女网站 | 91免费观看国产 | 国内精品久久久久影院优 | 在线视频久 | av综合 日韩 | 欧美成年黄网站色视频 | av 一区二区三区四区 | 亚洲婷婷网 | 日韩黄色免费电影 | 天天激情综合 | www.色就是色 | 日本中文在线播放 | 91视频免费播放 | 日日夜夜精品视频 | 中文字幕在线视频第一页 | 日韩欧美在线中文字幕 | 欧美日韩免费在线视频 | 91看片一区二区三区 | 欧美怡红院视频 | 中字幕视频在线永久在线观看免费 | 成人av在线观 | 精品国产一区二区三区在线观看 | 久久国产免费 | 97精品超碰一区二区三区 | 国产资源在线视频 | 青青河边草免费观看完整版高清 | 亚洲婷久久 | 国产在线综合视频 | 国产日韩视频在线 | 国产精品久久久久久久av电影 | 免费情趣视频 | 精品国产成人av在线免 | 国产精品一区二区在线观看免费 | 日日干网 | 中文字幕 国产专区 | 国际av在线 | 国产成人综合图片 | 四虎永久精品在线 | 成人久久18免费网站麻豆 | 久久影院午夜论 | 一区二区三区精品在线 | 国产视频一区精品 | 国产精品一区二区久久国产 | 天天干天天做天天爱 | 国产三级国产精品国产专区50 | 欧美不卡视频在线 | 欧美aaa级片| 一区在线播放 | 国产精品毛片网 | 欧美在线视频a | 亚洲综合最新在线 | 天天玩夜夜操 | 91原创在线观看 | 国产婷婷精品 | av在线看片 | 亚洲特级毛片 | 国产高清免费在线观看 | 在线观看亚洲精品视频 | 国产xx视频 | 亚洲综合欧美日韩狠狠色 | 欧美十八 | 在线亚洲激情 | 一本一道久久a久久精品蜜桃 | 精品久久久久久一区二区里番 | 丁香激情五月 | 在线免费视频你懂的 | 亚洲一区视频在线播放 | 日日操夜夜操狠狠操 | 国产人免费人成免费视频 | 成人免费在线看片 | 天天草天天插 | 黄色av在 | 亚洲五月| 69国产在线观看 | 日韩成人精品一区二区三区 | 国产亚洲久一区二区 | 91在线免费视频观看 | 成年人免费av | 日日操操操 | 天天舔夜夜操 | 婷婷色视频 | 欧美一级视频免费 | 一区二区电影在线观看 | 狠狠五月婷婷 | 欧美综合干 | aaa亚洲精品一二三区 | 在线观看日韩精品视频 | 国产视频一区二区三区在线 | 国产一区二区午夜 | 精品久久久久久电影 | 在线看一级片 | 国产福利在线免费 | 国产色在线 | 国产在线视频一区二区三区 | 久久综合九色欧美综合狠狠 | 日韩精品影视 | 免费福利片2019潦草影视午夜 | 国产在线欧美在线 | 99视频精品免费观看, | 国产成年人av | 人人射 | 韩日成人av | 免费看毛片在线 | 免费观看久久久 | 最近中文字幕视频网 | 国产91在线观 | 久草在线中文888 | 日日夜夜天天 | 人人干免费| 亚洲最大av在线播放 | 日韩欧美在线免费观看 | 色中文字幕在线观看 | 日韩欧美网址 | 久久综合九色综合欧美就去吻 | 日日干天夜夜 | 五月婷香蕉久色在线看 | 精品国产诱惑 | 天天拍天天爽 | 日本系列中文字幕 | 久久亚洲综合色 | 精品久久久久久久久久久久 | 久久久久女人精品毛片九一 | 久久久午夜剧场 | 国产午夜精品久久 | 久久精美视频 | 日本h视频在线观看 | 国产综合视频在线观看 | 玖玖在线观看视频 | 中文字幕在线观看免费 | 懂色av懂色av粉嫩av分享吧 | 国产馆在线播放 | 91九色精品女同系列 | 亚洲国产成人久久 | 国产韩国日本高清视频 | 免费看国产一级片 | 91av视频在线免费观看 | 免费午夜网站 | 中文字幕丝袜一区二区 | 在线观看视频97 | 日韩精品91偷拍在线观看 | 国产中文字幕在线免费观看 | 日韩草比 | 欧美日韩亚洲在线观看 | 国产网站色 | 欧美日韩国产精品久久 | 亚洲欧美视频网站 | 久久99精品国产一区二区三区 | 香蕉在线观看 | a√天堂资源 | 亚洲视频精选 | 97碰碰视频 | 久久精品亚洲国产 | 97av影院 | 久久精品成人 | 中文字幕中文 | 中文字幕久久精品 | 九九免费在线看完整版 | 九九热国产视频 | 天天操天天操天天操天天操天天操天天操 | 日p在线观看 | 亚洲高清在线观看视频 | 久久9999久久免费精品国产 | 伊人色综合久久天天 | 国产成人亚洲在线电影 | 国产精品久久网站 | 免费又黄又爽视频 | 亚洲国产成人在线观看 | 久久久免费精品国产一区二区 | 黄色免费在线看 | 日韩网站免费观看 | 国产福利精品一区二区 | 国产91精品看黄网站在线观看动漫 | 一级一片免费看 | 欧美日韩精品久久久 | 又黄又网站 | 亚洲日本欧美在线 | 一区在线观看视频 | 国产精品美女久久久久久免费 | 91传媒激情理伦片 | 99久久精品国产系列 | 日韩激情一二三区 | 亚洲人天堂 | 久久在线影院 | 日韩av在线影视 | 亚洲妇女av | 国产黄a三级 | 国产日本亚洲高清 | 久久精品www人人爽人人 | 精品少妇一区二区三区在线 | 91片黄在线观 | 在线黄色国产电影 | 五月婷婷激情综合 | aaawww| 99国产精品免费网站 | 天天躁天天操 | 国际精品久久久久 | 天天操操操操操 | 超碰97免费 | 最新国产精品亚洲 | 激情欧美一区二区三区免费看 | 国产玖玖精品视频 | 久久亚洲精品国产亚洲老地址 | 在线观看色视频 | 成人久久18免费网站图片 | 免费看黄在线看 | 91九色成人| 久草在线免费新视频 | 91九色在线 | 91夫妻视频 | 日韩精品一区二 | 欧美午夜久久久 | 午夜精品一区二区三区视频免费看 | 免费观看mv大片高清 | 中文字幕在线播放日韩 | 久久精精品视频 | 免费不卡中文字幕视频 | 91日韩在线专区 | 欧美天天射 | 91看片一区二区三区 | 午夜色婷婷 | 午夜三级理论 | 久久欧美精品 | 免费观看国产精品视频 | 久久精品国产免费观看 | 不卡视频一区二区三区 | 日韩三级精品 | 日韩a在线 | 91麻豆视频网站 | 天堂入口网站 | 黄色大片网 | 超碰97中文| 国产精品福利无圣光在线一区 | 国产精品一区二区久久久 | 97超碰国产精品女人人人爽 | av网站有哪些 | 日韩欧美视频一区 | 欧美一二三视频 | 69久久久久久久 | 欧美另类交在线观看 | 日本中文字幕在线看 | 夜色成人网 | 永久中文字幕 | 国产精品久久精品 | 免费看国产一级片 | 亚洲三级网 | 色婷婷国产精品一区在线观看 | 欧美成年性 | 久久男女视频 | 毛片播放网站 | 久久久久国产精品一区 | 国产精品系列在线 | 天天做天天爽 | 激情五月婷婷丁香 | 99电影| 亚洲精品视频在线播放 | 二区三区在线视频 | 亚洲精品乱码久久久久久蜜桃欧美 | 91成人在线观看喷潮 | 奇米导航 | 国产高清免费视频 | av片子在线观看 | 精品一区二区在线免费观看 | 久久精品视频99 | 国产福利精品在线观看 | 狠狠操综合 | 国产亚洲高清视频 | 国产中文字幕一区二区 | www.com久久久 | 色婷婷天天干 | 久久草草热国产精品直播 | 少妇搡bbb | 免费av观看网站 | 在线99| 免费观看mv大片高清 | 人人插人人搞 | 中文字幕一区二区在线播放 | 五月综合在线观看 | 国产麻豆剧传媒免费观看 | 久草在线手机视频 | 免费成人黄色av | 在线看免费 | 五月天欧美精品 | 麻豆久久久久久久 | 五月天综合激情 | 久久久精品网站 | 婷婷激情综合五月天 | av综合站 | 中文字幕av在线免费 | 在线 国产 亚洲 欧美 | 亚洲成a人片在线观看网站口工 | 丁香六月综合网 | 手机看片国产日韩 | 久久免费视频一区 | 久久免费大片 | 99成人在线视频 | 日日天天狠狠 | 伊人中文字幕在线 | 伊人干综合 | 亚洲国产一区二区精品专区 | 99国产在线| 免费观看91视频 | 在线看国产一区 | 韩日电影在线免费看 | 丝袜制服综合网 | 激情久久伊人 | 丁香免费视频 | 在线观看av中文字幕 | 日韩综合在线观看 | 亚洲精品视频在线播放 | 日韩中字在线观看 | 国产在线a免费观看 | 一区二区三区在线电影 | 成人av资源在线 | 久久免视频 | 亚洲欧美日韩一区二区三区在线观看 | 992tv又爽又黄的免费视频 | 丁香 婷婷 激情 | 狠狠色丁婷婷日日 | 国产亚洲一区二区三区 | 国产aa免费视频 | 91手机电视| 国产二区精品 | 777久久久| 美女在线黄 | 久久精品官网 | 狠狠操夜夜 | 麻豆传媒电影在线观看 | 午夜免费久久看 | 97超级碰碰| 亚洲精品乱码久久久久久9色 | 亚洲va欧美va人人爽 | 一本色道久久综合亚洲二区三区 | 国产精品美女久久久久久久久 | 亚洲视频久久 | 又黄又刺激又爽的视频 | 狠狠干成人综合网 | 国产一级性生活视频 | 96精品高清视频在线观看软件特色 | 精品国产伦一区二区三区观看体验 | 久久精品视频日本 | 国产精品一区在线 | 韩日精品在线 | 欧美日韩一区二区三区不卡 | 丝袜足交在线 | 久久精品网址 | 亚洲精选国产 | 一区二区三区日韩视频在线观看 | 91麻豆精品国产自产在线 | 国产视频1区2区 | 国产在线成人 | 久久久久久久福利 | 精品国产一区二区三区av性色 | 91九色porny在线 | 九九免费观看全部免费视频 | 国产精品h在线观看 | 国产一级91 | 久久99深爱久久99精品 | 国产成人a v电影 | 色狠狠干 | 97碰在线视频 | 久久精品99国产精品亚洲最刺激 | 国产精品v欧美精品 | 国产精品网址在线观看 | 色视频网站免费观看 | 亚洲国产成人在线观看 | 国产精品岛国久久久久久久久红粉 | av成人资源 | 欧美aa级 | 91成人在线观看高潮 | 精品少妇一区二区三区在线 | 久久激情小说 | 亚洲成人动漫在线观看 | 亚州国产精品久久久 | 日韩中文字幕免费视频 | 久久精品看片 | 激情五月综合 | 日韩免费电影在线观看 | 96国产在线 | 91av电影 | 欧美在线视频精品 | 日韩电影中文字幕在线 | 中文字幕在线播放第一页 | 久久久精品成人 | 国产不卡一二三区 | 日韩专区在线播放 | 91中文字幕在线视频 | 在线观看中文字幕第一页 | 最近中文字幕国语免费av | 在线观看国产91 | 中文字幕中文字幕在线一区 | 5月丁香婷婷综合 | 精品国产中文字幕 | 超碰97在线看 | 日韩免费电影网站 | 国产精品6999成人免费视频 | 久久久久麻豆v国产 | 91精品亚洲影视在线观看 | 国产精品久久久久久久久蜜臀 | 精品亚洲午夜久久久久91 | 香蕉久草 | 亚洲成人一区 | 中文字幕免费一区二区 | 亚洲 欧美 综合 在线 精品 | 国内精品久久久久影院男同志 | 国产视频久久久久 | 五月婷婷色丁香 | www色| 成人在线视频观看 | 在线国产欧美 | 亚洲精品高清在线观看 | 最近日韩中文字幕中文 | 久久久99精品免费观看app | 天堂av最新网址 | 国产精品女同一区二区三区久久夜 | 国产精品乱看 | 在线91色 | 欧美在线观看视频一区二区三区 | 国产视频中文字幕在线观看 | 欧美成人精品三级在线观看播放 | 91传媒激情理伦片 | 久久看片| 在线视频久久 | 97精品国产aⅴ | 人人插人人玩 | 91成人短视频在线观看 | 最新中文字幕在线播放 | 亚洲精品视频在线观看免费 | 欧美一级片免费观看 | 99热这里只有精品在线观看 | 91成人网在线播放 | 日韩午夜精品福利 | 国产在线观看一 | 91av99| 日韩大片在线播放 | 久久精品伊人 | 久久精品免视看 | 91完整版在线观看 | 久久精品看 | 国内精品久久久久 | 99r在线视频 | 精品国产一区二区三区不卡 | 亚洲一区二区精品 | 在线小视频 | 日韩精品你懂的 | 精品久久久久久一区二区里番 | 一区二区三区在线观看免费视频 | 国产精品久久久久久久久免费 | 91精品一区二区三区蜜臀 | www.操.com | av午夜电影 | 99人久久精品视频最新地址 | 91成年人在线观看 | 最新不卡av | 在线观看免费福利 | 性色在线视频 | 日韩二区在线播放 | www国产亚洲精品久久麻豆 | 性日韩欧美在线视频 | 中文字幕有码在线 | 一级性视频 | 国产精品一区久久久久 | 在线观看免费高清视频大全追剧 | a级国产乱理论片在线观看 特级毛片在线观看 | 国产男女爽爽爽免费视频 | 成人av电影免费在线观看 | 欧美另类v | 色偷偷88欧美精品久久久 | 免费看黄电影 | 国产aaa毛片| 精品国产一区二区三区噜噜噜 | 91麻豆精品国产91久久久使用方法 | 国语自产偷拍精品视频偷 | 国产视频精品久久 | 欧美性色综合网站 | 久久亚洲欧美日韩精品专区 | 国产精品久久久久久久久久久免费 | 一区二区精品在线视频 | 麻豆精品在线 | 在线小视频你懂得 | 国产日韩在线视频 | 伊人亚洲综合网 | 日韩一区二区免费在线观看 | 天天干天天拍天天操 | 91人人网| 国产黄a三级 | 久久露脸国产精品 | 久久久免费高清视频 | 成人91在线| 亚洲精品综合一二三区在线观看 | 最近更新的中文字幕 | 欧美影院久久 | 欧美一区,二区 | 天天综合中文 | 国产精品丝袜 | 久久国产露脸精品国产 | 欧美aa一级 | 成人日韩av | 天天操天天干天天 | 超碰97av在线 | 亚洲永久字幕 | 成人黄色在线观看视频 | 国产a精品 | 国产午夜一级毛片 | 色综合久久99 | 一区二区三区四区久久 | 久久永久免费视频 | 中文av字幕在线观看 | 一本一道久久a久久精品蜜桃 | 国产黄色一级片在线 | 国产在线综合视频 | 超碰97国产在线 | 久久久免费电影 | 9ⅰ精品久久久久久久久中文字幕 | 国产在线精品一区 | 黄色在线观看免费网站 | 成人久久18免费网站 | 黄色影院在线播放 | 久久久国产99久久国产一 | 久久黄色免费观看 | 亚洲最大av | 九九免费在线观看视频 | 国产在线观看高清视频 | 亚洲一区二区精品3399 | www久久精品 | 美女黄视频免费 | 国产三级香港三韩国三级 | 西西444www大胆高清视频 | 黄色一及电影 | 91视频网址入口 | 一区二区视频欧美 | 激情丁香在线 | 成人免费看电影 | 在线一二三四区 | 激情视频网页 | 夜夜爽www| 成人国产网站 | 91成人精品一区在线播放69 | 成人a级网站 | 亚洲久草网 |