当前位置: 首页 > 工具软件 > NPoco > 使用案例 >

SQLite和NPoco的数据库初始化器

宋嘉懿
2023-12-01

目录

介绍

IDbInitialiser

IDatabaseConfigurator

示例实现

注意事项


介绍

这篇文章是在我创建一个小型博客风格的Web应用程序时出现的,其中EFSQL Server的开销太大,我需要一个更小、更轻的进程内数据库,我可以将它与Microsoft AspNet Identity框架一起使用。我之前在使用Umbraco时曾使用PetaPoco作为轻量级ORM,随后发现NPoco添加了一些不错的功能,包括许多方法的异步版本。SQLite是我对进程内数据库的选择。

然而,这个解决方案没有提供任何方式来自动保持数据库更新模式更改,因此我创建了一个简单的初始化框架,这就是本文的内容。

恕我直言,SQLite是一款出色的产品,甚至支持全文搜索。但是,一个小的限制是在某些情况下,SQLite仅支持标准SQL命令的一个子集。例如,ALTER TABLE不能用于删除列,而且不像CREATE TABLE IF NOT EXISTS,没有类似的ALTER TABLE ADD COLUMN IF NOT EXISTS。因此,任何初始化程序都必须能够处理更复杂的场景,而不仅仅是执行脚本。当然,初始化程序还必须具有有效的版本控制机制,以便它知道何时何地开始应用更改。

由此产生的初始化程序绝不是完美的,我相信读者会找到很多方法来改进它,但是它可以完成工作,它灵活、自动且易于实现。它包括2个接口:

IDbInitialiser

/// <summary>
/// IDbInitialiser is used to manage database initialisation and upgrades.
/// You would normally call this once at application startup
/// </summary>
public interface IDbInitialiser : IDisposable
{
    /// <summary>
    /// Perform database initialisation. 
    /// This should move the database from it's current
    /// version up to the latest version. 
    /// The IDbInitialiser will resolve how to locate and sort the 
    /// required IDatabaseConfigurators
    /// </summary>
    void InitialiseDatabase();
    /// <summary>
    /// Perform database initialisation. 
    /// This should move the database from it's current
    /// version up to the latest version. 
    /// The correct sequence of the IDatabaseConfigurators is the 
    /// responsibility of the caller.
    /// </summary>
    /// <param name="configurators">An array of 
    /// IDatabaseConfigurators which will be execute in order</param>
    /// <param name="dispose">Set to true to cause 
    /// each IDatabaseConfigurator to be disposed after use</param>
    void InitialiseDatabase(IDatabaseConfigurator[] configurators, bool dispose = false);
    /// <summary>
    /// After completion this should show the initial version of the database
    /// </summary>
    long InitialVersion { get; }
    /// <summary>
    /// After completion this should show the final version of the database
    /// </summary>
    long FinalVersion { get; }
    /// <summary>
    /// After completion this should show 
    /// the number of IDatabaseConfigurators which were executed
    /// </summary>
    long ConfiguratorsRun { get; }
}

此接口提供维护数据库模式的主要功能。每次应用程序启动时都会调用该InitialiseDatabase方法,并且应该执行将数据库带到当前版本所需的所有更新。它提供的属性不是必需的,但对于调试或记录数据库更改活动很有用。

IDatabaseConfigurator

/// <summary>
/// Configurators are the components which actually make database changes. 
/// To use automatic configuration the configurator classes 
/// must use a consistent naming convention 
/// which ends in 3 numeric digits starting 
/// with 001 e.g. Config001, Config002, Config003 etc. 
/// The 3 digit number is the database version number 
/// and is stored in Sqlite using a PRAGMA.
/// The convention must use a consistent name 
/// e.g. Configxxx so that the configurators can be sorted
/// into the correct operational sequence.
/// For manual configuration then class naming is 
/// irrelevant since you must provide an array of
/// configurators to IDbInitialiser which are already 
/// in the correct order and which provide their
/// own version numbers.
/// </summary>
public interface IDatabaseConfigurator : IDisposable
{
    /// <summary>
    /// Provides the version number of this set of database changes
    /// </summary>
    int Version { get; }
    /// <summary>
    /// Perform any actions on the database before changing the schema. This might 
    /// involve copying data to temp tables etc to avoid data loss
    /// </summary>
    /// <param name="db">An Npoco Database object</param>
    void PreMigrate(IDatabase db);
    /// <summary>
    /// Update the database schema
    /// The final task must be to update the version number in Sqlite
    /// </summary>
    /// <param name="db">An Npoco Database object</param>
    void Migrate(IDatabase db);
    /// <summary>
    /// Perform any actions on the database after changing the schema. This might
    /// include copying data back from temporary tables and then cleaning up.
    /// </summary>
    /// <param name="db">An Npoco Database object</param>
    void PostMigrate(IDatabase db);
    /// <summary>
    /// Perform any seeding needed by the database. This might include setting
    /// new column values to a default as well as genuine data seeding
    /// </summary>
    /// <param name="db">An Npoco Database object</param>
    void Seed(IDatabase db);
}

这个interface提供了用于操作特定数据库版本更改的核心功能。每次发布需要更改数据库时,都会创建一个新的IDatabaseConfigurator,它将执行所需的所有更改。

示例实现

SQLite的一个很好的特性是,当你第一次以任何方式访问数据库时,如果它不存在,SQLite将创建空数据库。我使用SQLite本身来存储当前的数据库版本号。这是使用PRAGMA user_version命令完成的。

当按照惯例使用更新过程时,每个IDatabaseConfigurator必须使用类名的最后三个字符提过它的版本号(例如Config000Config001等等)。初始化器使用反射定位和实例化配置器,完成后也会处理它们。InitialiseDatabase的第二个版本允许您提供自己的预初始化配置器对象列表,这些对象必须按正确的顺序排列,并且您可以选择负责处理它们。

MyDbInitialiser 将整个更新序列包装在一个事务中,这样数据库要么完全升级,要么在发生错误时根本不升级。

假设需要对数据库进行一些更改,应用程序的每个版本都将有自己的IDatabaseConfigurator。除了播种之外,这些还允许您执行迁移前和迁移后任务。提供的示例说明了如何将其连接到Microsoft Identity框架以生成种子RoleUser对象。

示例项目是为VS2017提供的,初始化程序的执行是使用单元测试而不是虚拟应用程序演示的(请注意,这些不是一组正确的真正测试,而只是演示初始化程序执行的一种方式)。

使用初始化程序时,应在每次应用程序启动时执行。对于MVC应用程序,一个好点可能是Application_Start()方法:

using (var db = new MyDb())
{
    using (var initialiser = new MyDbInitialiser(db))
    {
        initialiser.InitialiseDatabase();
    }
}

注意事项

SQLiteSystem.Data.SQLite一起使用时,您需要在配置文件中添加所需的DbProviderFactory条目。

<system.data>
    <DbProviderFactories>
        <add name="SQLite Data Provider" 
        invariant="System.Data.SQLite" 
        description=".NET Framework Data Provider for SQLite" 
        type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
    </DbProviderFactories>
</system.data>

SQLite习惯于让SQLite.Interop.dll保持打开状态,从而导致构建失败。发生这种情况是因为测试运行程序在测试之间保留在内存中。通过使用测试设置停止将执行引擎保留在内存中来解决此问题。

此示例不是生产代码,它只是如何使用SQLiteNPoco实现interface 的示例。

https://www.codeproject.com/Articles/1193110/A-Database-Initialiser-for-SQLite-and-NPoco

 类似资料: