This is one in a series of articles about Time Tortoise, a Universal Windows Platform app for planning and tracking your work schedule. For more on the development of this app and the ideas behind it, see my Time Tortoise category page.
Last week, I upgraded the Time Tortoise projects to .NET Standard 2.0, and also upgraded related NuGet packages that had .NET Standard 2.0 versions. The upgrade was mostly successful, but it broke a few unit tests. In the process of fixing them this week, I had to find yet another technique for resolving dependencies.
Microsoft.Data.Sqlite, EF Core, and SQLitePCL.raw
Time Tortoise uses SQLite to store activities, time segments, and other time tracking data. Since SQLite is written in C, it’s not conveniently accessible from a .NET application. Fortunately, people have built interfaces from .NET to SQLite. For example, there’s the Microsoft.Data.Sqlite.SqliteConnection class for creating a connection to a SQLite database. And Entity Framework Core abstracts away most of the database-specific details of reading and writing data.
In v2.0, Microsoft.Data.Sqlite and EF Core have adopted SQLitePCL.raw, a library that provides low-level access to SQLite. This change is intended to be mostly transparent to application code, but like many changes involving package upgrades, it affected Time Tortoise unit test projects.
Unit Test Errors
The first error I ran into was this one, which occurred in my database integration tests:
System.Exception : This is the ‘bait’. You probably need to add one of the SQLitePCLRaw.bundle_* nuget packages to your platform project.
Bait refers to the Bait and Switch Pattern, a trick used when building Portable Class Libraries (like SQLitePCL.raw) to take advantage of platform-specific features that wouldn’t normally be available to a PCL.
The error message provided a clear fix, so I took its advice and added the SQLitePCLRaw.bundle_green NuGet package to the projects that needed it.
Once I did that and re-ran the unit tests, I got a new message when attempting to instantiate a
Message: System.TypeInitializationException : The type initializer for ‘Microsoft.Data.Sqlite.SqliteConnection’ threw an exception.
—- System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation.
——– System.DllNotFoundException : Unable to load DLL ‘e_sqlite3’: The specified module could not be found. (Exception from HRESULT: 0x8007007E)
I found a few references to this error online, including a GitHub issue that closely matched my configuration: EF Core 2.0, unit tests, and an in-memory SQLite database. According to the issue discussion, the root cause is related to “transitive packages in project references in NuGet.” That sounds similar to the many dependency problems that I have run into with unit tests. But I wasn’t able to resolve the error using any of the advice in that issue, so I decided to do some more investigation.
A few weeks ago, I built a simple tool to facilitate missing dependency investigation. But it wasn’t giving me any information about this missing DLL. The problem:
e_sqlite3.dll is an unmanaged DLL, which means the standard .NET facilities that my tool is based on won’t help.
A web search for debugging DllNotFoundException turned up the perfect Stack Overflow answer for this problem: use Process Monitor. Of course. That tool logs all file references, including .NET assemblies and any other file type. So if a process is trying to load
e_sqlite3.dll, Process Monitor should show exactly where the process thinks it should be.
On first run, the tool is very noisy, since it returns all file activity in the system. After filtering out the noise, I found that
vstest.execution.x86.exe (the Visual Studio unit test execution engine) was looking not for
e_sqlite3.dll, but for a file called
VCRUNTIME140_APP.dll. Presumably, the C-based SQLite engine needs this C runtime library, and for some reason the unit test runner can’t find it.
I found a copy of the DLL in
C:\Program Files\ WindowsApps\ Microsoft.VCLibs.140.00_14.0.24605.0_x86__8wekyb3d8bbwe. From previous dependency debugging experience, I knew that
C:\PROGRAM FILES (X86)\ MICROSOFT VISUAL STUDIO\ 2017\ ENTERPRISE\ COMMON7\ IDE\ COMMONEXTENSIONS\ MICROSOFT\ TESTWINDOW\ is where the test runner would look for it. After copying it there, my tests all ran successfully.
Although my assembly binding log parser is the appropriate tool in most cases for .NET dependency investigation, it’s good to have a way to also investigate missing unmanaged dependencies.