diff --git a/Directory.Build.props b/Directory.Build.props
index d00d035c..a6a75b3a 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -39,7 +39,12 @@
- ManagedOnly
+ ManagedOnly
+ 1.5.5
+
+ $(MSBuildThisFileDirectory)artifacts\natives\$(DuckDbVersion)\
This package does not include a copy of the native DuckDB library.
diff --git a/DuckDB.NET.Benchmarks/Benchmarks.csproj b/DuckDB.NET.Benchmarks/Benchmarks.csproj
index 2de0564f..8ced6dcd 100644
--- a/DuckDB.NET.Benchmarks/Benchmarks.csproj
+++ b/DuckDB.NET.Benchmarks/Benchmarks.csproj
@@ -20,14 +20,4 @@
-
-
-
- false
- PreserveNewest
- runtimes\%(RecursiveDir)\%(FileName)%(Extension)
-
-
-
diff --git a/DuckDB.NET.Benchmarks/NativeLibraryLoader.cs b/DuckDB.NET.Benchmarks/NativeLibraryLoader.cs
deleted file mode 100644
index 429de245..00000000
--- a/DuckDB.NET.Benchmarks/NativeLibraryLoader.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-namespace DuckDB.NET.Benchmarks;
-
-///
-/// Loads the platform-native DuckDB library for the benchmark process.
-///
-internal static class NativeLibraryLoader
-{
- [ModuleInitializer]
- public static void Init()
- {
- if (GetRid() is not { } rid)
- {
- return;
- }
-
- _ = NativeLibrary.TryLoad(Path.Join("runtimes", rid, "native", "duckdb"), Assembly.GetExecutingAssembly(), DllImportSearchPath.AssemblyDirectory, out _) ||
- NativeLibrary.TryLoad(Path.Join("runtimes", rid, "native", "libduckdb"), Assembly.GetExecutingAssembly(), DllImportSearchPath.AssemblyDirectory, out _);
- }
-
- private static string? GetRid()
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- return Environment.Is64BitProcess ? "win-x64" : "win-x86";
- }
-
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- {
- return RuntimeInformation.ProcessArchitecture switch
- {
- Architecture.X64 => "linux-x64",
- Architecture.Arm64 => "linux-arm64",
- _ => null,
- };
- }
-
- if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- return "osx";
- }
-
- return null;
- }
-}
diff --git a/DuckDB.NET.Bindings/Bindings.csproj b/DuckDB.NET.Bindings/Bindings.csproj
index 3ba02773..d99b9617 100644
--- a/DuckDB.NET.Bindings/Bindings.csproj
+++ b/DuckDB.NET.Bindings/Bindings.csproj
@@ -3,7 +3,7 @@
DuckDB Bindings for C#.
-- Updated to DuckDB v1.5.5
+- Updated to DuckDB v$(DuckDbVersion)
New features:
- Added bindings for the Arrow C Data Interface (duckdb_to_arrow_schema, duckdb_data_chunk_to_arrow, duckdb_result_get_arrow_options)
@@ -11,7 +11,7 @@ New features:
DuckDB.NET.Native
win-x64;win-arm64;linux-x64;linux-arm64;osx
- https://github.com/duckdb/duckdb/releases/download/v1.5.5
+ https://github.com/duckdb/duckdb/releases/download/v$(DuckDbVersion)
True
..\keyPair.snk
true
@@ -23,7 +23,16 @@ New features:
-
+
+
+
+ <_PackDuckDbNatives Condition="'$(BuildType)' == 'Full' ">true
+ <_PackDuckDbNatives Condition="'$(_PackDuckDbNatives)' == ''">false
+
+
+
<_NativeLib Include="win-x64" LibUrl="$(DuckDbArtifactRoot)/libduckdb-windows-amd64.zip" />
<_NativeLib Include="win-arm64" LibUrl="$(DuckDbArtifactRoot)/libduckdb-windows-arm64.zip" />
@@ -36,20 +45,29 @@ New features:
<_NativeLib Include="linux-x64" LibUrl="$(DuckDbArtifactRoot)/duckdb-binaries-linux-amd64.zip" InnerZipName="libduckdb-linux-amd64.zip" Condition="$([MSBuild]::IsOSPlatform('Linux'))" />
<_NativeLib Include="osx" LibUrl="$(DuckDbArtifactRoot)/duckdb-binaries-osx.zip" InnerZipName="libduckdb-osx-universal.zip" Condition="$([MSBuild]::IsOSPlatform('OSX'))" />
-
+
-
+
-
-
+
+
+
false
PreserveNewest
- true
+ $(_PackDuckDbNatives)
\runtimes
- runtimes\%(RecursiveDir)\%(FileName)%(Extension)
-
+ runtimes\%(RecursiveDir)%(FileName)%(Extension)
+
+
+
+
diff --git a/DuckDB.NET.Bindings/DownloadNativeLibs.targets b/DuckDB.NET.Bindings/DownloadNativeLibs.targets
index 0a73e20c..0c89ee23 100644
--- a/DuckDB.NET.Bindings/DownloadNativeLibs.targets
+++ b/DuckDB.NET.Bindings/DownloadNativeLibs.targets
@@ -1,47 +1,77 @@
-
-
-
-
-
- $(MSBuildProjectDirectory)\obj\runtimes\$(Rid)\native.zip
-
-
-
-
-
-
+
+
+
+ $(DuckDbNativeRoot)$(Rid)
+ $(RidDir)\native
+ $(RidDir)\native.zip
+ $(RidDir)\temp
+ $(RidDir)\duckdb.inputs.cache
+ $(RidDir)\duckdb.download.cache
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
- $(MSBuildProjectDirectory)\obj\runtimes\$(Rid)\native.zip
- $(MSBuildProjectDirectory)\obj\runtimes\$(Rid)\temp
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DuckDB.NET.Bindings/DuckDBNativeLibrary.cs b/DuckDB.NET.Bindings/DuckDBNativeLibrary.cs
new file mode 100644
index 00000000..9fa0a3a9
--- /dev/null
+++ b/DuckDB.NET.Bindings/DuckDBNativeLibrary.cs
@@ -0,0 +1,74 @@
+using System.IO;
+using System.Reflection;
+
+namespace DuckDB.NET.Native;
+
+///
+/// Resolves the native DuckDB library from the runtimes folder beside this assembly.
+///
+///
+/// A NuGet package writes a runtimeTargets entry into deps.json and the host probes
+/// runtimes/{rid}/native on its own. A project reference produces no such entry, so without this
+/// resolver every project that consumes DuckDB.NET by reference has to load the library itself.
+///
+internal static class DuckDBNativeLibrary
+{
+ // CA2255 warns against ModuleInitializer in a library. Registering a DllImport resolver is the
+ // case the rule exempts: the resolver has to be in place before the first P/Invoke, and those
+ // live on nested types whose static constructors the outer type cannot hook.
+#pragma warning disable CA2255
+ [ModuleInitializer]
+#pragma warning restore CA2255
+ internal static void Register() =>
+ NativeLibrary.SetDllImportResolver(typeof(DuckDBNativeLibrary).Assembly, Resolve);
+
+ private static IntPtr Resolve(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
+ {
+ if (libraryName != NativeMethods.DuckDbLibrary || GetRuntimeIdentifier() is not { } rid)
+ {
+ return IntPtr.Zero;
+ }
+
+ // The four-argument overload applies the platform naming convention, so "duckdb" matches
+ // libduckdb.dylib, libduckdb.so and duckdb.dll. The two-argument one needs an exact path.
+ // Passing a relative path keeps this working under single-file publish, where
+ // Assembly.Location is empty.
+ foreach (var candidate in (string[])["duckdb", "libduckdb"])
+ {
+ var path = Path.Combine("runtimes", rid, "native", candidate);
+
+ if (NativeLibrary.TryLoad(path, assembly, DllImportSearchPath.AssemblyDirectory, out var handle))
+ {
+ return handle;
+ }
+ }
+
+ // Zero hands the name back to the default probing, so a library already on the system or
+ // resolved from a NuGet package still loads.
+ return IntPtr.Zero;
+ }
+
+ ///
+ /// These identifiers name the folders the build stages, not the full RID graph. DuckDB ships one
+ /// universal macOS binary, so that folder is "osx" rather than osx-x64 and osx-arm64.
+ ///
+ private static string? GetRuntimeIdentifier()
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return Environment.Is64BitProcess ? "win-x64" : "win-x86";
+ }
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ return RuntimeInformation.ProcessArchitecture switch
+ {
+ Architecture.X64 => "linux-x64",
+ Architecture.Arm64 => "linux-arm64",
+ _ => null,
+ };
+ }
+
+ return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "osx" : null;
+ }
+}
diff --git a/DuckDB.NET.Bindings/NativeMethods/NativeMethods.All.cs b/DuckDB.NET.Bindings/NativeMethods/NativeMethods.All.cs
index 51183c92..aa935918 100644
--- a/DuckDB.NET.Bindings/NativeMethods/NativeMethods.All.cs
+++ b/DuckDB.NET.Bindings/NativeMethods/NativeMethods.All.cs
@@ -2,7 +2,7 @@
public static partial class NativeMethods
{
- private const string DuckDbLibrary = "duckdb";
+ internal const string DuckDbLibrary = "duckdb";
//Grouped according to https://duckdb.org/docs/archive/0.8.1/api/c/api
}
\ No newline at end of file
diff --git a/DuckDB.NET.Data/Data.csproj b/DuckDB.NET.Data/Data.csproj
index 15b11e3c..5096979a 100644
--- a/DuckDB.NET.Data/Data.csproj
+++ b/DuckDB.NET.Data/Data.csproj
@@ -3,7 +3,7 @@
DuckDB ADO.NET Provider for C#.
-- Updated to DuckDB v1.5.5
+- Updated to DuckDB v$(DuckDbVersion)
New features:
- Apache Arrow result streaming via DuckDBCommand.ExecuteArrowStream and ExecuteArrowBatchesAsync (#26)
diff --git a/DuckDB.NET.Samples/Program.cs b/DuckDB.NET.Samples/Program.cs
index f575c49c..0d1f6297 100644
--- a/DuckDB.NET.Samples/Program.cs
+++ b/DuckDB.NET.Samples/Program.cs
@@ -1,7 +1,6 @@
using Dapper;
using DuckDB.NET.Data;
using DuckDB.NET.Native;
-using DuckDB.NET.Test.Helpers;
using System;
using System.Data.Common;
using System.Diagnostics;
@@ -15,12 +14,6 @@ class Program
{
static void Main(string[] args)
{
- if (!NativeLibraryHelper.TryLoad())
- {
- Console.Error.WriteLine("native assembly not found");
- return;
- }
-
DapperSample();
AdoNetSamples();
diff --git a/DuckDB.NET.Samples/Samples.csproj b/DuckDB.NET.Samples/Samples.csproj
index c22e1f3e..ad364db8 100644
--- a/DuckDB.NET.Samples/Samples.csproj
+++ b/DuckDB.NET.Samples/Samples.csproj
@@ -9,10 +9,6 @@
Full
-
-
-
-
diff --git a/DuckDB.NET.Test/Helpers/NativeLibraryHelper.cs b/DuckDB.NET.Test/Helpers/NativeLibraryHelper.cs
deleted file mode 100644
index 80cb8411..00000000
--- a/DuckDB.NET.Test/Helpers/NativeLibraryHelper.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using System;
-using System.IO;
-using System.Reflection;
-using System.Runtime.InteropServices;
-
-namespace DuckDB.NET.Test.Helpers;
-
-static class NativeLibraryHelper
-{
- public static bool TryLoad()
- {
- if (GetRid() is not { } rid)
- {
- return false;
- }
-
- return NativeLibrary.TryLoad(Path.Join("runtimes", rid, "native", "duckdb"), Assembly.GetExecutingAssembly(), DllImportSearchPath.AssemblyDirectory, out _) ||
- NativeLibrary.TryLoad(Path.Join("runtimes", rid, "native", "libduckdb"), Assembly.GetExecutingAssembly(), DllImportSearchPath.AssemblyDirectory, out _);
- }
-
- private static string GetRid()
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- return Environment.Is64BitProcess ? "win-x64" : "win-x86";
- }
-
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- {
- return RuntimeInformation.ProcessArchitecture switch
- {
- Architecture.X64 => "linux-x64",
- Architecture.Arm64 => "linux-arm64",
- _ => null,
- };
- }
-
- if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- return "osx";
- }
-
- return null;
- }
-}
\ No newline at end of file
diff --git a/DuckDB.NET.Test/ModuleInit.cs b/DuckDB.NET.Test/ModuleInit.cs
index a9b77c3e..230f8901 100644
--- a/DuckDB.NET.Test/ModuleInit.cs
+++ b/DuckDB.NET.Test/ModuleInit.cs
@@ -1,5 +1,4 @@
using System.Runtime.CompilerServices;
-using DuckDB.NET.Test.Helpers;
#nullable enable
namespace DuckDB.NET.Test;
@@ -8,7 +7,6 @@ public static class ModuleInit
[ModuleInitializer]
public static void Init()
{
- NativeLibraryHelper.TryLoad();
AssertionOptions.AssertEquivalencyUsing(options => options.Using(new DateTimeOffsetTimeComparer()));
}
diff --git a/DuckDB.NET.Test/Test.csproj b/DuckDB.NET.Test/Test.csproj
index 37ced068..9eb1d118 100644
--- a/DuckDB.NET.Test/Test.csproj
+++ b/DuckDB.NET.Test/Test.csproj
@@ -26,16 +26,4 @@
-
-
-
-
-
- false
- PreserveNewest
- \runtimes
- runtimes\%(RecursiveDir)\%(FileName)%(Extension)
-
-
-
diff --git a/README.md b/README.md
index ea5f8b02..181d1832 100644
--- a/README.md
+++ b/README.md
@@ -80,6 +80,18 @@ using var duckDBConnection = new DuckDBConnection("DataSource=md:{your_database}
If you want to build DuckDB extensions with C#, see [Giorgi/DuckDB.ExtensionKit](https://github.com/Giorgi/DuckDB.ExtensionKit).
+## Updating the DuckDB version
+
+Set `DuckDbVersion` in `Directory.Build.props`:
+
+```xml
+1.5.5
+```
+
+That property drives the download URL, the staging path, the download cache, and the release notes in both packages. Build once and the new engine downloads.
+
+Each version is staged in its own folder under `artifacts/natives/`, so a previous version stays on disk and switching back downloads nothing. To try a version without editing the file, pass `-p:DuckDbVersion=1.4.1` to `dotnet build`.
+
## Known Issues
When debugging your project that uses DuckDB.NET library, you may get the following error: **System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt**. The error happens due to debugger interaction with the native memory. For a workaround check out [Debugger Options mess up debugging session during Marshalling