使用 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.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 函数。基本思路是:
-
重命名数据库中的当前表。
让 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