From 01269e01038aeb2cb7f85dc6294df8f9024bac08 Mon Sep 17 00:00:00 2001 From: Antony Corbett Date: Wed, 23 Mar 2022 18:48:24 +0000 Subject: [PATCH] Use the recommended bulk insert method for Sqlite. Added test for 100,000 items. SetBulk() --- SqliteCache.Tests/BulkInsertTests.cs | 29 ++++++++++++++ SqliteCache/SqliteCache.cs | 60 +++++++++++++++++++++++++--- 2 files changed, 84 insertions(+), 5 deletions(-) diff --git a/SqliteCache.Tests/BulkInsertTests.cs b/SqliteCache.Tests/BulkInsertTests.cs index b88973f..84e91cb 100755 --- a/SqliteCache.Tests/BulkInsertTests.cs +++ b/SqliteCache.Tests/BulkInsertTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; @@ -124,5 +125,33 @@ public async Task MultipleBulkCalls() CollectionAssert.AreEqual(item2, DefaultEncoding.GetBytes("test two")); } } + + [TestMethod] + public void LargeScaleBulkTests() + { + using (var cache = CreateDefault(true)) + { + const int count = 100_000; + + var testObject = new List>(); + for (var n = 0; n < count; n++) + { + testObject.Add(new KeyValuePair($"item{n+1}", DefaultEncoding.GetBytes($"value{n+1}"))); + } + + cache.SetBulk(testObject, new DistributedCacheEntryOptions + { + AbsoluteExpiration = DateTimeOffset.UtcNow.AddDays(1) + }); + + var value1 = cache.Get("item1"); + Assert.IsNotNull(value1); + CollectionAssert.AreEqual(DefaultEncoding.GetBytes("value1"), value1); + + var lastValue = cache.Get($"item{count}"); + Assert.IsNotNull(lastValue); + CollectionAssert.AreEqual(DefaultEncoding.GetBytes($"value{count}"), lastValue); + } + } } } diff --git a/SqliteCache/SqliteCache.cs b/SqliteCache/SqliteCache.cs index 35a1791..621109d 100755 --- a/SqliteCache/SqliteCache.cs +++ b/SqliteCache/SqliteCache.cs @@ -390,11 +390,16 @@ private void CreateForSet(DbCommand cmd, string key, byte[] value, DistributedCa cmd.Parameters.AddWithValue("@key", key); cmd.Parameters.AddWithValue("@value", value); - AddExpirationParameters(cmd, options); + var expirationValues = GetExpirationValues(options); + + cmd.Parameters.AddWithValue("@expiry", expirationValues.expiry?.Ticks ?? (object)DBNull.Value); + cmd.Parameters.AddWithValue("@renewal", expirationValues.renewal?.Ticks ?? (object)DBNull.Value); } private void CreateBulkInsert(DbCommand cmd, IEnumerable> keyValues, DistributedCacheEntryOptions options) { + var expirationValues = GetExpirationValues(options); + StringBuilder sb = new StringBuilder(); sb.AppendLine(DbCommands.Commands[(int)Operation.BulkInsert]); int i = 0; @@ -408,12 +413,13 @@ private void CreateBulkInsert(DbCommand cmd, IEnumerable> keyValues, Di }); } + public void SetBulk(IEnumerable> keyValues, DistributedCacheEntryOptions options, + CancellationToken cancel = default) + { + var expirationValues = GetExpirationValues(options); + var expiryParamValue = expirationValues.expiry?.Ticks ?? (object) DBNull.Value; + var renewalParamValue = expirationValues.renewal?.Ticks ?? (object)DBNull.Value; + + var keyValuesArray = keyValues.ToArray(); // prevent possible multiple enumeration + + Commands.Use(Operation.BulkInsert, cmd => + { + using var transaction = cmd.Connection?.BeginTransaction(); + cmd.Transaction = transaction; + cmd.CommandText += " ($key, $val, $expiry, $renewal);"; + + var keyParam = cmd.CreateParameter(); + keyParam.ParameterName = "$key"; + cmd.Parameters.Add(keyParam); + + var valParam = cmd.CreateParameter(); + valParam.ParameterName = "$val"; + cmd.Parameters.Add(valParam); + + var expiryParam = cmd.CreateParameter(); + expiryParam.ParameterName = "$expiry"; + cmd.Parameters.Add(expiryParam); + + var renewalParam = cmd.CreateParameter(); + renewalParam.ParameterName = "$renewal"; + cmd.Parameters.Add(renewalParam); + + foreach (var row in keyValuesArray) + { + keyParam.Value = row.Key; + valParam.Value = row.Value; + expiryParam.Value = expiryParamValue; + renewalParam.Value = renewalParamValue; + + cmd.ExecuteNonQuery(); + } + + transaction?.Commit(); + }); + } + public void RemoveExpired() { var removed = (long) Commands.Use(Operation.RemoveExpired, cmd =>