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:

  1. AOT Compilation’s Reflection Limitations
    The .NET Native Toolchain aggressively optimizes binaries by stripping unused metadata. Reflection-heavy operations like Assembly.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.

  2. Incomplete Runtime Directives in Default.rd.xml
    The UWP project’s Default.rd.xml file controls metadata preservation for reflection. Missing directives for System.Data.SQLite or its dependencies prevent the AOT compiler from retaining critical type/method metadata. The CheckAssemblyCodeBase method attempts reflection on the assembly without guidance to preserve CodeBase-related metadata.

  3. Version-Specific SQLite Interop Behavior
    Older System.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 for x86, x64, and ARM64 in respective output directories
  • Correct Content and CopyToOutputDirectory 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:

  1. Local deployment to a Windows device via Visual Studio
  2. Release configuration with .NET Native Toolchain enabled
  3. 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.

Related Guides

Leave a Reply

Your email address will not be published. Required fields are marked *