原文: http://zetcode.com/db/sqlitecsharp/trans/

在本章中,我们将处理事务。 首先,我们提供一些基本定义。 然后,我们将有一些程序显示如何处理事务。

事务是针对一个或多个数据库中数据的数据库操作的基本单位。 事务中所有 SQL 语句的影响可以全部提交给数据库,也可以全部回滚。

在 SQLite 中,除SELECT以外的任何命令都将启动隐式事务。 同样,在事务中,诸如CREATE TABL …,VACUUMPRAGMA之类的命令将在执行之前提交先前的更改。

手动事务以BEGIN TRANSACTION语句开始,并以COMMITROLLBACK语句结束。

SQLite 支持三种非标准的事务级别:DEFERREDIMMEDIATEEXCLUSIVE。 除非我们启动自己的事务,否则 SQLite 会自动将每个命令放入其自己的事务中。 请注意,这也可能会受到驱动程序的影响。 SQLite Python 驱动程序默认情况下已关闭自动提交模式,并且第一个 SQL 命令启动新事务。

  1. using System;
  2. using Mono.Data.Sqlite;
  3. public class Example
  4. {
  5. static void Main()
  6. {
  7. string cs = "URI=file:test.db";
  8. using (SqliteConnection con = new SqliteConnection(cs))
  9. {
  10. con.Open();
  11. using (SqliteCommand cmd = con.CreateCommand())
  12. {
  13. cmd.CommandText = "DROP TABLE IF EXISTS Friends";
  14. cmd.ExecuteNonQuery();
  15. cmd.CommandText = @"CREATE TABLE Friends(Id INTEGER PRIMARY KEY,
  16. Name TEXT)";
  17. cmd.ExecuteNonQuery();
  18. cmd.CommandText = "INSERT INTO Friends(Name) VALUES ('Tom')";
  19. cmd.ExecuteNonQuery();
  20. cmd.CommandText = "INSERT INTO Friends(Name) VALUES ('Rebecca')";
  21. cmd.ExecuteNonQuery();
  22. cmd.CommandText = "INSERT INTO Friends(Name) VALUES ('Jim')";
  23. cmd.ExecuteNonQuery();
  24. cmd.CommandText = "INSERT INTO Friends(Name) VALUES ('Robert')";
  25. cmd.ExecuteNonQuery();
  26. cmd.CommandText = "INSERT INTO Friends(Name) VALUES ('Julian')";
  27. cmd.ExecuteNonQuery();
  28. }
  29. con.Close();
  30. }
  31. }
  32. }

我们创建一个Friends表,并用数据填充它。 我们没有明确地启动事务,也没有调用commitrollback方法。 但是数据已写入表中。 这是因为默认情况下,我们在自动提交模式下工作。 在这种模式下,每个 SQL 语句立即生效。

  1. cmd.CommandText = "DROP TABLE IF EXISTS Friends";
  2. cmd.ExecuteNonQuery();
  3. cmd.CommandText = @"CREATE TABLE Friends(Id INTEGER PRIMARY KEY,
  4. Name TEXT)";
  5. cmd.ExecuteNonQuery();

如果Friends表已经存在,我们将其删除。 然后,我们使用CREATE TABLE语句创建表。

  1. cmd.CommandText = "INSERT INTO Friends(Name) VALUES ('Tom')";
  2. cmd.ExecuteNonQuery();
  3. cmd.CommandText = "INSERT INTO Friends(Name) VALUES ('Rebecca')";
  4. cmd.ExecuteNonQuery();
  5. ...

我们插入两行。

  1. sqlite> SELECT * FROM Friends;
  2. 1|Tom
  3. 2|Rebecca
  4. 3|Jim
  5. 4|Robert
  6. 5|Julian

Friends表已成功创建。

在第二个示例中,我们将使用BeginTransaction()方法启动自定义事务。

  1. using System;
  2. using Mono.Data.Sqlite;
  3. public class Example
  4. {
  5. static void Main()
  6. {
  7. string cs = "URI=file:test.db";
  8. using (SqliteConnection con = new SqliteConnection(cs))
  9. {
  10. con.Open();
  11. using(SqliteTransaction tr = con.BeginTransaction())
  12. {
  13. using (SqliteCommand cmd = con.CreateCommand())
  14. {
  15. cmd.Transaction = tr;
  16. cmd.CommandText = "DROP TABLE IF EXISTS Friends";
  17. cmd.ExecuteNonQuery();
  18. cmd.CommandText = @"CREATE TABLE Friends(Id INTEGER PRIMARY KEY,
  19. Name TEXT)";
  20. cmd.ExecuteNonQuery();
  21. cmd.CommandText = "INSERT INTO Friends(Name) VALUES ('Tom')";
  22. cmd.ExecuteNonQuery();
  23. cmd.CommandText = "INSERT INTO Friends(Name) VALUES ('Rebecca')";
  24. cmd.ExecuteNonQuery();
  25. cmd.CommandText = "INSERT INTO Friends(Name) VALUES ('Jim')";
  26. cmd.ExecuteNonQuery();
  27. cmd.CommandText = "INSERT INTO Friends(Name) VALUES ('Robert')";
  28. cmd.ExecuteNonQuery();
  29. cmd.CommandText = "INSERT INTO Friends(Name) VALUES ('Julian')";
  30. cmd.ExecuteNonQuery();
  31. cmd.CommandText = "INSERT INTO Friends(Name) VALUES ('Jane')";
  32. cmd.ExecuteNonQuery();
  33. }
  34. tr.Commit();
  35. }
  36. con.Close();
  37. }
  38. }
  39. }

所有 SQL 命令组成一个单元。 要么全部保存,要么什么都不保存。 这是事务背后的基本思想。

  1. using(SqliteTransaction tr = con.BeginTransaction())

BeginTransaction()方法启动事务。

  1. cmd.Transaction = tr;

我们设置SqliteCommand在其中执行的事务。

  1. tr.Commit();

如果一切正常,我们将整个事务提交到数据库。 如果发生异常,则事务将在后台回滚。

显式回滚调用

现在,我们将显示一个示例,在发生异常的情况下,我们手动回滚事务。

  1. using System;
  2. using Mono.Data.Sqlite;
  3. public class Example
  4. {
  5. static void Main()
  6. {
  7. string cs = "URI=file:test.db";
  8. SqliteConnection con = null;
  9. SqliteTransaction tr = null;
  10. SqliteCommand cmd = null;
  11. try
  12. {
  13. con = new SqliteConnection(cs);
  14. con.Open();
  15. tr = con.BeginTransaction();
  16. cmd = con.CreateCommand();
  17. cmd.Transaction = tr;
  18. cmd.CommandText = "DROP TABLE IF EXISTS Friends";
  19. cmd.ExecuteNonQuery();
  20. cmd.CommandText = @"CREATE TABLE Friends(Id INTEGER PRIMARY KEY,
  21. Name TEXT)";
  22. cmd.ExecuteNonQuery();
  23. cmd.CommandText = "INSERT INTO Friends(Name) VALUES ('Tom')";
  24. cmd.ExecuteNonQuery();
  25. cmd.CommandText = "INSERT INTO Friends(Name) VALUES ('Rebecca')";
  26. cmd.ExecuteNonQuery();
  27. cmd.CommandText = "INSERT INTO Friends(Name) VALUES ('Jim')";
  28. cmd.ExecuteNonQuery();
  29. cmd.CommandText = "INSERT INTO Friends(Name) VALUES ('Robert')";
  30. cmd.ExecuteNonQuery();
  31. cmd.CommandText = "INSERT INTO Friends(Name) VALUES ('Julian')";
  32. cmd.ExecuteNonQuery();
  33. cmd.CommandText = "INSERT INTO Friends(Name) VALUES ('Jane')";
  34. cmd.ExecuteNonQuery();
  35. tr.Commit();
  36. } catch (SqliteException ex)
  37. {
  38. Console.WriteLine("Error: {0}", ex.ToString());
  39. if (tr != null)
  40. {
  41. try
  42. {
  43. tr.Rollback();
  44. } catch (SqliteException ex2)
  45. {
  46. Console.WriteLine("Transaction rollback failed.");
  47. Console.WriteLine("Error: {0}", ex2.ToString());
  48. } finally
  49. {
  50. tr.Dispose();
  51. }
  52. }
  53. } finally
  54. {
  55. if (cmd != null)
  56. {
  57. cmd.Dispose();
  58. }
  59. if (tr != null)
  60. {
  61. tr.Dispose();
  62. }
  63. if (con != null)
  64. {
  65. try
  66. {
  67. con.Close();
  68. } catch (SqliteException ex)
  69. {
  70. Console.WriteLine("Closing connection failed.");
  71. Console.WriteLine("Error: {0}", ex.ToString());
  72. } finally
  73. {
  74. con.Dispose();
  75. }
  76. }
  77. }
  78. }
  79. }

我们创建自己的trycatchfinally块,以解决可能的问题。

  1. } catch (SqliteException ex)
  2. {
  3. Console.WriteLine("Error: {0}", ex.ToString());
  4. if (tr != null)
  5. {
  6. try
  7. {
  8. tr.Rollback();
  9. } catch (SqliteException ex2)
  10. {
  11. Console.WriteLine("Transaction rollback failed.");
  12. Console.WriteLine("Error: {0}", ex2.ToString());
  13. } finally
  14. {
  15. tr.Dispose();
  16. }
  17. }
  18. }

在创建Friends表的过程中引发异常时,我们将调用Rollback()方法。 即使进行回滚,也可能会发生异常。 因此,我们也检查这种情况。

  1. if (cmd != null)
  2. {
  3. cmd.Dispose();
  4. }
  5. if (tr != null)
  6. {
  7. tr.Dispose();
  8. }

当一切顺利时,我们将配置资源。

  1. if (con != null)
  2. {
  3. try
  4. {
  5. con.Close();
  6. } catch (SqliteException ex)
  7. {
  8. Console.WriteLine("Closing connection failed.");
  9. Console.WriteLine("Error: {0}", ex.ToString());
  10. } finally
  11. {
  12. con.Dispose();
  13. }
  14. }

关闭连接时,我们可能会收到另一个异常。 我们在这里处理这种情况。

错误

当事务中存在错误时,将回滚事务,并且不会将任何更改提交到数据库。

  1. using System;
  2. using Mono.Data.Sqlite;
  3. public class Example
  4. {
  5. static void Main()
  6. {
  7. string cs = "URI=file:test.db";
  8. using (SqliteConnection con = new SqliteConnection(cs))
  9. {
  10. con.Open();
  11. using(SqliteTransaction tr = con.BeginTransaction())
  12. {
  13. using (SqliteCommand cmd = con.CreateCommand())
  14. {
  15. cmd.Transaction = tr;
  16. cmd.CommandText = "UPDATE Friends SET Name='Thomas' WHERE Id=1";
  17. cmd.ExecuteNonQuery();
  18. cmd.CommandText = "UPDATE Friend SET Name='Bob' WHERE Id=4";
  19. cmd.ExecuteNonQuery();
  20. }
  21. tr.Commit();
  22. }
  23. con.Close();
  24. }
  25. }
  26. }

在代码示例中,我们要更改两个名称。 有两个语句构成一个事务。 第二个 SQL 语句中有错误。 因此,该事务将回滚。

  1. cmd.CommandText = "UPDATE Friend SET Name='Bob' WHERE Id=4";

表名称不正确。 数据库中没有Friend表。

  1. $ mono trans_error.exe
  2. Unhandled Exception: Mono.Data.Sqlite.SqliteException: SQLite error
  3. no such table: Friend
  4. ...

运行示例将显示此错误消息。 事务回滚。

  1. sqlite> SELECT * FROM Friends;
  2. 1|Tom
  3. 2|Rebecca
  4. 3|Jim
  5. 4|Robert
  6. 5|Julian
  7. 6|Jane

Friends表中没有发生任何变化。 即使第一个UPDATE语句正确。

我们将再次尝试更改两行; 这次不使用SqliteTransaction

  1. using System;
  2. using Mono.Data.Sqlite;
  3. public class Example
  4. {
  5. static void Main()
  6. {
  7. string cs = "URI=file:test.db";
  8. using (SqliteConnection con = new SqliteConnection(cs))
  9. {
  10. con.Open();
  11. using (SqliteCommand cmd = con.CreateCommand())
  12. {
  13. cmd.CommandText = "UPDATE Friends SET Name='Thomas' WHERE Id=1";
  14. cmd.ExecuteNonQuery();
  15. cmd.CommandText = "UPDATE Friend SET Name='Bob' WHERE Id=4";
  16. cmd.ExecuteNonQuery();
  17. }
  18. con.Close();
  19. }
  20. }
  21. }

我们尝试更新Friends表中的两个名称,汤姆(Tom)到托马斯(Thomas)和罗伯特(Robert)到鲍勃(Bob)。

  1. cmd.CommandText = "UPDATE Friend SET Name='Bob' WHERE Id=4";
  2. cmd.ExecuteNonQuery();

UPDATE语句不正确。

  1. $ mono notrans_error.exe
  2. Unhandled Exception: Mono.Data.Sqlite.SqliteException: SQLite error
  3. no such table: Friend

我们收到与上一个示例相同的错误消息。

  1. sqlite> SELECT * FROM Friends;
  2. 1|Thomas
  3. 2|Rebecca
  4. 3|Jim
  5. 4|Robert
  6. 5|Julian
  7. 6|Jane

但是这一次,第一个UPDATE语句被保存。 第二个不是。

在 SQLite C# 教程的这一部分中,我们处理了事务。