如何在 .NET Framework 上使用 SQLite EF Core 数据库提供程序打包单文件可执行文件?

0xc*_*ced 1 .net sqlite entity-framework-core

我想使用SQLite Entity Framework Core Database Provider编写一个应用程序,将其打包为单文件可执行文件(即没有任何 .dll 文件的单个 exe 文件)并使其在 .NET Framework 4.7.2 上运行。

这可能吗?如果可以,如何实现?

0xc*_*ced 5

是的,这是可能的,但它涉及大量工作,无论是在构建时还是在运行时。

首先,您必须使用Costura Fody 插件将参考文献嵌入为资源。将其添加到您的 csproj 文件中:

<ItemGroup>
    <PackageReference Include="Costura.Fody" Version="4.1.0" />
</ItemGroup>
Run Code Online (Sandbox Code Playgroud)

这样做会自动将所有 dll 打包到主可执行文件中并在运行时加载它们。对于托管 dll,一切都是开箱即用的,但本机 dll 需要更多的工作。costura32如果我们将本机库嵌入到名为或 的目录中,Costura 可以负责预加载本机库costura64通过一些 MSBuild 魔法,我们可以嵌入SQLitePCLRaw.lib.e_sqlite3提供的本机 sqlite dll (这是Microsoft.EntityFrameworkCore.Sqlite包的间接依赖项):

<ItemGroup>
    <PackageReference Include="SQLitePCLRaw.lib.e_sqlite3" Version="2.0.4" GeneratePathProperty="true" />
</ItemGroup>

<Target Name="EmbedNativeSQLiteDllWithCostura" BeforeTargets="ResolveAssemblyReferences">
    <ItemGroup>
        <EmbeddedResource Include="$(PkgSQLitePCLRaw_lib_e_sqlite3)\runtimes\win-x86\native\e_sqlite3.dll">
            <Link>costura32\e_sqlite3.dll</Link>
            <Visible>false</Visible>
        </EmbeddedResource>
        <EmbeddedResource Include="$(PkgSQLitePCLRaw_lib_e_sqlite3)\runtimes\win-x64\native\e_sqlite3.dll">
            <Link>costura64\e_sqlite3.dll</Link>
            <Visible>false</Visible>
        </EmbeddedResource>
        <Content Remove="@(Content)" Condition="'%(Filename)%(Extension)' == 'e_sqlite3.dll'" />
        <ReferenceCopyLocalPaths Remove="@(ReferenceCopyLocalPaths)" Condition="'%(Filename)%(Extension)' == 'e_sqlite3.dll' OR '%(Filename)%(Extension)' == 'SQLitePCLRaw.batteries_v2.dll'" />
    </ItemGroup>
</Target>
Run Code Online (Sandbox Code Playgroud)

让我们来分解一下。

首先,我们需要一个显式的PackageReferenceto SQLitePCLRaw.lib.e_sqlite3GeneratePathProperty="true"以便我们可以访问本机 sqlite dll 路径。

然后我们将 x86 和 x64 本机库嵌入到costura32和中costura64,以便 Costura 在启动时自动加载它们(甚至在Main调用函数之前)。

我们还需要e_sqlite3.dllContent项目中删除文件,否则所有本机 dll 都会复制到输出目录中。我们在输出目录中不需要它们,因为我们将使用嵌入的。

最后,我们需要从项目中删除e_sqlite3.dll和 ,以便它们不会作为 Costura 资源嵌入。是一个本机库,我们已经嵌入了它。不得嵌入,因为我们将编写自己的 SQLitePCLRaw 初始化程序,并且我们不希望 EF Core 运行默认初始化程序。SQLitePCLRaw.batteries_v2.dllReferenceCopyLocalPathse_sqlite3.dllSQLitePCLRaw.batteries_v2.dllBatteries_V2.Init()

这就是构建部分。现在让我们看看运行时必须做什么。

由于我们阻止SQLitePCLRaw.batteries_v2.dll了 Costura 嵌入,因此默认初始化程序将不会运行Assembly.Load将返回null)。因此,我们必须在使用SqliteConnection. 我们将重用SQLite3Provider_dynamic_cdecl(来自SQLitePCLRaw.provider.dynamic_cdecl包)并使用我们自己的接口实现来配置它IGetFunctionPointer。我们的实现搜索e_sqlite.dllCostura 自动加载的当前进程的模块,并使用NativeLibrary.TryGetExportSQLitePCLRaw 中的方法:

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using SQLitePCL;

public class ModuleGetFunctionPointer : IGetFunctionPointer
{
    private readonly ProcessModule _module;

    public static ProcessModule GetModule(string moduleName)
    {
        var modules = Process.GetCurrentProcess().Modules.Cast<ProcessModule>().Where(e => Path.GetFileNameWithoutExtension(e.ModuleName) == moduleName).ToList();
        if (modules.Count == 0)
        {
            throw new ArgumentException($"Found no modules named '{moduleName}' in the current process.", nameof(moduleName));
        }
        if (modules.Count > 1)
        {
            throw new ArgumentException($"Found several modules named '{moduleName}' in the current process.", nameof(moduleName));
        }
        return modules[0];
    }

    public ModuleGetFunctionPointer(string moduleName) : this(GetModule(moduleName))
    {
    }

    public ModuleGetFunctionPointer(ProcessModule module)
    {
        _module = module ?? throw new ArgumentNullException(nameof(module));
    }

    public IntPtr GetFunctionPointer(string name) => NativeLibrary.TryGetExport(_module.BaseAddress, name, out var address) ? address : IntPtr.Zero;
}
Run Code Online (Sandbox Code Playgroud)

最后,在程序的一开始,我们需要初始化 SQLitePCLRaw 提供程序:

const string name = "e_sqlite3";
SQLite3Provider_dynamic_cdecl.Setup(name, new ModuleGetFunctionPointer(name));
SQLitePCL.raw.SetProvider(new SQLite3Provider_dynamic_cdecl());
Run Code Online (Sandbox Code Playgroud)

一切就绪后,SQLite EF Core 数据库提供程序就可以在 .NET Framework 上的单文件可执行文件中使用。完整的工作示例代码可供参考。

请注意,如果您的目标是 .NET Core 而不是 .NET Framework,则无需这样做(如果需要)。发布单文件可执行文件将开箱即用。