ServiceStack OrmLite如何检测代码模型的更改并重新创建数据库



是否有一种首选方法可以检测代码模型中的更改并自动重新创建数据库?我不需要迁移数据,如果有变化,我可以完全删除所有表,从模型中重新创建表,并用代码中的初始数据集填充新表。

与此相关:有没有一种方法可以通过使用ServiceStack版本的ORMLite来列出数据库中的所有表?

目前我正在使用我自己的类,但如果已经有了这样的东西,我不想发明轮子。

Web.config:

<connectionStrings>
<add name="ApplicationServices" 
connectionString="data source=(local);Integrated Security=SSPI;database=MyTestDatabase" 
providerName="System.Data.SqlClient" />
</connectionStrings>

数据库Util:

public class DatabaseUtil
{
private const int CURR_VERSION = 1;
private static string connString = WebConfigurationManager.ConnectionStrings["ApplicationServices"].ConnectionString;
public static void Init(Funq.Container container)
{
using (var db = connString.OpenDbConnection())
{
using (IDbConnection dbConn = connString.OpenDbConnection())
{
var createScript = !db.TableExists(typeof(ZatabaseConfig).Name.ToString());
if (!createScript)
{
var first = dbConn.FirstOrDefault<ZatabaseConfig>("");
createScript = first == null || first.Version < CURR_VERSION;
}
if (createScript)
{
DropAndCreateDatabase(dbConn);
}
}
// db.InsertAll(SeedData);
}
}
private static void DropAndCreateDatabase(IDbConnection dbConn)
{
var tables = new[] { typeof(Table1), typeof(Table2), typeof(Table3), typeof(ZatabaseConfig) };
// running drop tables only once doesn't remove tables that are referenced
using (var dbDrop = createConnection())
dbDrop.ExecuteSql(DROP_EVERYTHING_CONSTRAINT);
for (int i = 0; i < 5; i++)
{
// dropping table 5 times to eliminate foreign constraints
try
{
using (var dbNew = createConnection())
dbNew.ExecuteSql(DROP_EVERYTHING_TABLES);
}
catch
{
}
}           
//Drop and re-create all Auth and registration tables
//var authRepo = (OrmLiteAuthRepository)container.Resolve<IUserAuthRepository>();
//authRepo.DropAndReCreateTables();
dbConn.CreateTables(true, tables);
dbConn.Insert(new ZatabaseConfig { ConfigId = (int)ZatabaseConfigIds.Version, Name = CURR_VERSION });
}
private static string DROP_EVERYTHING_CONSTRAINT = @"
SELECT 'ALTER TABLE ' + OBJECT_NAME(f.parent_object_id)+
' DROP CONSTRAINT ' + f.name 
FROM .sys.foreign_keys AS f
INNER JOIN .sys.foreign_key_columns AS fc
ON f.OBJECT_ID = fc.constraint_object_id
";
private static string DROP_EVERYTHING_TABLES = @"
exec sp_MSforeachtable 'DROP TABLE ?'
";
}

据我所知,没有内置的机制。但你可以扮演自己的角色:

创建单独的模型部件:

如果您发现自己经常更改模型,那么我建议您将模型创建为单独的部件。因此,在您的解决方案中创建一个新的库项目,并将您的模型移动到那里。然后在主项目中引用该项目。无论如何,这是一种良好的组织实践。

然后,在(模型的)Properties/AssemblyInfo.cs中,确保AssemblyVersion设置有通配符内部版本号,并删除[assembly: AssemblyFileVersion ...](如果存在)。

[assembly: AssemblyVersion("1.0.*")]

所以我的模型:类看起来像这样:

using System;
using ServiceStack.Model;
using ServiceStack.DataAnnotations;
namespace Blog
{
public static class Model
{
public class Article : IHasId<int>
{
[AutoIncrement, PrimaryKey]
public int Id { get; set; }
...

请注意,我使用了一个外部静态类Model。这使得我的所有表在我的项目中都很容易被引用。

创建一种方法来检测对模型部件的更改:

现在我们有了一个程序集,当我们对它进行新的构建时,它的版本号将自动增加,我们需要能够在应用程序中检测到程序集的更改,以便重新创建表。

下面的代码执行以下操作:

  1. 确定Model程序集的类型。因为它可以使用反射来确定当前程序集的版本号。

  2. 检查上次创建的数据库模型版本的应用程序配置设置。

  3. 如果找不到配置设置或版本号不匹配,则会解决数据库连接。

  4. 然后,将从程序集中推断出具有模型的表。这样做的好处是,我们可以向Model程序集中添加更多的表,而不必更改drop/create代码。

  5. 数据库将删除并创建表。

  6. 将保存新的程序集版本号。

  7. 如果在不更改模型的情况下重新启动应用程序,则数据库将保持不变,但进行更改并重新启动,将重新创建具有的数据库。

public static void Init(Funq.Container container)
{
...
CheckDatabaseModel(typeof(Model));
}
public void CheckDatabaseModel(Type modelType)
{
// Get the referenced model version
string modelVersion = Assembly.GetAssembly(modelType).GetName().Version.ToString();
// Determine the last model version number from the configuration
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
var lastModelVersion = config.AppSettings.Settings["DatabaseModelVersion"];
// Determine if the model has changed
if(lastModelVersion == null || lastModelVersion.Value != modelVersion)
{
Console.WriteLine("Model has changed");
using(var db = Resolve<IDbConnectionFactory>().OpenDbConnection())
{
// Drop and recreate the tables
// Determine the tables from the model assembly
var tables = modelType.GetNestedTypes();
db.DropAndCreateTables(tables);
// Repopulate database with initial data.
// Save the model version to settings
if(lastModelVersion == null)
config.AppSettings.Settings.Add("DatabaseModelVersion", modelVersion);
else 
config.AppSettings.Settings["DatabaseModelVersion"].Value = modelVersion;     
config.Save(ConfigurationSaveMode.Modified);
}
} else {
// The model numbers matched
Console.WriteLine("Model is current");
}
}

处理表创建顺序

你的数据库可能会有外键约束,然后你会发现你需要按照特定的顺序创建表,否则数据库会不满意。

在指定创建顺序以满足任何约束之前,手动为db.DropAndCreateTables创建表阵列时。但由于我们使用的是modelTypes.GetNestedTypes(),订单不再由我们控制。有几种方法可以解决这个问题。

1:禁用密钥检查约束(不推荐)

最基本的是,在我们创建表时,指示我们的数据库忽略约束。在MySQL中,代码为:

db.ExecuteSql("SET foreign_key_checks = 0;");
db.DropAndCreateTables(tables);
db.ExecuteSql("SET foreign_key_checks = 1;");

MSSQL或其他数据库中所需的代码会有所不同,在某些情况下可能不可能。但这最终是一种危险的做事方式。毕竟,这些限制是有原因的。

2:在我们的模型中定义表格创建顺序(推荐):

我们可以创建一个简单的属性,用它来装饰我们的表,告诉我们的数据库设置代码按照什么顺序来做事情。这样做的好处是我们不必求助于约束,维护人员很清楚事情将按照什么顺序发生。

属性:

public class TableCreationOrderAttribute : Attribute
{
public int Order { get; private set; }
public TableCreationOrderAttribute(int order)
{
Order = order;
}
}

装饰模型:

public static class Model
{
[TableCreationOrder(3)]
public class Article : IHasId<int>
{
[AutoIncrement, PrimaryKey]
public int Id { get; set; }
...

因此,现在我们需要告诉数据库设置代码如何使用此顺序来正确创建表。将此线路db.DropAndCreateTables(tables);替换为:

var orderedTables = new Dictionary<int, Type>();
var unorderedTables = new List<Type>(); // Tables without the attribute will be created last, but in no specific order
foreach(var table in tables)
{
var order = Attribute.GetCustomAttribute(table, typeof(TableCreationOrderAttribute)) as TableCreationOrderAttribute;
if(order != null)
orderedTables.Add(order.Order, table);
else
unorderedTables.Add(table);
}
foreach(var table in orderedTables.OrderBy(t=>t.Key))
db.DropAndCreateTable(table.Value);
foreach(var table in unorderedTables)
db.DropAndCreateTable(table);

摘要

它看起来可能很多,但实际上并不是,CheckDatabaseModel方法可以缩减到少于35行代码。它是通用的,因此您可以将它添加到实用程序库中,并在其他项目中使用单个调用再次重用它。您永远不必担心手动触发数据库刷新。

该方法的完整源代码可以在这里找到

包括一个简化的分步指南,因为这个答案包括很多额外的解释


有没有办法通过使用ServiceStack版本的ORMLite来获得数据库中所有表的列表?

  • 直接内置到ORMLite中:没有
  • 您可以使用db.ExecSql在数据库中查询此信息。每个数据库都有一个不同的命令来执行此操作。以这种方式使用原始SQL当然是可能的

希望这能有所帮助。

最新更新