9

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

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

4

6 回答 6

11

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

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

目前在OrmLite 的问题中讨论了一些线程,它们正在探索添加它的不同方法。

于 2013-01-04T05:58:15.890 回答
2

我实现了一个 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 测试过。无法处理重命名属性。

于 2015-04-27T17:49:52.430 回答
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;
        //}

以及在 PostgreSQL 中创建表时IOrmLiteDialectProvider.NamingStrategy.GetTableNameIOrmLiteDialectProvider.NamingStrategy.GetColumnNameOrmLite 使用的将表和列名从 PascalNotation 转换为 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);
            }
        }
    }
于 2013-11-24T20:07:10.537 回答
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);
        }
    }
}
于 2013-10-16T06:51:45.713 回答
1

所以我接受了 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);
        }

于 2018-04-20T12:16:33.573 回答