【问题标题】:ServiceStack OrmLite how to detect change to code model and recreate database?ServiceStack OrmLite 如何检测代码模型的更改并重新创建数据库?
【发布时间】:2014-01-23 22:17:20
【问题描述】:

是否有首选方法来检测代码模型中的更改并自动重新创建数据库?我不需要迁移数据,如果有变化,我可以完全删除所有表,从模型中重新创建表,并使用代码中的初始数据集填充新表。

与此相关:有没有办法通过使用 ServiceStack 的 ORMLite 版本来获取数据库中所有表的列表?

目前我正在使用我自己的课程,但如果已经有一些东西,我不想发明轮子。

Web.config:

<connectionStrings>
    <add name="ApplicationServices" 
         connectionString="data source=(local);Integrated Security=SSPI;database=MyTestDatabase" 
         providerName="System.Data.SqlClient" />
</connectionStrings>

数据库实用程序:

public class DatabaseUtil
{
    private const int CURR_VERSION = 1;
    private static string connString = WebConfigurationManager.ConnectionStrings["ApplicationServices"].ConnectionString;

    public static void Init(Funq.Container container)
    {
        using (var db = connString.OpenDbConnection())
        {
            using (IDbConnection dbConn = connString.OpenDbConnection())
            {
                var createScript = !db.TableExists(typeof(ZatabaseConfig).Name.ToString());
                if (!createScript)
                {
                    var first = dbConn.FirstOrDefault<ZatabaseConfig>("");
                    createScript = first == null || first.Version < CURR_VERSION;
                }

                if (createScript)
                {
                    DropAndCreateDatabase(dbConn);
                }
            }
            // db.InsertAll(SeedData);
        }
    }

    private static void DropAndCreateDatabase(IDbConnection dbConn)
    {
        var tables = new[] { typeof(Table1), typeof(Table2), typeof(Table3), typeof(ZatabaseConfig) };

        // running drop tables only once doesn't remove tables that are referenced
        using (var dbDrop = createConnection())
            dbDrop.ExecuteSql(DROP_EVERYTHING_CONSTRAINT);

        for (int i = 0; i < 5; i++)
        {
            // dropping table 5 times to eliminate foreign constraints
            try
            {
                using (var dbNew = createConnection())
                    dbNew.ExecuteSql(DROP_EVERYTHING_TABLES);
            }
            catch
            {
            }
        }           

        //Drop and re-create all Auth and registration tables
        //var authRepo = (OrmLiteAuthRepository)container.Resolve<IUserAuthRepository>();
        //authRepo.DropAndReCreateTables();

        dbConn.CreateTables(true, tables);
        dbConn.Insert(new ZatabaseConfig { ConfigId = (int)ZatabaseConfigIds.Version, Name = CURR_VERSION });
    }

    private static string DROP_EVERYTHING_CONSTRAINT = @"
        SELECT 'ALTER TABLE ' + OBJECT_NAME(f.parent_object_id)+
        ' DROP CONSTRAINT ' + f.name 
        FROM .sys.foreign_keys AS f
        INNER JOIN .sys.foreign_key_columns AS fc
        ON f.OBJECT_ID = fc.constraint_object_id
        ";
    private static string DROP_EVERYTHING_TABLES = @"
        exec sp_MSforeachtable 'DROP TABLE ?'
        ";
}

【问题讨论】:

    标签: servicestack ormlite-servicestack


    【解决方案1】:

    据我所知,没有内置机制。但你可以扮演自己的角色:

    创建单独的模型组件:

    如果您发现自己经常更改模型,那么我建议您将模型创建为单独的程序集。因此,在您的解决方案中创建一个新的库项目并将您的模型移动到那里。然后在您的主项目中引用该项目。无论如何,这是良好的组织实践。

    然后在您的Properties/AssemblyInfo.cs(模型)中确保AssemblyVersion 设置有通配符内部版本号,并删除[assembly: AssemblyFileVersion ...](如果存在)。

    [assembly: AssemblyVersion("1.0.*")]
    

    所以我的模型:类看起来像这样:

    using System;
    using ServiceStack.Model;
    using ServiceStack.DataAnnotations;
    
    namespace Blog
    {
        public static class Model
        {
            public class Article : IHasId<int>
            {
                [AutoIncrement, PrimaryKey]
                public int Id { get; set; }
                ...
    

    请注意,我使用了一个外部静态类 Model。这使我的所有表格都易于在我的项目中引用。

    创建一个方法来检测模型装配的变化:

    现在我们有一个程序集,当我们对其进行新构建时版本号会自动增加,我们需要能够检测应用程序中对程序集的更改,以便我们可以重新创建表。

    以下代码执行以下操作:

    1. 确定模型程序集的类型。因为它然后可以使用反射来确定当前程序集的版本号。

    2. 检查上次创建的数据库模型版本的应用程序配置设置。

    3. 如果未找到配置设置或版本号不匹配,则解析数据库连接。

    4. 然后从程序集中推断出带有模型的表。这样做的好处是我们可以向模型程序集添加更多表,而无需更改删除/创建代码。

    5. 数据库删除并创建表。

    6. 保存新的程序集版本号。

    7. 如果您在不更改模型的情况下重新启动应用程序,数据库将保持不变,但是进行更改并重新启动并重新创建数据库。

    public static void Init(Funq.Container container)
    {
        ...
        CheckDatabaseModel(typeof(Model));
    }
    
    public void CheckDatabaseModel(Type modelType)
    {
        // Get the referenced model version
        string modelVersion = Assembly.GetAssembly(modelType).GetName().Version.ToString();
    
        // Determine the last model version number from the configuration
        var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
        var lastModelVersion = config.AppSettings.Settings["DatabaseModelVersion"];
    
        // Determine if the model has changed
        if(lastModelVersion == null || lastModelVersion.Value != modelVersion)
        {
            Console.WriteLine("Model has changed");
            using(var db = Resolve<IDbConnectionFactory>().OpenDbConnection())
            {
                // Drop and recreate the tables
    
                // Determine the tables from the model assembly
                var tables = modelType.GetNestedTypes();
    
                db.DropAndCreateTables(tables);
    
                // Repopulate database with initial data.
    
                // Save the model version to settings
                if(lastModelVersion == null)
                    config.AppSettings.Settings.Add("DatabaseModelVersion", modelVersion);
                else 
                    config.AppSettings.Settings["DatabaseModelVersion"].Value = modelVersion;     
                config.Save(ConfigurationSaveMode.Modified);
            }
        } else {
            // The model numbers matched
            Console.WriteLine("Model is current");
        }
    }
    

    处理建表顺序

    您的数据库可能会有外键约束,然后您会发现您需要按特定顺序创建表,否则数据库将不满意。

    当您为 db.DropAndCreateTables 手动创建表数组时,您将指定创建顺序以满足任何约束。但是因为我们使用的是modelTypes.GetNestedTypes(),所以订单不再受我们控制。有几种方法可以解决这个问题。

    1:禁用密钥检查约束(不推荐)

    最基本的方法是在我们创建表时指示我们的数据库忽略约束。在 MySQL 中,代码为:

    db.ExecuteSql("SET foreign_key_checks = 0;");
    db.DropAndCreateTables(tables);
    db.ExecuteSql("SET foreign_key_checks = 1;");
    

    MSSQL 或其他数据库中所需的代码会有所不同,在某些情况下可能是不可能的。但这最终是一种危险的做事方式。毕竟,这些限制是有原因的。

    2:在我们的模型中定义表创建顺序(推荐):

    我们可以创建一个简单的属性来装饰我们的表,它告诉我们的数据库设置代码执行操作的顺序。这样做的好处是我们不必转换约束,维护人员很清楚为了事情会发生。

    属性:

    public class TableCreationOrderAttribute : Attribute
    {
        public int Order { get; private set; }
    
        public TableCreationOrderAttribute(int order)
        {
            Order = order;
        }
    }
    

    装饰模型:

    public static class Model
    {
        [TableCreationOrder(3)]
        public class Article : IHasId<int>
        {
            [AutoIncrement, PrimaryKey]
            public int Id { get; set; }
            ...
    

    所以现在我们需要告诉数据库设置代码如何使用这个顺序来正确地创建表。将此行 db.DropAndCreateTables(tables); 替换为:

    var orderedTables = new Dictionary<int, Type>();
    var unorderedTables = new List<Type>(); // Tables without the attribute will be created last, but in no specific order
    foreach(var table in tables)
    {
        var order = Attribute.GetCustomAttribute(table, typeof(TableCreationOrderAttribute)) as TableCreationOrderAttribute;
        if(order != null)
            orderedTables.Add(order.Order, table);
        else
            unorderedTables.Add(table);
    }
    
    foreach(var table in orderedTables.OrderBy(t=>t.Key))
        db.DropAndCreateTable(table.Value);
    
    foreach(var table in unorderedTables)
        db.DropAndCreateTable(table);
    

    总结

    看起来很多,但实际上并非如此,CheckDatabaseModel 方法可以缩减到少于 35 行代码。它是通用的,因此您可以将其添加到实用程序库并在其他项目中使用单个调用再次重用它。您永远不必担心手动触发数据库刷新。

    The full source code for the method can be found here.

    包括一个简化的分步指南,因为这个答案包括很多额外的解释。


    有没有办法使用 ServiceStack 的 ORMLite 版本来获取数据库中所有表的列表?

    • 直接内置到ORMLite中:否。
    • 您可以使用db.ExecSql 查询数据库以获取此信息。每个数据库都有不同的命令来执行此操作。以这种方式使用原始 SQL 肯定是可能的。

    希望这会有所帮助。

    【讨论】:

    • 现在,这是一个很好的答案... :) mythz 聘请你代替他回答问题了吗?我问只是因为你们两个真的可以写出详细的答案。
    • @kape123 很高兴你喜欢它。不,我希望 :) 我真的很喜欢 ServiceStack,并且享受解决问题的挑战。关于 SO 的详细答案比那些只会引出一百万个其他问题的答案有用得多。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多