使用OrmLite时,有没有办法在修改POCO时自动更新表模式?

时间:2013-01-03 15:57:47

标签: servicestack ormlite-servicestack

OrmLite可以识别我的POCO和我的架构之间的差异,并根据需要自动添加(或删除)列以强制架构与我的POCO保持同步吗?

如果此功能不存在,我是否可以在数据库中查询表模式,以便我可以手动执行同步?我发现this,但我使用的是安装了ServiceStack的OrmLite版本,在我的生命中,我找不到具有TableInfo类的命名空间。

6 个答案:

答案 0 :(得分:10)

我创建了一个扩展方法来自动将缺少的列添加到我的表中。到目前为止工作得很好。警告:获取列名的代码是特定于SQL Server的。

namespace System.Data
{
    public static class IDbConnectionExtensions
    {
        private static List<string> GetColumnNames(IDbConnection db, string tableName)
        {
            var columns = new List<string>();
            using (var cmd = db.CreateCommand())
            {
                cmd.CommandText = "exec sp_columns " + tableName;
                var reader = cmd.ExecuteReader();
                while (reader.Read())
                {
                    var ordinal = reader.GetOrdinal("COLUMN_NAME");
                    columns.Add(reader.GetString(ordinal));
                }
                reader.Close();
            }
            return columns;
        }

        public static void AlterTable<T>(this IDbConnection db) where T : new()
        {
            var model = ModelDefinition<T>.Definition;

            // just create the table if it doesn't already exist
            if (db.TableExists(model.ModelName) == false)
            {
                db.CreateTable<T>(overwrite: false);
                return;
            }

            // find each of the missing fields
            var columns = GetColumnNames(db, model.ModelName);
            var missing = ModelDefinition<T>.Definition.FieldDefinitions
                .Where(field => columns.Contains(field.FieldName) == false)
                .ToList();

            // add a new column for each missing field
            foreach (var field in missing)
            {
                var alterSql = string.Format("ALTER TABLE {0} ADD {1} {2}", 
                    model.ModelName,
                    field.FieldName, 
                    db.GetDialectProvider().GetColumnTypeDefinition(field.FieldType)
                    );
                Console.WriteLine(alterSql);
                db.ExecuteSql(alterSql);
            }
        }
    }
}

答案 1 :(得分:4)

目前在ServiceStack's OrmLite中没有对RDBMS架构与POCO的自动迁移的支持。

目前在OrmLite's issues中讨论的几个主题正在探索添加此内容的不同方法。

答案 2 :(得分:2)

以下是cornelha与PostgreSQL一起使用的稍微修改过的代码版本。删除了这个片段

        //private static List<string> GetColumnNames(object poco)
        //{
        //    var list = new List<string>();
        //    foreach (var prop in poco.GetType().GetProperties())
        //    {
        //        list.Add(prop.Name);
        //    }
        //    return list;
        //}

并使用IOrmLiteDialectProvider.NamingStrategy.GetTableNameIOrmLiteDialectProvider.NamingStrategy.GetColumnName方法在PostgreSQL中创建表时将表名和列名从PascalNotation转换为OrmLite使用的this_kind_of_notation。

    public static class IDbConnectionExtensions
    {
        private static List<string> GetColumnNames(IDbConnection db, string tableName, IOrmLiteDialectProvider provider)
        {
            var columns = new List<string>();
            using (var cmd = db.CreateCommand())
            {
                cmd.CommandText = getCommandText(tableName, provider);
                var tbl = new DataTable();
                tbl.Load(cmd.ExecuteReader());
                for (int i = 0; i < tbl.Columns.Count; i++)
                {
                    columns.Add(tbl.Columns[i].ColumnName);
                }

            }
            return columns;
        }

        private static string getCommandText(string tableName, IOrmLiteDialectProvider provider)
        {

            if (provider == PostgreSqlDialect.Provider)

                return string.Format("select * from {0} limit 1", tableName);
            else return string.Format("select top 1 * from {0}", tableName);
        }

        public static void AlterTable<T>(this IDbConnection db, IOrmLiteDialectProvider provider) where T : new()
        {
            var model = ModelDefinition<T>.Definition;
            var table = new T();
            var namingStrategy = provider.NamingStrategy;
            // just create the table if it doesn't already exist
            var tableName = namingStrategy.GetTableName(model.ModelName);
            if (db.TableExists(tableName) == false)
            {
                db.CreateTable<T>(overwrite: false);
                return;
            }

            // find each of the missing fields
            var columns = GetColumnNames(db, model.ModelName, provider);
            var missing = ModelDefinition<T>.Definition.FieldDefinitions
                                            .Where(field => columns.Contains(namingStrategy.GetColumnName(field.FieldName)) == false)
                                            .ToList();

            // add a new column for each missing field
            foreach (var field in missing)
            {
                var columnName = namingStrategy.GetColumnName(field.FieldName);
                var alterSql = string.Format("ALTER TABLE {0} ADD COLUMN {1} {2}",
                                             tableName,
                                             columnName,
                                             db.GetDialectProvider().GetColumnTypeDefinition(field.FieldType)
                    );
                Console.WriteLine(alterSql);
                db.ExecuteSql(alterSql);
            }
        }
    }

答案 3 :(得分:2)

我实现了一个UpdateTable函数。基本思路是:

  1. 重命名数据库上的当前表。
  2. 让OrmLite创建新架构。
  3. 将旧表中的相关数据复制到新表。
  4. 删除旧表。
  5. Github Repo:https://github.com/peheje/Extending-NServiceKit.OrmLite

    简明代码:

    public interface ISqlProvider
        {
            string RenameTableSql(string currentName, string newName);
            string GetColumnNamesSql(string tableName);
            string InsertIntoSql(string intoTableName, string fromTableName, string commaSeparatedColumns);
            string DropTableSql(string tableName);
        }
    
    public static void UpdateTable<T>(IDbConnection connection, ISqlProvider sqlProvider) where T : new()
            {
                connection.CreateTableIfNotExists<T>();
                var model = ModelDefinition<T>.Definition;
                string tableName = model.Name;
                string tableNameTmp = tableName + "Tmp";
                string renameTableSql = sqlProvider.RenameTableSql(tableName, tableNameTmp);
                connection.ExecuteNonQuery(renameTableSql);
    
                connection.CreateTable<T>();
    
                string getModelColumnsSql = sqlProvider.GetColumnNamesSql(tableName);
                var modelColumns = connection.SqlList<string>(getModelColumnsSql);
                string getDbColumnsSql = sqlProvider.GetColumnNamesSql(tableNameTmp);
                var dbColumns = connection.SqlList<string>(getDbColumnsSql);
    
                List<string> activeFields = dbColumns.Where(dbColumn => modelColumns.Contains(dbColumn)).ToList();
    
                string activeFieldsCommaSep = ListToCommaSeparatedString(activeFields);
                string insertIntoSql = sqlProvider.InsertIntoSql(tableName, tableNameTmp, activeFieldsCommaSep);
    
                connection.ExecuteSql(insertIntoSql);
    
                string dropTableSql = sqlProvider.DropTableSql(tableNameTmp);
                //connection.ExecuteSql(dropTableSql);  //maybe you want to clean up yourself, else uncomment
            }
    
            private static String ListToCommaSeparatedString(List<String> source)
            {
                var sb = new StringBuilder();
                for (int i = 0; i < source.Count; i++)
                {
                    sb.Append(source[i]);
                    if (i < source.Count - 1)
                    {
                        sb.Append(", ");
                    }
                }
                return sb.ToString();
            }
        }
    

    MySql实现:

    public class MySqlProvider : ISqlProvider
        {
            public string RenameTableSql(string currentName, string newName)
            {
                return "RENAME TABLE `" + currentName + "` TO `" + newName + "`;";
            }
    
            public string GetColumnNamesSql(string tableName)
            {
                return "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '" + tableName + "';";
            }
    
            public string InsertIntoSql(string intoTableName, string fromTableName, string commaSeparatedColumns)
            {
                return "INSERT INTO `" + intoTableName + "` (" + commaSeparatedColumns + ") SELECT " + commaSeparatedColumns + " FROM `" + fromTableName + "`;";
            }
    
            public string DropTableSql(string tableName)
            {
                return "DROP TABLE `" + tableName + "`;";
            }
        }
    

    用法:

     using (var db = dbFactory.OpenDbConnection())
     {
         DbUpdate.UpdateTable<SimpleData>(db, new MySqlProvider());
     }
    

    Haven未使用FK进行测试。无法处理重命名属性。

答案 4 :(得分:1)

我需要实现类似的东西并且发现Scott的帖子非常有帮助。我决定做一个小改动,这将使它更加不可知。由于我只使用Sqlite和MSSQL,因此我使getCommand方法非常简单,但可以扩展。我用一个简单的数据表来获取列。该解决方案完全符合我的要求。

    public static class IDbConnectionExtensions
{
    private static List<string> GetColumnNames(IDbConnection db, string tableName,IOrmLiteDialectProvider provider)
    {
        var columns = new List<string>();
        using (var cmd = db.CreateCommand())
        {
            cmd.CommandText = getCommandText(tableName, provider);
            var tbl = new DataTable();
            tbl.Load(cmd.ExecuteReader());
            for (int i = 0; i < tbl.Columns.Count; i++)
            {
                columns.Add(tbl.Columns[i].ColumnName);
            }

        }
        return columns;
    }

    private static string getCommandText(string tableName,  IOrmLiteDialectProvider provider)
    {

        if(provider ==  SqliteDialect.Provider)

        return string.Format("select * from {0} limit 1", tableName);
        else return string.Format("select top 1 * from {0}", tableName);
    }

    private static List<string> GetColumnNames(object poco)
    {
        var list = new List<string>();
        foreach (var prop in poco.GetType().GetProperties())
        {
            list.Add(prop.Name);
        }
        return list;
    }

    public static void AlterTable<T>(this IDbConnection db, IOrmLiteDialectProvider provider) where T : new()
    {
        var model = ModelDefinition<T>.Definition;
        var table = new T();
        // just create the table if it doesn't already exist
        if (db.TableExists(model.ModelName) == false)
        {
            db.CreateTable<T>(overwrite: false);
            return;
        }

        // find each of the missing fields
        var columns = GetColumnNames(db, model.ModelName,provider);
        var missing = ModelDefinition<T>.Definition.FieldDefinitions
                                        .Where(field => columns.Contains(field.FieldName) == false)
                                        .ToList();

        // add a new column for each missing field
        foreach (var field in missing)
        {
            var alterSql = string.Format("ALTER TABLE {0} ADD {1} {2}",
                                         model.ModelName,
                                         field.FieldName,
                                         db.GetDialectProvider().GetColumnTypeDefinition(field.FieldType)
                );
            Console.WriteLine(alterSql);
            db.ExecuteSql(alterSql);
        }
    }
}

答案 5 :(得分:1)

所以我接受了user44的回答,并修改了AlterTable方法,使其更有效率。 我没有为每个字段/列循环和运行一个SQL查询,而是将其合并为一个简单的文本解析(MySQL命令!)。

&#13;
&#13;
        public static void AlterTable<T>(this IDbConnection db, IOrmLiteDialectProvider provider) where T : new()
        {
            var model = ModelDefinition<T>.Definition;
            var table = new T();
            var namingStrategy = provider.NamingStrategy;
            // just create the table if it doesn't already exist
            var tableName = namingStrategy.GetTableName(model.ModelName);
            if (db.TableExists(tableName) == false)
            {
                db.CreateTable<T>(overwrite: false);
                return;
            }

            // find each of the missing fields
            var columns = GetColumnNames(db, model.ModelName, provider);
            var missing = ModelDefinition<T>.Definition.FieldDefinitions
                                            .Where(field => columns.Contains(namingStrategy.GetColumnName(field.FieldName)) == false)
                                            .ToList();
            string alterSql = "";
            string addSql = "";
            // add a new column for each missing field
            foreach (var field in missing)
            {
                var alt = db.GetDialectProvider().ToAddColumnStatement(typeof(T), field); // Should be made more efficient, one query for all changes instead of many
                int index = alt.IndexOf("ADD ");
                alterSql = alt.Substring(0, index);
                addSql += alt.Substring(alt.IndexOf("ADD COLUMN")).Replace(";", "") + ", ";
            }
            if (addSql.Length > 2)
                addSql = addSql.Substring(0, addSql.Length - 2);
            string fullSql = alterSql + addSql;
            Console.WriteLine(fullSql);
            db.ExecuteSql(fullSql);
        }
&#13;
&#13;
&#13;