ericsink / SQLitePCL.raw

A Portable Class Library (PCL) for low-level (raw) access to SQLite
Apache License 2.0
523 stars 109 forks source link

DllNotFoundException: Unable to load DLL 'e_sqlite3' in "Prism modular" application #433

Closed james1301 closed 2 years ago

james1301 commented 3 years ago

Hi I am trying to write a modular application that uses a sqlite db but I am getting an error when trying to access the db as it seems the project is not directly linked to the main exe project.

If I add a reference to sqlite in my main project it works, or if I add a reference to the project it works but this is breaking the modularity of the application as I want it to be free from knowing the database layer.

I am using .net 5.0. Here is my sample app that duplicates the problem: ModularAppSQLiteProblem.zip

Error: `System.TypeInitializationException HResult=0x80131534 Message=The type initializer for 'Microsoft.Data.Sqlite.SqliteConnection' threw an exception. Source=Microsoft.Data.Sqlite StackTrace: at Microsoft.Data.Sqlite.SqliteConnection..ctor(String connectionString) at Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal.SqliteRelationalConnection.CreateDbConnection() at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.get_DbConnection() at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.Open(Boolean errorsExpected) at Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal.SqliteDatabaseCreator.Exists() at Microsoft.EntityFrameworkCore.Storage.RelationalDatabaseCreator.EnsureDeleted() at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.EnsureDeleted() at ModularAppSQLiteProblem.Modules.ModuleName.Repository.Initialise() in C:\Users\james.baker\source\repos\ModularAppSQLiteProblem\ModularAppSQLiteProblem\Modules\ModularAppSQLiteProblem.Modules.ModuleName\Repository.cs:line 21 at ModularAppSQLiteProblem.Modules.ViewModels.ModuleName.DataBaseViewModel..ctor(IRepository repository) in C:\Users\james.baker\source\repos\ModularAppSQLiteProblem\ModularAppSQLiteProblem\Modules\ModularAppSQLiteProblem.Modules.ModuleName\ViewModels\DataBaseViewModel.cs:line 18

Inner Exception 1: TargetInvocationException: Exception has been thrown by the target of an invocation.

Inner Exception 2: DllNotFoundException: Unable to load DLL 'e_sqlite3' or one of its dependencies: The specified module could not be found. (0x8007007E) `

bricelam commented 3 years ago

Possibly related to #252

james1301 commented 3 years ago

Probably @bricelam, shame nothing has happened in 2 and a half years though 😬

ericsink commented 3 years ago

I tried running the repro sample and it just brings up an empty window, no exception thrown. How do I get the error you posted? I'm not familiar with Prism.

james1301 commented 3 years ago

@ericsink sorry, you might have to make sure you rebuild the solution, because the module project is so loosely coupled I don't think it will have built and so it will not be trying to access sqlite. Once the module is built then it should be there for it to be picked up when PRISM initialises. As long as the ModuleNameModule.OnInitialized() is called it should then error.

james1301 commented 3 years ago

@ericsink updated original sample with correct project dependencies so it should build correctly now.

ericsink commented 3 years ago

Still nothing.

I unzip the repro file. I tried building and running from Visual Studio and from the command line. In both cases, the main window comes up empty, no errors.

You're saying that if you unzip your repro zip file to a clean directory and then build and run it, you get the exception? If so, please explain the exact steps.

james1301 commented 3 years ago

Hmmmm @ericsink yeah I've just tried it. Have you re-downloaded from my original post?

If you put a breakpoint in ModuleNameModule.OnInitialized()? Does it hit. As long as that gets hit it should error. If it isn't being hit, then possibly try a rebuild on the solution to make sure everything builds. I am doing this from VS.

ericsink commented 3 years ago

Yes, I re-downloaded. And verified by running diff on the two directories.

Yes, I tried a rebuild.

I just added the breakpoint and it does hit. So I looked again for the exception and eventually found it.

I'll attempt a fix and see what happens.

ericsink commented 3 years ago

Update:

All I'm trying to do here is setup a simple dev loop where I (1) repro the problem, (2) make a change to use buildTransitive (aka #252), and (3) try again to see if the problem can still repro.

But I have not yet been to repro this with a tree I can currently develop in, for various reasons.

The repro app from OP uses net5.0 with M.D.Sqlite (and friends) at 5.0.9. If I just add PackageReference for my 2.0.5-pre development versions, I get MissingMethodExceptions, as 5.0.9 is referencing things I have deleted. This raises questions about whether 2.0.5 is going to be compatible with EFCore 5.x stuff, which is a separate issue.

I restored those missing methods in my dev tree, but I can't quite get the build to stop trying to include 2.0.4 stuff. Not sure why. It's complaining that the provider (with a 2.0.4.x version number) has no implemented of the soft_heap_limit function which was added after 2.0.4. So it's clearly got some 2.0.5 and 2.0.4 stuff mixed up. PackageReference with 2.0.4 and ExcludeAssets="all" didn't help.

So instead, I tried updating the repro app to 6.0, because I know 2.0.5 development tree works with EFCore 6.0-pre stuff. Now the repro app won't work with Visual Studio 2019, which means I would need to install VS 2022 preview.

So instead I tried just using the command line, but the repro app is swallowing the exception, so I can't tell if the problem is happening or not.

So instead I tried using the repro app unmodified from the zip file, do a build, and then hackishly use buildTransitive by modifying the layout of the files in the nuget package cache for 2.0.4. At this point I realized that the SQLitePCLRaw.lib.e_sqlite3 nuget package is only using the build folder for net461 for the props and targets files, and the actual e_sqlite3 binary. So the buildTransitive folder should make no difference to a net5.0 app anyway. Which would mean that maybe #252 isn't relevant here.

But oh, wait, 2.0.4 didn't actually target net5.0, and the warning message says the packages are getting restored using net461, which means the buildTransitive folder would be in play after all. But 2.0.5 shouldn't have that problem, since it multitargets net5.0.

So I did the package cache hack anyway and renamed the build folder to buildTransitive, which didn't make a difference in the repro app. Not sure why, but the failure is happening in the dynamic provider, which is another thing that wouldn't be used with 2.0.5 in this case.

So I cleared the package cache (again), and did a git checkout of my dev trees back in time to when 2.0.4 was released, made a change to use buildTransitive, and rebuilt. The repro app here still has the same problem. The only difference now is that my test suite fails.

Summary (for now):

I don't know for sure if buildTransitive #252 is relevant to this situation or not.

Because 2.0.5 multitargets net5.0, the problem here may be getting fixed another way, but I don't know for sure.

Issue #252 should have been fixed a long time ago, but given current conditions, it may be less important than ever.

I don't know if 2.0.5 is going to be compatible with efcore 5.x.

This test case might be unusual. I wish the repro didn't involve GUI code, because a console app tree would be easier to test. But the specific details of what "modular" means here w.r.t. Prism might in fact be the very thing that makes this case tricky. The app project in the repro sample appears to have no reference to the module project.

bricelam commented 3 years ago

This raises questions about whether 2.0.5 is going to be compatible with EFCore 5.x stuff

Keep in mind that EF Core 5.0 is a current (non-LTS) release and will go out of support three (maybe six) months after EF Core 6.0 is released in November. IMHO, it's probably fine if it doesn't work because anyone on 5.0 who wants to upgrade should also be upgrading to 6.0 during that time.

james1301 commented 2 years ago

Since .net 6 and Visual Studio 2022 have reached RC, I've upgraded the sample app and upgraded the SQLite nugets to the latest RC version but still I get the same error.

Is the suggestion that this should work OK in net 6 @ericsink and @bricelam? Do I need to do anything else for this to work?

ModularAppSQLiteProblem .net 6.zip

ericsink commented 2 years ago

I'm trying to take a fresh look at this old issue.

First of all, using the most recent repro sample project, I can reproduce the problem.

I'm pretty sure that #252 and the buildTransitive notion was a red herring. NuGet targets files are not involved here, as this is simply .NET 6.

Issues of EF Core 5 and my version 2.0.5 etc are also no longer in play.

One of my struggles in the past was that this problem is Prism-centric, and is using a definition of "modular" which is specific to Prism.

A light bulb went on for me just now when I was looking through the repro sample.

I do see the 3 main projects:

  1. The app
  2. The Services/IRepository assembly
  3. The module

The module project is not actually referenced (in the sense of an msbuild project reference) from either of the other two, which I understand to be the main point of this architecture, and tied to the notion of "modular" in this context.

But what kept confusing me is how the build products of the module project were ending up in the output directory of the app project without such a reference.

Then I found that the csproj for the module project is modifying its output path manually. It literally does stuff like this:

    <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
        <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
        <OutputPath>$(SolutionDir)\$(SolutionName)\$(SolutionName)\bin\x64\Debug</OutputPath>
    </PropertyGroup>

This seems very fragile. For example, I suspect it won't work with dotnet publish.

And I also suspect this is the reason why the DllImport stuff can't find the native library. In theory, it should be possible to configure a special resolver to allow the native library to be found.

Bottom line: Without special effort, I don't think this approach is likely to "just work" with SQLitePCLRaw, because MSBuild project references are the way that .NET configures the ability for DllImport to find things.

OTOH, if this Prism/modules approach is working well with other cases which involve the use of a bundled native code library, I would be interested in how they're doing it.

james1301 commented 2 years ago

Thank you for looking into this @ericsink. We have a similar issue with System.DirectoryServices.AccountManagement and yes are aware of the donet Publish issues.

We are planning to revisit this and see if we can come up with a better solution. Any recommendations to gain a similar outcome that works would be appreciated. Plan to start looking at this https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support

ericsink commented 2 years ago

I don't think I have any useful suggestions on how to do plugins. There are lots of approaches. Interop with a native code library will be tricky in most cases, but it can be done.

I'll go ahead and close this issue. Best wishes.