【问题标题】:With OrmLite, is there a way to automatically update table schema when my POCO is modified?使用 OrmLite,有没有办法在我的 POCO 被修改时自动更新表模式?
【发布时间】: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,我假设代码库具有相同的功能,即使它们是用不同的语言编写的。我的假设是错误的。谢谢你的澄清。

标签: servicestack 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.GetTableName 和IOrmLiteDialectProvider.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 函数。基本思路是:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-07-08
        • 1970-01-01
        • 2021-07-07
        • 2022-01-26
        • 2012-08-01
        • 2021-08-17
        • 1970-01-01
        相关资源
        最近更新 更多