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..bb4c97a41c5 100644
--- a/src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs
+++ b/src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs
@@ -113,6 +113,74 @@ public override void Rollback()
RollbackInternal();
}
+#if NET
+ ///
+ public override bool SupportsSavepoints => true;
+
+ ///
+ public override void Save(string savepointName)
+ {
+ if (savepointName == null)
+ {
+ throw new ArgumentNullException(nameof(savepointName));
+ }
+
+ if (string.IsNullOrWhiteSpace(savepointName))
+ {
+ throw new ArgumentException($"{nameof(savepointName)} can't be empty", nameof(savepointName));
+ }
+
+ if (_completed || _connection.State != ConnectionState.Open)
+ {
+ throw new InvalidOperationException(Resources.TransactionCompleted);
+ }
+
+ _connection.ExecuteNonQuery("SAVEPOINT " + savepointName);
+ }
+
+ ///
+ public override void Rollback(string savepointName)
+ {
+ if (savepointName == null)
+ {
+ throw new ArgumentNullException(nameof(savepointName));
+ }
+
+ if (string.IsNullOrWhiteSpace(savepointName))
+ {
+ throw new ArgumentException($"{nameof(savepointName)} can't be empty", nameof(savepointName));
+ }
+
+ if (_completed || _connection.State != ConnectionState.Open)
+ {
+ throw new InvalidOperationException(Resources.TransactionCompleted);
+ }
+
+ _connection.ExecuteNonQuery("ROLLBACK TO " + savepointName);
+ }
+
+ ///
+ public override void Release(string savepointName)
+ {
+ if (savepointName == null)
+ {
+ throw new ArgumentNullException(nameof(savepointName));
+ }
+
+ if (string.IsNullOrWhiteSpace(savepointName))
+ {
+ throw new ArgumentException($"{nameof(savepointName)} can't be empty", nameof(savepointName));
+ }
+
+ if (_completed || _connection.State != ConnectionState.Open)
+ {
+ throw new InvalidOperationException(Resources.TransactionCompleted);
+ }
+
+ _connection.ExecuteNonQuery("RELEASE SAVEPOINT " + savepointName);
+ }
+#endif
+
///
/// 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..1ad8095fd77 100644
--- a/test/Microsoft.Data.Sqlite.Tests/SqliteTransactionTest.cs
+++ b/test/Microsoft.Data.Sqlite.Tests/SqliteTransactionTest.cs
@@ -356,6 +356,28 @@ public void Dispose_can_be_called_more_than_once()
}
}
+#if NET
+ [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"));
+ }
+#endif
+
private static void CreateTestTable(SqliteConnection connection)
{
connection.ExecuteNonQuery(