我想写一个使用SQLite Entity Framework Core Database Provider的应用程序,将其打包为单文件可执行文件(即没有任何.dll文件的单个exe文件),并使其在.NET框架4.7.2上运行。
这是可能的吗?如果是,是如何实现的?
发布于 2020-09-14 15:50:22
是的,这是可能的,但它涉及到大量的工作,无论是在构建时还是在运行时。
首先,您必须使用Costura Fody add-in将引用嵌入为资源。将以下内容添加到您的csproj文件:
<ItemGroup>
<PackageReference Include="Costura.Fody" Version="4.1.0" />
</ItemGroup>
这样做将自动打包主可执行文件中的所有all,并在运行时加载它们。对于托管dlls来说,一切都是开箱即用的,但本机dlls需要做更多的工作。如果我们将本机库嵌入到名为costura32
或costura64
的目录中,Costura可以负责预加载本机库。通过一些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>
让我们把它分解一下。
首先,我们需要一个显式的指向GeneratePathProperty="true"
的SQLitePCLRaw.lib.e_sqlite3
的PackageReference
,这样我们就可以访问本地sqlite dll路径。
然后,我们将x86和x64本地库嵌入到costura32
和costura64
中,以便Costura在启动时自动加载它们(甚至在调用Main
函数之前)。
我们还需要从Content
项中删除e_sqlite3.dll
文件,否则所有本机all都将复制到输出目录中。我们不需要在输出目录中使用它们,因为我们将使用嵌入式文件。
最后,我们需要从ReferenceCopyLocalPaths
项中删除e_sqlite3.dll
和SQLitePCLRaw.batteries_v2.dll
,这样它们就不会嵌入到Costura资源中。e_sqlite3.dll
是一个本地库,我们已经嵌入了它。不能嵌入SQLitePCLRaw.batteries_v2.dll
,因为我们将编写自己的SQLitePCLRaw初始化器,并且我们不希望EF Core运行默认的Batteries_V2.Init()
初始化器。
这就是构建部分。现在让我们看看在运行时必须做些什么。
因为我们阻止了SQLitePCLRaw.batteries_v2.dll
被Costura嵌入,所以默认的初始化器will not run (Assembly.Load
将返回null
)。因此,在使用SqliteConnection
之前,我们必须使用configure a SQLite provider。我们将重用SQLite3Provider_dynamic_cdecl
(来自SQLitePCLRaw.provider.dynamic_cdecl包),并使用我们自己的IGetFunctionPointer
接口实现来配置它。我们的实现搜索由Costura自动加载的当前进程的e_sqlite.dll
模块,并使用来自SQLitePCLRaw的NativeLibrary.TryGetExport
方法:
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;
}
最后,在程序的最开始,我们需要初始化SQLitePCLRaw提供程序:
const string name = "e_sqlite3";
SQLite3Provider_dynamic_cdecl.Setup(name, new ModuleGetFunctionPointer(name));
SQLitePCL.raw.SetProvider(new SQLite3Provider_dynamic_cdecl());
一切就绪后,可以在.NET框架上的单个文件可执行文件中使用SQLite EF核心数据库提供程序。完整的工作sample code可供参考。
请注意,如果您的目标是.NET核心而不是.NET框架,则如果这是必需的,则为none。Publishing a single-file executable将开箱即用。
https://stackoverflow.com/questions/63887709
复制相似问题