@@ -23,6 +23,9 @@ namespace Microsoft.Data.Sqlite
2323 /// <seealso href="https://docs.microsoft.com/dotnet/standard/data/sqlite/async">Async Limitations</seealso>
2424 public partial class SqliteConnection : DbConnection
2525 {
26+ private static readonly bool UseOldBehavior35715 =
27+ AppContext . TryGetSwitch ( "Microsoft.EntityFrameworkCore.Issue35715" , out var enabled35715 ) && enabled35715 ;
28+
2629 internal const string MainDatabaseName = "main" ;
2730
2831 private const int SQLITE_WIN32_DATA_DIRECTORY_TYPE = 1 ;
@@ -48,6 +51,8 @@ public partial class SqliteConnection : DbConnection
4851 private static readonly StateChangeEventArgs _fromClosedToOpenEventArgs = new StateChangeEventArgs ( ConnectionState . Closed , ConnectionState . Open ) ;
4952 private static readonly StateChangeEventArgs _fromOpenToClosedEventArgs = new StateChangeEventArgs ( ConnectionState . Open , ConnectionState . Closed ) ;
5053
54+ private static string [ ] ? NativeDllSearchDirectories ;
55+
5156 static SqliteConnection ( )
5257 {
5358 Type . GetType ( "SQLitePCL.Batteries_V2, SQLitePCLRaw.batteries_v2" )
@@ -624,11 +629,82 @@ public virtual void LoadExtension(string file, string? proc = null)
624629
625630 private void LoadExtensionCore ( string file , string ? proc )
626631 {
627- var rc = sqlite3_load_extension ( Handle , utf8z . FromString ( file ) , utf8z . FromString ( proc ) , out var errmsg ) ;
628- if ( rc != SQLITE_OK )
632+ if ( UseOldBehavior35715 )
633+ {
634+ var rc = sqlite3_load_extension ( Handle , utf8z . FromString ( file ) , utf8z . FromString ( proc ) , out var errmsg ) ;
635+ if ( rc != SQLITE_OK )
636+ {
637+ throw new SqliteException ( Resources . SqliteNativeError ( rc , errmsg . utf8_to_string ( ) ) , rc , rc ) ;
638+ }
639+ }
640+ else
641+ {
642+ SqliteException ? firstException = null ;
643+ foreach ( var path in GetLoadExtensionPaths ( file ) )
644+ {
645+ var rc = sqlite3_load_extension ( Handle , utf8z . FromString ( path ) , utf8z . FromString ( proc ) , out var errmsg ) ;
646+ if ( rc == SQLITE_OK )
647+ {
648+ return ;
649+ }
650+
651+ if ( firstException == null )
652+ {
653+ // We store the first exception so that error message looks more obvious if file appears in there
654+ firstException = new SqliteException ( Resources . SqliteNativeError ( rc , errmsg . utf8_to_string ( ) ) , rc , rc ) ;
655+ }
656+ }
657+
658+ if ( firstException != null )
659+ {
660+ throw firstException ;
661+ }
662+ }
663+ }
664+
665+ private static IEnumerable < string > GetLoadExtensionPaths ( string file )
666+ {
667+ // we always try original input first
668+ yield return file ;
669+
670+ string ? dirName = Path . GetDirectoryName ( file ) ;
671+
672+ // we don't try to guess directories for user, if they pass a path either absolute or relative - they're on their own
673+ if ( ! string . IsNullOrEmpty ( dirName ) )
629674 {
630- throw new SqliteException ( Resources . SqliteNativeError ( rc , errmsg . utf8_to_string ( ) ) , rc , rc ) ;
675+ yield break ;
631676 }
677+
678+ bool shouldTryAddingLibPrefix = ! file . StartsWith ( "lib" , StringComparison . Ordinal ) && ! RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ;
679+
680+ if ( shouldTryAddingLibPrefix )
681+ {
682+ yield return $ "lib{ file } ";
683+ }
684+
685+ NativeDllSearchDirectories ??= GetNativeDllSearchDirectories ( ) ;
686+
687+ foreach ( string dir in NativeDllSearchDirectories )
688+ {
689+ yield return Path . Combine ( dir , file ) ;
690+
691+ if ( shouldTryAddingLibPrefix )
692+ {
693+ yield return Path . Combine ( dir , $ "lib{ file } ") ;
694+ }
695+ }
696+ }
697+
698+ private static string [ ] GetNativeDllSearchDirectories ( )
699+ {
700+ string ? searchDirs = AppContext . GetData ( "NATIVE_DLL_SEARCH_DIRECTORIES" ) as string ;
701+
702+ if ( string . IsNullOrEmpty ( searchDirs ) )
703+ {
704+ return [ ] ;
705+ }
706+
707+ return searchDirs ! . Split ( [ Path . PathSeparator ] , StringSplitOptions . RemoveEmptyEntries ) ;
632708 }
633709
634710 /// <summary>
0 commit comments