diff --git a/global.json b/global.json index b3fcac52..d5bf446d 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ -{ - "sdk": { - "version": "9.0.100", - "rollForward": "latestFeature" - } +{ + "sdk": { + "version": "9.0.100", + "rollForward": "latestFeature" + } } \ No newline at end of file diff --git a/src/SQLite.cs b/src/SQLite.cs index 72525c56..a5d36375 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -25,6 +25,7 @@ using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; #if NET8_0_OR_GREATER @@ -41,6 +42,7 @@ #endif using System.Text; using System.Threading; +using ExecutionEngineException = System.ExecutionEngineException; #if USE_CSHARP_SQLITE using Sqlite3 = Community.CsharpSqlite.Sqlite3; @@ -2632,6 +2634,14 @@ void OnTableChanged (TableMapping table, NotifyTableChangedAction action) } public event EventHandler TableChanged; + + public static void RegisterFastColumnSetter ( + Type type, + string name, + Action setter) + { + FastColumnSetter.RegisterFastColumnSetter (type, name, setter); + } } public class NotifyTableChangedEventArgs : EventArgs @@ -3592,7 +3602,17 @@ public IEnumerable ExecuteDeferredQuery (TableMapping map) continue; if (fastColumnSetters[i] != null) { - fastColumnSetters[i].Invoke (obj, stmt, i); + try { + fastColumnSetters[i].Invoke (obj, stmt, i); + } +#pragma warning disable CS0618 // Type or member is obsolete + catch (ExecutionEngineException) { +#pragma warning restore CS0618 // Type or member is obsolete + // Column setter has AOT Problem so don't use it. + fastColumnSetters[i] = null; + Trace.WriteLine($"FastColumnSetter AOT Jit Exception on Type {map.MappedType.FullName} Column {cols[i].Name}"); + i--; // go one back and read it with default implementation + } } else { var colType = SQLite3.ColumnType (stmt, i); @@ -3922,6 +3942,14 @@ object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clr internal class FastColumnSetter { + private static ConcurrentDictionary<(Type, string), Action> customSetter = + new ConcurrentDictionary<(Type, string), Action> (); + + public static void RegisterFastColumnSetter(Type type, string name, Action setter) + { + customSetter[(type, name)] = setter; + } + /// /// Gets a for a generic method, suppressing AOT warnings. /// @@ -3954,7 +3982,9 @@ internal static MethodInfo GetFastSetterMethodInfoUnsafe (Type mappedType) /// internal static Action GetFastSetter (SQLiteConnection conn, TableMapping.Column column) { - Action fastSetter = null; + if (customSetter.TryGetValue ((typeof(T), column.Name), out var fastSetter)) { + return fastSetter; + } Type clrType = column.PropertyInfo.PropertyType; diff --git a/tests/SQLite.Tests/FastColumnSetterTest.cs b/tests/SQLite.Tests/FastColumnSetterTest.cs new file mode 100644 index 00000000..66efa0ba --- /dev/null +++ b/tests/SQLite.Tests/FastColumnSetterTest.cs @@ -0,0 +1,115 @@ +using System; +using System.Linq; +#if NETFX_CORE +using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using SetUp = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestInitializeAttribute; +using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute; +using Test = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestMethodAttribute; +#else +using NUnit.Framework; +#endif + +namespace SQLite.Tests +{ + [TestFixture] + public class FastColumnSetterTest + { + public class TestSetter + { + [AutoIncrement, PrimaryKey] + public int Id { get; set; } + + public string Data { get; set; } + + public DateTime Date { get; set; } + } + + public class TestDb : SQLiteConnection + { + public TestDb (String path) + : base (path) + { + CreateTable (); + } + } + + [Test] + public void SetFastColumnSetters_AndReadData() + { + FastColumnSetter.RegisterFastColumnSetter( + typeof(TestSetter), + nameof(TestSetter.Id), + (obj, stmt, index) => { ((TestSetter)obj).Id = SQLite3.ColumnInt(stmt, index); }); + + FastColumnSetter.RegisterFastColumnSetter ( + typeof (TestSetter), + nameof (TestSetter.Data), + (obj, stmt, index) => { ((TestSetter)obj).Data = SQLite3.ColumnString (stmt, index); }); + + FastColumnSetter.RegisterFastColumnSetter ( + typeof (TestSetter), + nameof (TestSetter.Date), + (obj, stmt, index) => { ((TestSetter)obj).Date = new DateTime (SQLite3.ColumnInt64 (stmt, index)); }); + + var n = 20; + var cq = from i in Enumerable.Range (1, n) + select new TestSetter { + Data = Convert.ToString (i), + Date = new DateTime (2013, 1, i) + }; + + var db = new TestDb (TestPath.GetTempFileName ()); + db.InsertAll (cq); + + var results = db.Table ().Where (o => o.Data.Equals ("10")); + Assert.AreEqual (results.Count (), 1); + Assert.AreEqual (results.FirstOrDefault ().Data, "10"); + } + + [Test] + public void SetFastColumnSetters_AndReadData_IsCalled() + { + int callCount = 0; + + FastColumnSetter.RegisterFastColumnSetter ( + typeof (TestSetter), + nameof (TestSetter.Id), + (obj, stmt, index) => { + ((TestSetter)obj).Id = SQLite3.ColumnInt (stmt, index); + callCount++; + }); + + FastColumnSetter.RegisterFastColumnSetter ( + typeof (TestSetter), + nameof (TestSetter.Data), + (obj, stmt, index) => { + ((TestSetter)obj).Data = SQLite3.ColumnString (stmt, index); + callCount++; + }); + + FastColumnSetter.RegisterFastColumnSetter ( + typeof (TestSetter), + nameof (TestSetter.Date), + (obj, stmt, index) => { + ((TestSetter)obj).Date = new DateTime (SQLite3.ColumnInt64 (stmt, index)); + callCount++; + }); + + var n = 20; + var cq = from i in Enumerable.Range (1, n) + select new TestSetter { + Data = Convert.ToString (i), + Date = new DateTime (2013, 1, i) + }; + + var db = new TestDb (TestPath.GetTempFileName ()); + db.InsertAll (cq); + + var results = db.Table ().Where (o => o.Data.Equals ("10")); + Assert.AreEqual (results.Count (), 1); + Assert.AreEqual (results.FirstOrDefault ().Data, "10"); + + Assert.IsTrue(callCount > 0); + } + } +} diff --git a/tests/SQLite.Tests/SQLite.Tests.csproj b/tests/SQLite.Tests/SQLite.Tests.csproj index 5c14b15d..d44f218a 100644 --- a/tests/SQLite.Tests/SQLite.Tests.csproj +++ b/tests/SQLite.Tests/SQLite.Tests.csproj @@ -1,4 +1,4 @@ - + net8.0