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

Posted

技术标签:

【中文标题】使用 OrmLite,有没有办法在我的 POCO 被修改时自动更新表模式?【英文标题】:With OrmLite, is there a way to automatically update table schema when my POCO is modified? 【发布时间】:2012-12-18 00:33:36 【问题描述】:

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

如果这种能力不存在,我有没有办法在数据库中查询表模式,以便我可以手动执行同步?我找到了this,但我使用的是随 ServiceStack 一起安装的 OrmLite 版本,并且在我的一生中,我找不到具有 TableInfo 类的命名空间。

【问题讨论】:

等等。你说的是ORMLite JAVA orm 还是ormlite-servicestack:code.google.com/p/servicestack/wiki/OrmLite 我专门使用 ormlite-servicestack,我假设代码库具有相同的功能,即使它们是用不同的语言编写的。我的假设是错误的。谢谢你的澄清。 【参考方案1】:

我创建了一个扩展方法来自动将缺失的列添加到我的表中。到目前为止工作得很好。警告:获取列名的代码是特定于 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);
            
        
    

【讨论】:

不错的解决方案。一条通知:db.GetDialectProvider().GetColumnTypeDefinition(field.FieldType) 在这里,您将获得该类型的默认值。类型可能具有[StringLength(50)] 之类的属性,因此如果您不想错过它们,您应该检查属性及其值以在 DB 中设置正确的类型。 作为对 Scott 的提议和 Ivan 的 cmets 的更新,以下语句在最新版本的 ServiceStack 中可用,可用于 foreach 循环:var alterSql = db.GetDialectProvider().ToAddColumnStatement(typeof(T), field); 这考虑了字符串长度、nvarchar 等.【参考方案2】:

不,ServiceStack's OrmLite 中目前不支持自动迁移 RDBMS Schema 与 POCO。

OrmLite's issues 目前正在讨论一些线程,它们正在探索添加此内容的不同方法。

【讨论】:

感谢@mythz 快速准确的回复。 @mythz OrmLite 问题的链接已损坏。 是否有自动迁移支持的更新?【参考方案3】:

这里是 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 方法将表名和列名从PascalNotation 转换为OrmLite 在PostgreSQL 中创建表时使用的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);
            
        
    

【讨论】:

【参考方案4】:

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

    重命名数据库中的当前表。 让 OrmLite 创建新架构。 将旧表中的相关数据复制到新表中。 删除旧表。

Github 仓库: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());
 

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

【讨论】:

【参考方案5】:

我需要实现类似的东西,发现 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);
        
    

【讨论】:

由于我还不能评论其他帖子,回复 user44: 现在我们需要的只是一种方法,将现有数据复制到临时表,升级表并将数据复制回来。根据提供者的不同,几乎没有变化,在这里我们自己是赢家。【参考方案6】:

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

        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);
        

【讨论】:

以上是关于使用 OrmLite,有没有办法在我的 POCO 被修改时自动更新表模式?的主要内容,如果未能解决你的问题,请参考以下文章

ORMLite 更改 onUpgrade 中的 allowGeneratedIdInsert

如何在 android 中使用 ProGuard 和 OrmLite

ORMLite 中的集合

POCO图片浏览器怎么关闭后会自动最小化。我重新下载安装还是那样的。请问高手有没有解决的办法

ServiceStack OrmLite“喜欢”Linq

ORM 性能:greenDAO 比 ORMLite 快吗?