diff --git a/src/Microsoft.Data.Sqlite.Core/Microsoft.Data.Sqlite.Core.csproj b/src/Microsoft.Data.Sqlite.Core/Microsoft.Data.Sqlite.Core.csproj
index 027520179af..a44c5d0d09c 100644
--- a/src/Microsoft.Data.Sqlite.Core/Microsoft.Data.Sqlite.Core.csproj
+++ b/src/Microsoft.Data.Sqlite.Core/Microsoft.Data.Sqlite.Core.csproj
@@ -15,7 +15,7 @@ Microsoft.Data.Sqlite.SqliteException
Microsoft.Data.Sqlite.SqliteFactory
Microsoft.Data.Sqlite.SqliteParameter
Microsoft.Data.Sqlite.SqliteTransaction
- netstandard2.0
+ netstandard2.0;net5.0
3.6
true
Microsoft.Data.Sqlite.Core.ruleset
diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs b/src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs
index cf6cb9a3201..945b9c2edef 100644
--- a/src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs
+++ b/src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs
@@ -4,6 +4,7 @@
using System;
using System.Data;
using System.Data.Common;
+using System.Text;
using Microsoft.Data.Sqlite.Properties;
using static SQLitePCL.raw;
@@ -113,6 +114,97 @@ public override void Rollback()
RollbackInternal();
}
+#if NET
+ ///
+ public override bool SupportsSavepoints => true;
+#endif
+
+ ///
+ /// Creates a savepoint in the transaction. This allows all commands that are executed after the savepoint was
+ /// established to be rolled back, restoring the transaction state to what it was at the time of the savepoint.
+ ///
+ /// The name of the savepoint to be created.
+#if NET
+ public override void Save(string savepointName)
+#else
+ public void Save(string savepointName)
+#endif
+ {
+ if (savepointName is null)
+ {
+ throw new ArgumentNullException(nameof(savepointName));
+ }
+
+ if (_completed || _connection.State != ConnectionState.Open)
+ {
+ throw new InvalidOperationException(Resources.TransactionCompleted);
+ }
+
+ _connection.ExecuteNonQuery(
+ new StringBuilder()
+ .Append("SAVEPOINT \"")
+ .Append(savepointName.Replace("\"", "\"\""))
+ .Append("\";")
+ .ToString());
+ }
+
+ ///
+ /// Rolls back all commands that were executed after the specified savepoint was established.
+ ///
+ /// The name of the savepoint to roll back to.
+#if NET
+ public override void Rollback(string savepointName)
+#else
+ public void Rollback(string savepointName)
+#endif
+ {
+ if (savepointName is null)
+ {
+ throw new ArgumentNullException(nameof(savepointName));
+ }
+
+ if (_completed || _connection.State != ConnectionState.Open)
+ {
+ throw new InvalidOperationException(Resources.TransactionCompleted);
+ }
+
+ _connection.ExecuteNonQuery(
+ new StringBuilder()
+ .Append("ROLLBACK TO SAVEPOINT \"")
+ .Append(savepointName.Replace("\"", "\"\""))
+ .Append("\";")
+ .ToString());
+ }
+
+ ///
+ /// Destroys a savepoint previously defined in the current transaction. This allows the system to
+ /// reclaim some resources before the transaction ends.
+ ///
+ /// The name of the savepoint to release.
+#if NET
+ public override void Release(string savepointName)
+#else
+ public void Release(string savepointName)
+#endif
+ {
+ if (savepointName is null)
+ {
+ throw new ArgumentNullException(nameof(savepointName));
+ }
+
+ if (_completed || _connection.State != ConnectionState.Open)
+ {
+ throw new InvalidOperationException(Resources.TransactionCompleted);
+ }
+
+ _connection.ExecuteNonQuery(
+ new StringBuilder()
+ .Append("RELEASE SAVEPOINT \"")
+ .Append(savepointName.Replace("\"", "\"\""))
+ .Append("\";")
+ .ToString());
+ }
+
///
/// Releases any resources used by the transaction and rolls it back.
///
diff --git a/test/Microsoft.Data.Sqlite.Tests/SqliteTransactionTest.cs b/test/Microsoft.Data.Sqlite.Tests/SqliteTransactionTest.cs
index 522234b0e9e..9e33003fbe5 100644
--- a/test/Microsoft.Data.Sqlite.Tests/SqliteTransactionTest.cs
+++ b/test/Microsoft.Data.Sqlite.Tests/SqliteTransactionTest.cs
@@ -356,6 +356,26 @@ public void Dispose_can_be_called_more_than_once()
}
}
+ [Fact]
+ public void Savepoint()
+ {
+ using var connection = new SqliteConnection("Data Source=:memory:");
+ connection.Open();
+ CreateTestTable(connection);
+
+ var transaction = connection.BeginTransaction();
+ transaction.Save("MySavepointName");
+
+ connection.ExecuteNonQuery("INSERT INTO TestTable (TestColumn) VALUES (8)");
+ Assert.Equal(1L, connection.ExecuteScalar("SELECT COUNT(*) FROM TestTable;"));
+
+ transaction.Rollback("MySavepointName");
+ Assert.Equal(0L, connection.ExecuteScalar("SELECT COUNT(*) FROM TestTable;"));
+
+ transaction.Release("MySavepointName");
+ Assert.Throws(() => transaction.Rollback("MySavepointName"));
+ }
+
private static void CreateTestTable(SqliteConnection connection)
{
connection.ExecuteNonQuery(