From afc32b539c9d850c7784af6251d34dd3c2dc5151 Mon Sep 17 00:00:00 2001 From: Dagalon <63908589+Dagalon@users.noreply.github.com> Date: Mon, 6 Jul 2026 06:09:19 +0200 Subject: [PATCH 1/3] Tighten DuckDB SQL handling --- DuckDB-Memory-Lib/ConnectionManager.cs | 20 +++++----- DuckDB-Memory-Lib/DuckDB-Memory-Lib.csproj | 4 +- DuckDB-Memory-Lib/DuckTools.cs | 4 +- DuckDB-Memory-Lib/QueryExecutor.cs | 43 ++++++++++++++++------ DuckDB-Memory-Tests/T_DuckTools.cs | 26 ++++++++++++- DuckDB-Memory-Tests/T_Execute_Queries.cs | 18 ++++++++- 6 files changed, 88 insertions(+), 27 deletions(-) diff --git a/DuckDB-Memory-Lib/ConnectionManager.cs b/DuckDB-Memory-Lib/ConnectionManager.cs index 5a2f800..d589239 100644 --- a/DuckDB-Memory-Lib/ConnectionManager.cs +++ b/DuckDB-Memory-Lib/ConnectionManager.cs @@ -1,4 +1,4 @@ -using System.Data; +using System.Data; using DuckDB.NET.Data; @@ -23,7 +23,7 @@ public static ConnectionManager GetInstance() } /// - /// Retrieves an open SQLite connection identified by the provided alias, creating it when necessary. + /// Retrieves an open DuckDB connection identified by the provided alias, creating it when necessary. /// public DuckDBConnection GetConnection(string? alias = null, string? path = null) { @@ -72,7 +72,7 @@ public void CloseConnection(string? alias = null) } /// - /// Closes and disposes all active SQLite connections managed by this instance. + /// Closes and disposes all active DuckDB connections managed by this instance. /// public void CloseAllConnections() { @@ -95,7 +95,7 @@ public void CloseAllConnections() } /// - /// Opens the provided SQLite connection when it is not already open. + /// Opens the provided DuckDB connection when it is not already open. /// private static void EnsureOpen(DuckDBConnection connection) { @@ -182,11 +182,13 @@ public static void Register(string path, string idDataBase) /// public static void DeleteRegister(string idDataBase) { - var keepIdDataBases = _mapIdDataBase.Values.ToList(); - int indexValues = keepIdDataBases.IndexOf(idDataBase); - string pathIdDataBase = _mapIdDataBase.Keys.ToList()[indexValues]; - - _mapIdDataBase.Remove(pathIdDataBase); + var pathIdDataBase = _mapIdDataBase + .FirstOrDefault(entry => string.Equals(entry.Value, idDataBase, StringComparison.OrdinalIgnoreCase)) + .Key; + if (pathIdDataBase is not null) + { + _mapIdDataBase.Remove(pathIdDataBase); + } } } diff --git a/DuckDB-Memory-Lib/DuckDB-Memory-Lib.csproj b/DuckDB-Memory-Lib/DuckDB-Memory-Lib.csproj index 55d0e02..367e3eb 100644 --- a/DuckDB-Memory-Lib/DuckDB-Memory-Lib.csproj +++ b/DuckDB-Memory-Lib/DuckDB-Memory-Lib.csproj @@ -1,4 +1,4 @@ - + DuckDb_Memory_Lib @@ -7,7 +7,7 @@ - + diff --git a/DuckDB-Memory-Lib/DuckTools.cs b/DuckDB-Memory-Lib/DuckTools.cs index 59df907..12d7d57 100644 --- a/DuckDB-Memory-Lib/DuckTools.cs +++ b/DuckDB-Memory-Lib/DuckTools.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using DuckDB.NET.Data; using DuckDB.NET.Data.DataChunk.Reader; using DuckDB.NET.Data.DataChunk.Writer; @@ -177,7 +177,7 @@ public static EnumsDuckMemory.Output AttachedDataBase(DuckDBConnection db, strin } else { - attachedQry = $"ATTACH '{strConnection}' AS \"{aliasDataBase}\" "; + attachedQry = $"ATTACH '{strConnection}' AS {QuoteIdentifier(aliasDataBase)}"; } try diff --git a/DuckDB-Memory-Lib/QueryExecutor.cs b/DuckDB-Memory-Lib/QueryExecutor.cs index c3c0693..9f670a8 100644 --- a/DuckDB-Memory-Lib/QueryExecutor.cs +++ b/DuckDB-Memory-Lib/QueryExecutor.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using DuckDB.NET.Data; using System.IO; @@ -30,15 +30,16 @@ public static EnumsDuckMemory.Output CreateTable(DuckDBConnection db, string idD for (var i = 0; i < noFields; i++) { var t = NetTypeToDuckDbType.GetDuckDbType(types[i]); - fieldsDefinition.Add(headers[i] + " " + t); + fieldsDefinition.Add(DuckTools.QuoteIdentifier(headers[i]) + " " + t); } } try { - var qry = $"CREATE TABLE IF NOT EXISTS {idDataBase}.{idTable}({string.Join(",", fieldsDefinition)})"; - var cmd = new DuckDBCommand(qry, db); + var tableName = $"{DuckTools.QuoteIdentifier(idDataBase)}.{DuckTools.QuoteIdentifier(idTable)}"; + var qry = $"CREATE TABLE IF NOT EXISTS {tableName}({string.Join(",", fieldsDefinition)})"; + using var cmd = new DuckDBCommand(qry, db); cmd.ExecuteNonQuery(); return EnumsDuckMemory.Output.SUCCESS; @@ -71,7 +72,7 @@ public static EnumsDuckMemory.Output CreateParquetTable(DuckDBConnection db, str var tableName = $"{DuckTools.QuoteIdentifier(idDataBase)}.{DuckTools.QuoteIdentifier(idTable)}"; var parquetPath = parquetPathFile.Replace("'", "''"); var qry = $"CREATE OR REPLACE TABLE {tableName} AS SELECT * FROM '{parquetPath}';"; - var cmd = new DuckDBCommand(qry, db); + using var cmd = new DuckDBCommand(qry, db); cmd.ExecuteNonQuery(); return EnumsDuckMemory.Output.SUCCESS; @@ -90,8 +91,8 @@ public static (EnumsDuckMemory.Output, List>) Execute { try { - var cmd = new DuckDBCommand(qry, db); - var qryResult = cmd.ExecuteReader(); + using var cmd = new DuckDBCommand(qry, db); + using var qryResult = cmd.ExecuteReader(); var resultList = new List>(); @@ -104,7 +105,6 @@ public static (EnumsDuckMemory.Output, List>) Execute } } - qryResult.Close(); return (EnumsDuckMemory.Output.SUCCESS, resultList); } catch (Exception ex) @@ -121,14 +121,33 @@ public static (EnumsDuckMemory.Output, List>) Execute { try { - qry = parameters.Keys.Aggregate(qry, - (current, param) => current.Replace(param, parameters[param], StringComparison.OrdinalIgnoreCase)); - return ExecuteQryReader(db, qry); + using var cmd = new DuckDBCommand(qry, db); + foreach (var parameter in parameters) + { + var dbParameter = cmd.CreateParameter(); + dbParameter.ParameterName = parameter.Key; + dbParameter.Value = parameter.Value; + cmd.Parameters.Add(dbParameter); + } + + using var qryResult = cmd.ExecuteReader(); + var resultList = new List>(); + + if (qryResult.HasRows) + { + while (qryResult.Read()) + { + resultList.Add(Enumerable.Range(0, qryResult.FieldCount) + .ToDictionary(qryResult.GetName, qryResult.GetValue)); + } + } + + return (EnumsDuckMemory.Output.SUCCESS, resultList); } catch (Exception ex) { Debug.WriteLine(ex); - throw new Exception($"{ex.Message}-{EnumsDuckMemory.Output.ERROR_TO_EXECUTE_QUERY}", ex); + return (EnumsDuckMemory.Output.ERROR_TO_EXECUTE_QUERY, []); } } } \ No newline at end of file diff --git a/DuckDB-Memory-Tests/T_DuckTools.cs b/DuckDB-Memory-Tests/T_DuckTools.cs index 4ca40a9..9af0ca4 100644 --- a/DuckDB-Memory-Tests/T_DuckTools.cs +++ b/DuckDB-Memory-Tests/T_DuckTools.cs @@ -1,4 +1,4 @@ -using DuckDb_Memory_Lib; +using DuckDb_Memory_Lib; namespace DuckDB_Memory_Tests; @@ -88,6 +88,30 @@ public void T_GetListTables_And_DropTable_Handle_Existing_And_Missing_Tables() }); } + + [Test] + public void T_DeleteRegister_Ignores_Missing_Id() + { + Assert.DoesNotThrow(() => KeeperRegisterIdDataBase.DeleteRegister($"MISSING_{Guid.NewGuid():N}")); + } + + [Test] + public void T_CreateTable_Quotes_Database_And_Table_Identifiers() + { + var databaseId = $"TOOLS DB {Guid.NewGuid():N}"; + var tableId = $"TOOLS TABLE {Guid.NewGuid():N}"; + var conn = ConnectionManager.GetInstance().GetConnection(databaseId); + + var dbOutput = DuckTools.CreateDatabase(conn, databaseId, null); + Assert.That(dbOutput, Is.EqualTo(EnumsDuckMemory.Output.SUCCESS)); + + var createOutput = QueryExecutor.CreateTable(conn, databaseId, tableId, ["id INTEGER"]); + Assert.That(createOutput, Is.EqualTo(EnumsDuckMemory.Output.SUCCESS)); + + var listTables = DuckTools.GetListTables(conn, databaseId); + Assert.That(listTables.Item2, Does.Contain(tableId)); + } + [Test] public void T_DeleteDataBase_Detaches_Existing_Database_And_Validates_Blank_Id() { diff --git a/DuckDB-Memory-Tests/T_Execute_Queries.cs b/DuckDB-Memory-Tests/T_Execute_Queries.cs index 9158797..9109969 100644 --- a/DuckDB-Memory-Tests/T_Execute_Queries.cs +++ b/DuckDB-Memory-Tests/T_Execute_Queries.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using DuckDb_Memory_Lib; namespace DuckDB_Memory_Tests; @@ -41,4 +41,20 @@ public void T_Create_Table_From_Parquet() Assert.That(resultQry.Item1, Is.EqualTo(EnumsDuckMemory.Output.SUCCESS)); } + [Test] + public void T_ExecuteQryReader_Uses_DuckDb_Parameters() + { + var conn = ConnectionManager.GetInstance().GetConnection($"PARAM_DB_{Guid.NewGuid():N}"); + var result = QueryExecutor.ExecuteQryReader( + conn, + "SELECT @value AS VALUE", + new Dictionary { ["@value"] = "O'Reilly" }); + + Assert.Multiple(() => + { + Assert.That(result.Item1, Is.EqualTo(EnumsDuckMemory.Output.SUCCESS)); + Assert.That(result.Item2.Single()["VALUE"], Is.EqualTo("O'Reilly")); + }); + } + } From fecedd849c8c192c8c00f375a428c24e6b3cb1f0 Mon Sep 17 00:00:00 2001 From: Dagalon <63908589+Dagalon@users.noreply.github.com> Date: Mon, 6 Jul 2026 06:14:29 +0200 Subject: [PATCH 2/3] Share connection manager implementation --- DuckDB-Memory-Lib/ConnectionManager.cs | 96 +----------------- DuckDB-Memory-Lib/DuckDB-Memory-Lib.csproj | 4 + Shared/ConnectionManagerBase.cs | 98 +++++++++++++++++++ SqliteDB-Memory-Lib/ConnectionManager.cs | 96 +----------------- .../SqliteDB-Memory-Lib.csproj | 4 + 5 files changed, 116 insertions(+), 182 deletions(-) create mode 100644 Shared/ConnectionManagerBase.cs diff --git a/DuckDB-Memory-Lib/ConnectionManager.cs b/DuckDB-Memory-Lib/ConnectionManager.cs index d589239..aa55ece 100644 --- a/DuckDB-Memory-Lib/ConnectionManager.cs +++ b/DuckDB-Memory-Lib/ConnectionManager.cs @@ -1,17 +1,14 @@ -using System.Data; using DuckDB.NET.Data; +using MemoryDb_Lib.Shared; namespace DuckDb_Memory_Lib; -public sealed class ConnectionManager +public sealed class ConnectionManager : ConnectionManagerBase { private static readonly Lazy LazyInstance = new(() => new ConnectionManager(), LazyThreadSafetyMode.ExecutionAndPublication); - private readonly object _syncRoot = new(); - private readonly Dictionary _connections = new(StringComparer.OrdinalIgnoreCase); - private ConnectionManager() { } /// @@ -27,94 +24,11 @@ public static ConnectionManager GetInstance() /// public DuckDBConnection GetConnection(string? alias = null, string? path = null) { - var normalizedAlias = NormalizeAlias(alias); - - lock (_syncRoot) - { - if (_connections.TryGetValue(normalizedAlias, out var existingConnection)) - { - EnsureOpen(existingConnection); - return existingConnection; - } - - var newConnection = DuckTools.GetInstance(path); - EnsureOpen(newConnection); - _connections[normalizedAlias] = newConnection; - - return newConnection; - } - } - - /// - /// Closes and disposes the connection associated with the provided alias. - /// - public void CloseConnection(string? alias = null) - { - var normalizedAlias = NormalizeAlias(alias); - - lock (_syncRoot) - { - if (!_connections.TryGetValue(normalizedAlias, out var connection)) - { - return; - } - - try - { - connection.Close(); - } - finally - { - connection.Dispose(); - _connections.Remove(normalizedAlias); - } - } + return GetConnectionCore(alias, path, DuckTools.GetInstance); } /// - /// Closes and disposes all active DuckDB connections managed by this instance. - /// - public void CloseAllConnections() - { - lock (_syncRoot) - { - foreach (var connection in _connections.Values) - { - try - { - connection.Close(); - } - finally - { - connection.Dispose(); - } - } - - _connections.Clear(); - } - } - - /// - /// Opens the provided DuckDB connection when it is not already open. - /// - private static void EnsureOpen(DuckDBConnection connection) - { - if (connection.State != ConnectionState.Open) - { - connection.Open(); - } - } - - /// - /// Normalizes aliases to ensure consistent lookups inside the connection dictionary. - /// - private static string NormalizeAlias(string? alias) - { - return string.IsNullOrWhiteSpace(alias) ? "default" : alias.Trim(); - } - - /// - /// Static helper that delegates to . + /// Static helper that delegates to . /// public static void Close(string? alias = null) { @@ -128,7 +42,7 @@ public static void CloseAll() { GetInstance().CloseAllConnections(); } - } +} public sealed class KeeperRegisterIdDataBase { diff --git a/DuckDB-Memory-Lib/DuckDB-Memory-Lib.csproj b/DuckDB-Memory-Lib/DuckDB-Memory-Lib.csproj index 367e3eb..2729815 100644 --- a/DuckDB-Memory-Lib/DuckDB-Memory-Lib.csproj +++ b/DuckDB-Memory-Lib/DuckDB-Memory-Lib.csproj @@ -10,4 +10,8 @@ + + + + diff --git a/Shared/ConnectionManagerBase.cs b/Shared/ConnectionManagerBase.cs new file mode 100644 index 0000000..d2fba45 --- /dev/null +++ b/Shared/ConnectionManagerBase.cs @@ -0,0 +1,98 @@ +using System.Data; +using System.Data.Common; + +namespace MemoryDb_Lib.Shared; + +/// +/// Thread-safe base implementation for named database connection managers. +/// +public abstract class ConnectionManagerBase where TConnection : DbConnection +{ + private readonly object _syncRoot = new(); + private readonly Dictionary _connections = new(StringComparer.OrdinalIgnoreCase); + + /// + /// Retrieves an open connection identified by the provided alias, creating it when necessary. + /// + protected TConnection GetConnectionCore(string? alias, string? path, Func connectionFactory) + { + var normalizedAlias = NormalizeAlias(alias); + + lock (_syncRoot) + { + if (_connections.TryGetValue(normalizedAlias, out var existingConnection)) + { + EnsureOpen(existingConnection); + return existingConnection; + } + + var newConnection = connectionFactory(path); + EnsureOpen(newConnection); + _connections[normalizedAlias] = newConnection; + + return newConnection; + } + } + + /// + /// Closes and disposes the connection associated with the provided alias. + /// + public void CloseConnection(string? alias = null) + { + var normalizedAlias = NormalizeAlias(alias); + + lock (_syncRoot) + { + if (!_connections.TryGetValue(normalizedAlias, out var connection)) + { + return; + } + + try + { + connection.Close(); + } + finally + { + connection.Dispose(); + _connections.Remove(normalizedAlias); + } + } + } + + /// + /// Closes and disposes all active connections managed by this instance. + /// + public void CloseAllConnections() + { + lock (_syncRoot) + { + foreach (var connection in _connections.Values) + { + try + { + connection.Close(); + } + finally + { + connection.Dispose(); + } + } + + _connections.Clear(); + } + } + + private static void EnsureOpen(TConnection connection) + { + if (connection.State != ConnectionState.Open) + { + connection.Open(); + } + } + + private static string NormalizeAlias(string? alias) + { + return string.IsNullOrWhiteSpace(alias) ? "default" : alias.Trim(); + } +} diff --git a/SqliteDB-Memory-Lib/ConnectionManager.cs b/SqliteDB-Memory-Lib/ConnectionManager.cs index be61eeb..9fd0065 100644 --- a/SqliteDB-Memory-Lib/ConnectionManager.cs +++ b/SqliteDB-Memory-Lib/ConnectionManager.cs @@ -1,16 +1,13 @@ using Microsoft.Data.Sqlite; -using System.Data; +using MemoryDb_Lib.Shared; namespace SqliteDB_Memory_Lib; -public sealed class ConnectionManager +public sealed class ConnectionManager : ConnectionManagerBase { private static readonly Lazy LazyInstance = new(() => new ConnectionManager(), LazyThreadSafetyMode.ExecutionAndPublication); - private readonly object _syncRoot = new(); - private readonly Dictionary _connections = new(StringComparer.OrdinalIgnoreCase); - private ConnectionManager() { } /// @@ -26,94 +23,11 @@ public static ConnectionManager GetInstance() /// public SqliteConnection GetConnection(string? alias = null, string? path = null) { - var normalizedAlias = NormalizeAlias(alias); - - lock (_syncRoot) - { - if (_connections.TryGetValue(normalizedAlias, out var existingConnection)) - { - EnsureOpen(existingConnection); - return existingConnection; - } - - var newConnection = SqLiteLiteTools.GetInstance(path); - EnsureOpen(newConnection); - _connections[normalizedAlias] = newConnection; - - return newConnection; - } - } - - /// - /// Closes and disposes the connection associated with the provided alias. - /// - public void CloseConnection(string? alias = null) - { - var normalizedAlias = NormalizeAlias(alias); - - lock (_syncRoot) - { - if (!_connections.TryGetValue(normalizedAlias, out var connection)) - { - return; - } - - try - { - connection.Close(); - } - finally - { - connection.Dispose(); - _connections.Remove(normalizedAlias); - } - } + return GetConnectionCore(alias, path, SqLiteLiteTools.GetInstance); } /// - /// Closes and disposes all active SQLite connections managed by this instance. - /// - public void CloseAllConnections() - { - lock (_syncRoot) - { - foreach (var connection in _connections.Values) - { - try - { - connection.Close(); - } - finally - { - connection.Dispose(); - } - } - - _connections.Clear(); - } - } - - /// - /// Opens the provided SQLite connection when it is not already open. - /// - private static void EnsureOpen(SqliteConnection connection) - { - if (connection.State != ConnectionState.Open) - { - connection.Open(); - } - } - - /// - /// Normalizes aliases to ensure consistent lookups inside the connection dictionary. - /// - private static string NormalizeAlias(string? alias) - { - return string.IsNullOrWhiteSpace(alias) ? "default" : alias.Trim(); - } - - /// - /// Static helper that delegates to . + /// Static helper that delegates to . /// public static void Close(string? alias = null) { @@ -127,7 +41,7 @@ public static void CloseAll() { GetInstance().CloseAllConnections(); } - } +} public sealed class KeeperRegisterIdDataBase { diff --git a/SqliteDB-Memory-Lib/SqliteDB-Memory-Lib.csproj b/SqliteDB-Memory-Lib/SqliteDB-Memory-Lib.csproj index 457f1f0..afe9a85 100644 --- a/SqliteDB-Memory-Lib/SqliteDB-Memory-Lib.csproj +++ b/SqliteDB-Memory-Lib/SqliteDB-Memory-Lib.csproj @@ -9,4 +9,8 @@ + + + + From 85f77638ec5e71a4cc9d3dec63d98517c62537e0 Mon Sep 17 00:00:00 2001 From: Dagalon <63908589+Dagalon@users.noreply.github.com> Date: Tue, 7 Jul 2026 08:45:00 +0200 Subject: [PATCH 3/3] Upgrade DuckDb and partial fied bug --- Directory.Build.props | 2 +- DuckDB-Memory-Lib/DuckTools.cs | 3 +- DuckDB-Memory-Lib/DuckTypes.cs | 60 +++++++++++++++++++++++- DuckDB-Memory-Lib/QueryExecutor.cs | 4 +- DuckDB-Memory-Tests/T_Execute_Queries.cs | 4 +- 5 files changed, 66 insertions(+), 7 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 2986c46..f85aa3d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -20,6 +20,6 @@ - + diff --git a/DuckDB-Memory-Lib/DuckTools.cs b/DuckDB-Memory-Lib/DuckTools.cs index 12d7d57..1defec4 100644 --- a/DuckDB-Memory-Lib/DuckTools.cs +++ b/DuckDB-Memory-Lib/DuckTools.cs @@ -128,8 +128,7 @@ public static EnumsDuckMemory.Output RegisterScalarFunction( { db.RegisterScalarFunction( idFunction, - func, - isPureFunction: true); + func); return EnumsDuckMemory.Output.SUCCESS; } diff --git a/DuckDB-Memory-Lib/DuckTypes.cs b/DuckDB-Memory-Lib/DuckTypes.cs index 1ec8a1e..c118211 100644 --- a/DuckDB-Memory-Lib/DuckTypes.cs +++ b/DuckDB-Memory-Lib/DuckTypes.cs @@ -1,4 +1,5 @@ -using DuckDB.NET.Native; +using System.Data; +using DuckDB.NET.Native; namespace DuckDb_Memory_Lib; @@ -76,4 +77,61 @@ public static (object? Value, Type NetType) StrTryParse(string? value) // Fallback → VARCHAR return (value, typeof(string)); } + + /// + /// Maps a .NET type to the closest ADO.NET DbType. + /// The resulting DbType can be used by DuckDB.NET to infer the native DuckDB type. + /// + public static DbType GetDbType(Type type) + { + type = Nullable.GetUnderlyingType(type) ?? type; + + if (type == typeof(bool)) + return DbType.Boolean; + + if (type == typeof(byte)) + return DbType.Byte; + + if (type == typeof(short)) + return DbType.Int16; + + if (type == typeof(int)) + return DbType.Int32; + + if (type == typeof(long)) + return DbType.Int64; + + if (type == typeof(float)) + return DbType.Single; + + if (type == typeof(double)) + return DbType.Double; + + if (type == typeof(decimal)) + return DbType.Decimal; + + if (type == typeof(DateOnly)) + return DbType.Date; + + if (type == typeof(TimeOnly)) + return DbType.Time; + + if (type == typeof(DateTime)) + return DbType.DateTime; + + if (type == typeof(Guid)) + return DbType.Guid; + + if (type == typeof(byte[])) + return DbType.Binary; + + if (type == typeof(string)) + return DbType.String; + + throw new NotSupportedException( + $"The .NET type '{type.FullName}' cannot be mapped to a DbType."); + } } + + + diff --git a/DuckDB-Memory-Lib/QueryExecutor.cs b/DuckDB-Memory-Lib/QueryExecutor.cs index 9f670a8..46ce7b7 100644 --- a/DuckDB-Memory-Lib/QueryExecutor.cs +++ b/DuckDB-Memory-Lib/QueryExecutor.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using DuckDB.NET.Data; using System.IO; +using DuckDB.NET.Native; namespace DuckDb_Memory_Lib; @@ -127,9 +128,10 @@ public static (EnumsDuckMemory.Output, List>) Execute var dbParameter = cmd.CreateParameter(); dbParameter.ParameterName = parameter.Key; dbParameter.Value = parameter.Value; + dbParameter.DbType = NetTypeToDuckDbType.GetDbType(parameter.Value.GetType()); cmd.Parameters.Add(dbParameter); } - + using var qryResult = cmd.ExecuteReader(); var resultList = new List>(); diff --git a/DuckDB-Memory-Tests/T_Execute_Queries.cs b/DuckDB-Memory-Tests/T_Execute_Queries.cs index 9109969..52e2937 100644 --- a/DuckDB-Memory-Tests/T_Execute_Queries.cs +++ b/DuckDB-Memory-Tests/T_Execute_Queries.cs @@ -47,8 +47,8 @@ public void T_ExecuteQryReader_Uses_DuckDb_Parameters() var conn = ConnectionManager.GetInstance().GetConnection($"PARAM_DB_{Guid.NewGuid():N}"); var result = QueryExecutor.ExecuteQryReader( conn, - "SELECT @value AS VALUE", - new Dictionary { ["@value"] = "O'Reilly" }); + "SELECT $value AS VALUE", + new Dictionary { ["value"] = "O'Reilly" }); Assert.Multiple(() => {