摘要:寫這篇文章緣於昨天跟Linkin的一段聊天。我在使用ActiveRecord的一些技巧一文中的由實體類生成數據庫表提到了這樣一句話:生成數據庫表時只有當該表不存在時ActiveRecord才會生成,否則表如果存在ActiveRecord不會做任何事情,也不會報任何錯誤。Linkin說他在實驗時如果數據庫表存在,ActiveRecord會刪除表中的記錄,其實這句話是在有些情況下是不對的,本篇文章將詳細介紹Castle ActiveRecord中的Schema Pitfals。
主要內容
1.引言
2.CreateSchema和DropSchema
3.CreateSchemaFromFile
4.GenerateCreationScripts和GenerateDropScripts
一.引言
我在Castle ActiveRecord學習實踐(9):使用ActiveRecord的一些技巧一文中的由實體類生成數據庫表提到了這樣一句話:生成數據庫表時只有當該表不存在時ActiveRecord才會生成,否則表如果存在ActiveRecord不會做任何事情,也不會報任何錯誤。Linkin說他在實驗時如果數據庫表存在,ActiveRecord會刪除表中的記錄,其實那句話是在有些情況下是不對的,通過後面的分析我們會看到。
Castle ActiveRecord為我們提供了由實體類生成數據庫表的方法,它其實在底層是封裝了NHibernate.Tool.hbm2ddl中的SchemaExport,既創建數據庫表的方法都是通過SchemaExport類來完成了,所有的這些方法都在ActiveRecordStarter中提供,列表如下:
方 法 示例 CreateSchema() ActiveRecordStarter.CreateSchema(); CreateSchemaFromFile() ActiveRecordStarter.CreateSchemaFromFile("blog.sql"); DropSchema () ActiveRecordStarter.DropSchema(); GenerateDropScripts() ActiveRecordStarter.GenerateDropScripts("blog.sql"); GenerateCreationScripts() ActiveRecordStarter.GenerateCreationScripts("blog.sql");
二.CreateSchema和DropSchema
CreateSchema根據實體類來生成數據庫表,在調用ActiveRecordStarter.CreateSchema()之後,我們來看一下ActiveRecord中執行了什麼操作:
public static void CreateSchema()
{
CheckInitialized();
foreach(Configuration config in ActiveRecordBase._holder.GetAllConfigurations())
{
SchemaExport export = CreateSchemaExport(config);
try
{
export.Create( false, true );
}
catch(Exception ex)
{
throw new ActiveRecordException( "Could not create the schema", ex );
}
}
}
可以看到在ActiveRecord中,僅僅是調用了NHibernate中的SchemaExport的Create方法,並傳遞了兩個參數分別為fasle,true。現在我們跟蹤代碼到NHibernate中:
public void Create( bool script, bool export )
{
Execute( script, export, false, true );
}
其中的Execute實現如下,為了簡單起見,我省略了部分代碼:
public void Execute( bool script, bool export, bool justDrop, bool format )
{
IDbConnection connection = null;
StreamWriter fileOutput = null;
IConnectionProvider connectionProvider = null;
IDbCommand statement = null;
//
try
{
if( outputFile != null )
{
fileOutput = new StreamWriter( outputFile );
}
if( export )
{
connectionProvider = ConnectionProviderFactory.NewConnectionProvider( props );
connection = connectionProvider.GetConnection();
statement = connection.CreateCommand();
}
//格式化刪除SQL腳本
//執行腳本
if( !justDrop )
{
//格式化創建SQL腳本
//執行腳本
}
}
catch( HibernateException )
{
throw;
}
finally
{
//.
}
}
從代碼中我們可以看到,不管傳入的參數如何,它都會執行刪除腳本,然後判斷,是否只刪除而不創建,這樣一來,用CreateSchema來生成數據庫表,如果表已經存在,它刪除了已有的記錄就是必然的了,也就是說執行這個方法,相當於執行了下面這樣一段SQL腳本:
if exists (select * from dbo.sysobjects where id = object_id(N'Blogs') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table Blogs
create table Blogs (
blog_id INT IDENTITY NOT NULL,
blog_name NVARCHAR(255) null,
blog_author NVARCHAR(255) null,
primary key (blog_id)
)
同樣DropSchema也是這樣,不過justDrop為True罷了,它其實就執行了下面這句話:
if exists (select * from dbo.sysobjects where id = object_id(N'Blogs') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table Blogs
這也就是說, “生成數據庫表時只有當該表不存在時ActiveRecord才會生成,否則表如果存在ActiveRecord不會做任何事情,也不會報任何錯誤”在這種情況下是不對的,ActiveRecord會刪除已經存在的數據庫表,並重新創建,這就會導致表中的數據丟失。
三.CreateSchemaFromFile
CreateSchemaFromFile方法用來執行一段已經存在的SQL腳本,基本用法如下:
ActiveRecordStarter.CreateSchemaFromFile("blog.sql")
與其它四個方法不同的是這個方法並沒有使用NHibernate的任何操作,而是通過Castle ActiveRecord自己的ARSchemaCreator來實現的。
public static void CreateSchemaFromFile(String scriptFileName)
{
CheckInitialized();
ARSchemaCreator arschema = new ARSchemaCreator(
ActiveRecordBase._holder.GetConfiguration( typeof(ActiveRecordBase) ) );
arschema.Execute( scriptFileName );
}
在ARSchemaCreator中,它會對SQL腳本進行分析,並通過IDbConnection,IDbCommand實現,看其中的一個方法:
public static void ExecuteScriptParts(IDbConnection connection, String[] parts)
{
using(IDbCommand statement = connection.CreateCommand())
{
foreach(String part in parts)
{
try
{
statement.CommandText = part;
statement.CommandType = CommandType.Text;
statement.ExecuteNonQuery();
}
catch(Exception ex)
{
// Ignored, but we output it
Debug.WriteLine(String.Format("SQL: {0} \r\nthrew {1}. Ignoring", part, ex.Message));
}
}
}
}
創建數據庫表的方法很簡單,但是我們知道在查詢分析器中,如果執行創建數據庫表的SQL腳本,這張表存在的話,它會報“數據庫中已存在名為 '' 的對象”錯誤,那我們看上面這段代碼catch塊中有這樣一句注釋和代碼:
// Ignored, but we output it
Debug.WriteLine(String.Format("SQL: {0} \r\nthrew {1}. Ignoring", part, ex.Message));
這就是說如果數據庫中存在有Blogs這張表,我們再執行再通過CreateSchemaFromFile()方法來執行下面這段腳本,ActiveRecord將不做任何事情,並且不會報錯:
create table Blogs (
blog_id INT IDENTITY NOT NULL,
blog_name NVARCHAR(255) null,
blog_author NVARCHAR(255) null,
primary key (blog_id)
)
所以,前面那句話,應該是在使用CreateSchemaFromFile()方法的情況有效。
四.GenerateCreationScripts和GenerateDropScripts
有時候,我們可以使用這兩個方法來生成創建或者刪除數據庫表的SQL腳本,然後再利用CreateSchemaFromFile()使用這些腳本。這兩個方法的使用很簡單:
ActiveRecordStarter.GenerateDropScripts("blog.sql");
ActiveRecordStarter.GenerateCreationScripts("blog.sql");
它也是調用了NHibernate中的相應的方法,將會在當前應用程序目錄下生成一個blog.sql的腳本文件。
這篇文章就分析到這兒,最後特別要感謝Linkin,沒有他提的問題,我也不會去深入的研究這其中的細節,在以後的文章中,我會更加認真的去對待每一個問題。