Resolving PlatformNotSupportedException in .NET Native Toolchain Builds with SQLite
Issue Overview: Native Toolchain AOT Compilation Breaks SQLite Reflection
The core problem arises when attempting to deploy Xamarin/UWP applications using System.Data.SQLite v1.0.118 through the .NET Native Toolchain (Ahead-of-Time compilation). The runtime throws System.PlatformNotSupportedException
at UnsafeNativeMethods.CheckAssemblyCodeBase
, specifically failing when accessing Assembly.CodeBase
during native library pre-loading. This occurs exclusively in AOT-compiled builds (enabled via <UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
) but works in standard JIT-based debug configurations. The conflict stems from fundamental differences between reflection capabilities in JIT vs. AOT environments, particularly around assembly metadata accessibility.
Possible Causes: AOT Restrictions and Unresolved Reflection Metadata
Three primary factors converge to create this failure:
AOT Compilation’s Reflection Limitations
The .NET Native Toolchain aggressively optimizes binaries by stripping unused metadata. Reflection-heavy operations likeAssembly.CodeBase
require runtime metadata that isn’t preserved by default in AOT.System.Data.SQLite
uses reflection to locate native binaries by inspecting the executing assembly’s codebase path, which becomes unresolvable when metadata is trimmed.Incomplete Runtime Directives in Default.rd.xml
The UWP project’sDefault.rd.xml
file controls metadata preservation for reflection. Missing directives forSystem.Data.SQLite
or its dependencies prevent the AOT compiler from retaining critical type/method metadata. TheCheckAssemblyCodeBase
method attempts reflection on the assembly without guidance to preserveCodeBase
-related metadata.Version-Specific SQLite Interop Behavior
OlderSystem.Data.SQLite
versions (pre-1.0.117) have known issues with UWP’s AOT due to hardcoded paths and reflection patterns incompatible with .NET Native. While v1.0.118 includes UWP fixes, improper interop configuration (e.g., missing architecture-specific native binaries) can still trigger fallback logic that relies on reflection.
Troubleshooting Steps, Solutions & Fixes: Metadata Preservation and Native Binary Configuration
Step 1: Enforce Reflection Metadata Preservation
Modify Default.rd.xml
to explicitly preserve metadata for SQLite assemblies:
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Application>
<!-- Preserve all methods/types in SQLite assembly -->
<Assembly Name="System.Data.SQLite" Dynamic="Required All" />
<!-- Ensure RuntimeAssembly type metadata is retained -->
<Type Name="System.Reflection.RuntimeAssembly" Serialize="Required Public" />
<!-- Allow access to CodeBase property -->
<Type Name="System.Reflection.Assembly" >
<Property Name="CodeBase" Dynamic="Required" />
</Type>
</Application>
</Directives>
This forces the AOT compiler to retain reflection metadata for CodeBase
and all System.Data.SQLite
members. Validate by checking the ILLink
output for metadata trimming warnings.
Step 2: Verify Native Binary Deployment for AOT
AOT environments require explicit inclusion of architecture-specific SQLite native binaries. Ensure the UWP project includes:
sqlite3.dll
forx86
,x64
, andARM64
in respective output directories- Correct
Content
andCopyToOutputDirectory
settings in.csproj
:
<ItemGroup>
<Content Include="..\packages\SQLite.x64\build\native\bin\x64\sqlite3.dll">
<Link>sqlite3.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<!-- Repeat for x86/ARM64 -->
</ItemGroup>
Step 3: Bypass Reflection-Based Native Loading
Override SQLite’s default native library resolution by manually specifying the path before initializing connections:
SQLitePCL.Batteries_V2.Init();
SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_e_sqlite3());
This uses explicit initialization from SQLitePCLRaw
to avoid reflection-based discovery. Requires adding SQLitePCLRaw.bundle_e_sqlite3
NuGet package.
Step 4: Upgrade to AOT-Compatible SQLite Packages
Migrate from System.Data.SQLite
to Microsoft.Data.Sqlite
(maintained by .NET Foundation) with better UWP/AOT support:
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.10" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.6" />
Rewrite data access code to use Microsoft.Data.Sqlite
APIs, which avoid reflection for native binary resolution.
Step 5: Debug AOT Metadata Trimming with ILLink
Enable detailed trimming logs to identify missing metadata:
<PropertyGroup>
<IlcGenerateStackTraceData>true</IlcGenerateStackTraceData>
<IlcTrimAnalysis>true</IlcTrimAnalysis>
</PropertyGroup>
Inspect build output for warnings about trimmed types/methods related to System.Data.SQLite
. Add targeted <Type>
/<Method>
directives in Default.rd.xml
for any missing elements.
Step 6: Validate Assembly Loading Context in AOT
Override assembly resolution to log loading attempts:
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => {
Debug.WriteLine($"Resolving {args.Name}");
return null;
};
Check debug logs for failed assembly resolves, particularly for SQLite.Interop.dll
or architecture-specific variants. Ensure all dependencies are included as Content
.
Final Validation
Test the AOT build using:
- Local deployment to a Windows device via Visual Studio
Release
configuration with.NET Native Toolchain
enabled- ARM64/x64 architecture settings matching store submission requirements
Monitor first-chance exceptions in Visual Studio (Debug > Windows > Exception Settings) to catch any residual reflection issues. If PlatformNotSupportedException
persists, use dumpbin /exports
on the compiled .exe
to confirm SQLite native exports are present.