2001年4月份,我在博客中發過一個小工具,它是一個用ASP.NET寫的SQL SERVER的輔助小工具。 在這期間,有些人貌似對那個工具比較有興趣,所以我常能收到索要源代碼的郵件。 正好,我上月又發布了我的MVC框架,因此打算用【我的ASP.NET MVC框架】來重寫這個工具, 並開源。
工具的特點:
1. 采用ASP.NET編寫,並借助MyMVC框架。
2. 為了更好地接近桌面程序的操作體驗,網站采用純AJAX的方式實現。
3. 界面使用了 JQuery Easy UI
4. 代碼的語法着色使用了 syntaxhighlighter (JavaScript類庫)
工具的定位:只是輔助工具,因此功能有限,但要將有限的功能做得盡量好。
下面將分別介紹工具所能完成的功能,以及關鍵的實現代碼。
說明:工具的所有源代碼可以在本文的結尾處下載。
項目介紹
整個工具的源代碼結構如下:
|
項目由Visual Studio 2008創建,包含三個部分:
1. WebApp:一個ASP.NET網站,它是工具的可運行部分。 網站只包含一些HTML, CSS, JavaScript,DLL文件。
2. MyMvcEx:一個類庫項目,它提供了MyMVC框架的二個IActionResult接口的實現類, 用於向瀏覽器客戶端輸出DataTable, DataSet
3. SqlServerSmallToolLib:運行網站所需的所有后台代碼, 包括:Controller,BLL類,等等。 |
MyMVC框架發揮的作用
從前面的項目介紹中,我們可以看到,整個網站沒有一個ASPX文件,只有HTML文件,
所有對服務器的調用全由AJAX來實現,比如:下面的【比較數據庫】的代碼片段:
$.ajax({
cache: false, dataType: "json", type: "GET",
url: '/AjaxService/CompareDB.cspx',
data:{ srcConnId: $("#hfSrcConnId").val(),
destConnId: $("#hfDestConnId").val(),
srcDB: $("#cboSrcDB").combobox("getValue"),
destDB: $("#cboDestDB").combobox("getValue") ,
flag: flag
},
在服務端,我只要實現這樣一個C#方法就可以響應客戶端的請求了:
[Action]
public object CompareDB(string srcConnId, string destConnId, string srcDB, string destDB, string flag)
{
var result = CompareDBHelper.CompareDB(srcConnId, destConnId, srcDB, destDB, flag);
return new JsonResult(result);
}
至於說:JS發起的請求是如何調用到這個C#方法的,以及這個C#方法在調用時的參數和返回值的處理,全由MyMVC框架來實現。
對於開發AJAX來說,可以不用關心這個問題,只要寫出一個C#方法給JS調用就可以了。
引用MyMVC是件很簡單的事情,只需要在web.config中做如下的配置即可:
<httpHandlers>
<add path="*.cspx" verb="*" type="MyMVC.AjaxHandlerFactory, MyMVC" validate="true" />
</httpHandlers>
再補充一點:如果不喜歡看到Action方法包含較多的輸入參數,也可以使用下面的方法:
public class CompareDbOption
{
public string SrcConnId;
public string DestConnId;
public string SrcDb;
public string DestDb;
public string Flag;
}
[Action]
public object CompareDB(CompareDbOption option)
{
var result = CompareDBHelper.CompareDB(option.SrcConnId, option.DestConnId,
option.SrcDb, option.DestDb, option.Flag);
return new JsonResult(result);
}
如果您喜歡在瀏覽器的客戶端中使用jquery以及jquery.form.js插件,
您會發現在服務端再借助MyMVC框架來實現AJAX實在是太方便了。
再來一個添加連接的代碼片段:
Html表單代碼:
<div id="divConnectionDialog" style="padding:10px; width: 420px; height: 320px; display: none;" title="新增/編輯 數據庫連接信息">
<form id="formConnection" method="post" action="/AjaxService/SubmitConnectionInfo.cspx">
<table cellpadding="4" cellspacing="0" style="width: 100%; border: 0px;">
<tr><td>服務器IP/Name</td><td>
<input id="txtServerIP" name="ServerIP" type="text" class="myTextbox" style="width: 220px" />
</td></tr>
<tr><td>登錄方式</td><td>
<select id="cboSSPI" name="SSPI" style="width: 222px" panelWidth="222">
<option value="false">用戶名/密碼</option>
<option value="true">Windows連接</option>
</select>
</td></tr>
<tr><td>登錄名</td><td>
<input id="txtUserName" name="UserName" type="text" class="myTextbox" style="width: 220px" />
</td></tr>
<tr><td>登錄密碼</td><td>
<input id="txtPassword" name="Password" type="text" class="myTextbox" style="width: 220px" />
</td></tr>
<tr><td></td><td>
<input id="hfConnectionId" name="ConnectionId" type="hidden" value="" />
</td></tr>
</table>
<div><span id="spanWait" style="display: none;" class="waitText">請稍后......</span></div>
</form>
</div>
JavaScript提交表單代碼:
function SubmitConnectionForm(){
if( ValidateForm() == false ) return false;
$("#formConnection").ajaxSubmit({
success: function(responseText, statusText) {
if (responseText == "update OK" ){
$('#divConnectionDialog').dialog('close');
// 省略后面的代碼。
服務端C#代碼:
[Action]
public string SubmitConnectionInfo(ConnectionInfo info)
{
if( string.IsNullOrEmpty(info.ServerIP) )
throw new MyMessageException("ServerIP is empty.");
if( info.SSPI == false && string.IsNullOrEmpty(info.UserName) )
throw new MyMessageException("UserName is empty.");
bool isAdd = string.IsNullOrEmpty(info.ConnectionId);
if( isAdd ) {
info.ConnectionId = Guid.NewGuid().ToString();
ConnectionManager.AddConnection(info);
return info.ConnectionId;
}
else {
ConnectionManager.UpdateConnection(info);
return "update OK";
}
}
public sealed class ConnectionInfo
{
public string ConnectionId;
public string ServerIP;
public string UserName;
public string Password;
public bool SSPI;
public int Priority;
}
在整個工具的開發過程中,由於使用了MyMVC框架以及JQuery,AJAX的實現簡直是太容易了。
MyMVC框架的下載地址:http://www.cnblogs.com/fish-li/archive/2012/02/21/2361982.html
工具主界面
工具啟動后,將能看到下面的主界面:

主界面的左邊的【工具列表】中包含二個獨立的功能模塊。
右邊的上方區域是所有的數據庫連接的列表。
建議在初次使用時,將自己所需要訪問的SQL SERVER連接參數配置好。
這個工具可以管理多個連接,而且會根據連接的使用頻率來排序,以方便操作。
如果需要創建一個連接,可以點擊工具欄中的【新增連接】按鍵,將出現以下對話框。

工具可以支持二種連接方式:1. Windows信任連接,2. 用戶名/密碼連接。

數據庫連接列表的部分網頁代碼:
<div region="center" style="overflow:hidden;" title="數據庫連接列表" iconCls="icon-Relation">
<div class="easyui-layout" fit="true" border="false">
<div region="center">
<table id="tblConnList"></table>
</div>
<div region="south" split="true" style="height:220px; padding: 10px;" title="操作說明" iconCls="icon-help">
<p>1. “新增連接”,“刪除連接”,“設置連接”用於維護連接記錄。</p>
<p>2. “打開連接”將根據選擇的連接,打開 Database 對象瀏覽頁面。</p>
</div>
</div>
</div>
連接采用XML文件來保存,相關的操作代碼:
internal static class ConnectionManager
{
private static List<ConnectionInfo> s_list = null;
private static readonly Encoding DefaultEncoding = System.Text.Encoding.Unicode;
private static readonly string s_savePath = Path.Combine(HttpRuntime.AppDomainAppPath, @"App_Data\Connection.xml");
static ConnectionManager()
{
try {
string appDataPath = Path.Combine(HttpRuntime.AppDomainAppPath, "App_Data");
if( Directory.Exists(appDataPath) == false )
Directory.CreateDirectory(appDataPath);
}
catch { }
}
[MethodImpl(MethodImplOptions.Synchronized)]
public static List<ConnectionInfo> GetList()
{
EnsureListLoaded();
// 調用這個方法應該會比“修改”的次數會少很多,所以決定在這里排序。
return (from c in s_list orderby c.Priority descending select c).ToList();
}
[MethodImpl(MethodImplOptions.Synchronized)]
public static void AddConnection(ConnectionInfo info)
{
EnsureListLoaded();
s_list.Add(info);
SaveListToFile();
}
[MethodImpl(MethodImplOptions.Synchronized)]
public static void RemoveConnection(string ConnectionId)
{
EnsureListLoaded();
int index = -1;
for( int i = 0; i < s_list.Count; i++ )
if( s_list[i].ConnectionId == ConnectionId ) {
index = i;
break;
}
if( index >= 0 ) {
s_list.RemoveAt(index);
SaveListToFile();
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
public static void UpdateConnection(ConnectionInfo info)
{
EnsureListLoaded();
ConnectionInfo exist = s_list.FirstOrDefault(x => x.ConnectionId == info.ConnectionId);
if( exist != null ) {
exist.ServerIP = info.ServerIP;
exist.UserName = info.UserName;
exist.Password = info.Password;
exist.SSPI = info.SSPI;
// 注意:其它沒列出的成員,表示不需要在此更新。
SaveListToFile();
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
public static ConnectionInfo GetConnectionInfoById(string connectionId, bool increasePriority)
{
if( string.IsNullOrEmpty(connectionId) )
throw new ArgumentNullException("connectionId");
EnsureListLoaded();
ConnectionInfo exist = s_list.FirstOrDefault(x => x.ConnectionId == connectionId);
if( exist == null )
throw new MyMessageException("connectionId is invalid.");
if( increasePriority ) {
exist.Priority++;
SaveListToFile();
}
return exist;
}
private static void EnsureListLoaded()
{
if( s_list == null ) {
try {
s_list = XmlHelper.XmlDeserializeFromFile<List<ConnectionInfo>>(s_savePath, DefaultEncoding);
}
catch {
s_list = new List<ConnectionInfo>();
}
}
}
private static void SaveListToFile()
{
if( s_list == null || s_list.Count == 0 ) {
try {
File.Delete(s_savePath);
}
catch { }
}
else {
XmlHelper.XmlSerializeToFile(s_list, s_savePath, DefaultEncoding);
}
}
}
服務端的Action實現代碼:
[Action]
public object GetAllConnectionInfo()
{
List<ConnectionInfo> list = ConnectionManager.GetList();
ConnectionInfoDataGridJsonResult result = new ConnectionInfoDataGridJsonResult();
result.total = list.Count;
result.rows = list;
return new JsonResult(result);
}
[Action]
public string SubmitConnectionInfo(ConnectionInfo info)
{
if( string.IsNullOrEmpty(info.ServerIP) )
throw new MyMessageException("ServerIP is empty.");
if( info.SSPI == false && string.IsNullOrEmpty(info.UserName) )
throw new MyMessageException("UserName is empty.");
bool isAdd = string.IsNullOrEmpty(info.ConnectionId);
if( isAdd ) {
info.ConnectionId = Guid.NewGuid().ToString();
ConnectionManager.AddConnection(info);
return info.ConnectionId;
}
else {
ConnectionManager.UpdateConnection(info);
return "update OK";
}
}
[Action]
public void DeleteConnection(string connectionId)
{
if( string.IsNullOrEmpty(connectionId) )
throw new MyMessageException("connectionId is empty.");
ConnectionManager.RemoveConnection(connectionId);
}
[Action]
public string TestConnection(ConnectionInfo info)
{
BaseBLL instance = BaseBLL.GetInstance(null);
return instance.TestConnection(info);
}
Database 瀏覽器
在主界面的【數據庫連接列表】中,選擇一個連接,然后點擊工具欄上的【打開連接】按鍵,即可進入【Database 瀏覽器】界面。

在這個工具中,如果需要查看某個數據庫對象的定義,只需要點擊相應的對象節點就可以了:

為了操作方便,工具提供多標簽查看功能:

獲取數據庫對象列表的關鍵代碼:
private static readonly string s_QueryDatabaseListScript =
"SELECT dtb.name AS [Database_Name] FROM master.sys.databases AS dtb " +
"WHERE (CAST(case when dtb.name in ('master','model','msdb','tempdb') then 1 else dtb.is_distributor end AS bit)=0 " +
" and CAST(isnull(dtb.source_database_id, 0) AS bit)=0) " +
"ORDER BY [Database_Name] ASC";
protected override List<string> GetDatabaseNames(DbConnection connection)
{
return ExecuteQueryToStringList(connection, s_QueryDatabaseListScript);
}
private static readonly string s_GetObjectNamesFormat =
"select name from ( SELECT obj.name AS [Name], " +
"CAST( case when obj.is_ms_shipped = 1 then 1 " +
" when ( select major_id from sys.extended_properties " +
" where major_id = obj.object_id and minor_id = 0 and class = 1 and name = N'microsoft_database_tools_support') " +
" is not null then 1 else 0 " +
"end AS bit) AS [IsSystemObject] " +
"FROM sys.all_objects AS obj where obj.type in ({0}) )as tables where [IsSystemObject] = 0 ORDER BY [Name] ASC ";
private static readonly string s_ProcedureType = " N'P', N'PC' ";
private static readonly string s_FunctionType = " N'FN', N'IF', N'TF', N'FS', N'FT' ";
private static readonly string s_TableType = " N'U' ";
private static readonly string s_ViewType = " N'V' ";
protected override List<string> GetDbProcedureNames(DbConnection connection)
{
//string sql = "select name from sys.objects where type='P' order by name";
string sql = string.Format(s_GetObjectNamesFormat, s_ProcedureType);
return ExecuteQueryToStringList(connection, sql);
}
protected override List<string> GetDbFunctionNames(DbConnection connection)
{
//string sql = "select name from sys.objects where type='FN' order by name";
string sql = string.Format(s_GetObjectNamesFormat, s_FunctionType);
return ExecuteQueryToStringList(connection, sql);
}
protected override List<string> GetDbTableNames(DbConnection connection)
{
//string sql = "select name from sys.objects where type='U' where name != 'sysdiagrams' order by name";
string sql = string.Format(s_GetObjectNamesFormat, s_TableType);
return ExecuteQueryToStringList(connection, sql);
}
protected override List<string> GetDbViewNames(DbConnection connection)
{
//string sql = "select name from sys.objects where type='V' order by name";
string sql = string.Format(s_GetObjectNamesFormat, s_ViewType);
return ExecuteQueryToStringList(connection, sql);
}
查看數據庫對象的定義腳本的實現代碼:
protected override ItemCode GetProcedureItem(DbConnection connection, string name)
{
string query = string.Format("SELECT definition FROM sys.sql_modules JOIN sys.objects ON sys.sql_modules.object_id = sys.objects.object_id AND type in ({1}) and name = '{0}'", name, s_ProcedureType);
string script = TryExecuteQuery(connection, query);
return new ItemCode(name, ItemType.Procedure, script);
}
protected override ItemCode GetFunctionItem(DbConnection connection, string name)
{
string query = string.Format("SELECT definition FROM sys.sql_modules JOIN sys.objects ON sys.sql_modules.object_id = sys.objects.object_id AND type in ({1}) and name = '{0}'", name, s_FunctionType);
string script = TryExecuteQuery(connection, query);
return new ItemCode(name, ItemType.Function, script);
}
protected override ItemCode GetViewItem(DbConnection connection, string name)
{
string query = string.Format("SELECT definition FROM sys.sql_modules JOIN sys.objects ON sys.sql_modules.object_id = sys.objects.object_id AND type in ({1}) and name = '{0}'", name, s_ViewType);
string script = TryExecuteQuery(connection, query);
return new ItemCode(name, ItemType.View, script);
}
protected override ItemCode GetTableItem(DbConnection connection, string name)
{
string script = null;
try {
script = SmoHelper.ScriptTable(connection, null, name);
if( string.IsNullOrEmpty(script) )
script = s_CannotGetScript;
}
catch( Exception ex ) {
script = ex.Message;
}
return new ItemCode(name, ItemType.Table, script);
}
搜索數據庫
您可以在上圖所示界面的左邊樹控件中,選擇一個節點,右擊,然后選擇“在數據庫中搜索”,此時會出現如下對話框:

在上圖的對話框中,點擊確定按鍵后,可出現下面的查找結果:
說明:匹配行就會高亮顯示。

搜索數據庫對象的相關代碼:
[Action]
public object SearchDB(string connectionId, string dbName, string searchWord,
int wholeMatch, int caseSensitive, string searchScope, string limitCount)
{
if( string.IsNullOrEmpty(searchWord) )
throw new ArgumentNullException("searchWord");
BaseBLL instance = BaseBLL.GetInstance(connectionId);
DbOjbectType types = CompareDBHelper.GetDbOjbectTypeByFlag(searchScope);
List<ItemCode> list = instance.GetDbAllObjectScript(instance.ConnectionInfo, dbName, types);
List<SearchResultItem> result = new List<SearchResultItem>(list.Count);
int limitResultCount = 0;
int.TryParse(limitCount, out limitResultCount);
FishWebLib.StringSearcher searcher =
FishWebLib.StringSearcher.GetStringSearcher(searchWord, (wholeMatch == 1), (caseSensitive == 1));
foreach( ItemCode code in list ) {
if( limitResultCount != 0 && result.Count >= limitResultCount )
break;
if( code.SqlScript.IndexOf(searchWord, StringComparison.OrdinalIgnoreCase) >= 0 ) {
string[] lines = instance.SplitCodeToLineArray(code.SqlScript);
for( int i = 0; i < lines.Length; i++ )
if( searcher.IsMatch(lines[i]) ) {
SearchResultItem item = new SearchResultItem();
item.LineNumber = i + 1;
item.ObjectName = code.Name;
item.ObjectType = code.Type.ToString();
item.SqlScript = code.SqlScript;
result.Add(item);
break;
}
}
}
return new JsonResult(result);
}
public List<ItemCode> GetDbAllObjectScript(ConnectionInfo info, string dbName, DbOjbectType type)
{
List<ItemCode> list = new List<ItemCode>();
string connectionString = GetDbConnectionString(info, dbName);
using( DbConnection connection = CreateConnection(connectionString) ) {
connection.Open();
if( (type & DbOjbectType.Table) == DbOjbectType.Table ) {
List<string> nameList = GetDbTableNames(connection);
foreach( string name in nameList )
list.Add(GetTableItem(connection, name));
}
if( (type & DbOjbectType.Procedure) == DbOjbectType.Procedure ) {
List<string> nameList = GetDbProcedureNames(connection);
foreach( string name in nameList )
list.Add(GetProcedureItem(connection, name));
}
if( (type & DbOjbectType.Function) == DbOjbectType.Function ) {
List<string> nameList = GetDbFunctionNames(connection);
foreach( string name in nameList )
list.Add(GetFunctionItem(connection, name));
}
if( (type & DbOjbectType.View) == DbOjbectType.View ) {
List<string> nameList = GetDbViewNames(connection);
foreach( string name in nameList )
list.Add(GetViewItem(connection, name));
}
}
return list;
}
復制存儲過程工具
為了演示這個功能,先需要創建一個數據庫。我創建了一個數據庫:TestMyTool,它沒有任何數據庫對象,如下圖

然后,從主界面中啟動【復制存儲過程工具】,
接着選擇:數據庫連接,數據庫對象,
點擊【刷新列表】按鍵,將看到以下結果:
我們可以選擇要復制的(存儲過程,視圖,自定義函數)對象:

最后點擊【開始復制】按鍵,即可完成復制過程。
此時數據庫TestMyTool的顯示結果為:

此功能的核心部分實現代碼:
[Action]
public string CopyProcedures(string srcConnId, string destConnId, string srcDB, string destDB,
string spNames, string viewNames, string funcNames)
{
BaseBLL instance1 = BaseBLL.GetInstance(srcConnId);
BaseBLL instance2 = BaseBLL.GetInstance(destConnId);
if( instance1.GetType() != instance2.GetType() )
throw new Exception("數據庫的種類不一致,不能執行復制操作。");
if( srcConnId == destConnId && srcDB == destDB )
throw new Exception("無效的操作。");
List<ItemCode> procedures = instance1.GetDbAllObjectScript(instance1.ConnectionInfo, srcDB, spNames, viewNames, funcNames);
return instance2.UpdateProcedures(instance2.ConnectionInfo, destDB, procedures);
}
public List<ItemCode> GetDbAllObjectScript(ConnectionInfo info,
string dbName, string spNames, string viewNames, string funcNames)
{
List<ItemCode> list = new List<ItemCode>();
string connectionString = GetDbConnectionString(info, dbName);
using( DbConnection connection = CreateConnection(connectionString) ) {
connection.Open();
if( string.IsNullOrEmpty(spNames) == false ) {
foreach( string name in spNames.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries) )
list.Add(GetProcedureItem(connection, name));
}
if( string.IsNullOrEmpty(funcNames) == false ) {
foreach( string name in funcNames.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries) )
list.Add(GetFunctionItem(connection, name));
}
if( string.IsNullOrEmpty(viewNames) == false ) {
foreach( string name in viewNames.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries) )
list.Add(GetViewItem(connection, name));
}
}
return list;
}
public override string UpdateProcedures(ConnectionInfo info, string dbName, List<ItemCode> list)
{
string connectionString = GetDbConnectionString(info, dbName);
using( DbConnection connection = CreateConnection(connectionString) ) {
connection.Open();
DbCommand command = connection.CreateCommand();
foreach(ItemCode item in list) {
command.CommandText = GetDeleteObjectScript(item.Name, item.Type);
command.ExecuteNonQuery();
command.CommandText = string.Format(s_CreateObjectFormat, item.SqlScript.Replace("'", "''"));
command.ExecuteNonQuery();
}
}
return string.Format("操作成功,共復制了 {0} 個對象。", list.Count);
}
數據庫比較工具
為了方便后面的介紹,我將復制全部的存儲過程到TestMyTool,這個過程將省略貼圖。
不僅如此,我還對其中的一個存儲過程做了一點修改。

然后,在程序主界面中,啟動【數據庫比較工具】,
接着選擇:數據庫連接,數據庫對象,
點擊【開始比較數據庫】按鈕后,將能看到以下比較結果。
每個數據庫對象的定義中,第一個不匹配的行將以高亮行顯示。

為了能讓您知道不匹配行的出現位置,工具還會顯示不匹配行的前后5行代碼。
此功能的核心部分實現代碼:
internal static class CompareDBHelper
{
public sealed class ThreadParam
{
public BaseBLL Instance;
public string DbName;
public DbOjbectType DbOjbectType;
public List<ItemCode> Result;
public Exception Exception;
public ThreadParam(BaseBLL instance, string dbName, DbOjbectType type)
{
this.Instance = instance;
this.DbName = dbName;
this.DbOjbectType = type;
this.Result = new List<ItemCode>();
}
}
private static void ThreadWorkAction(object obj)
{
ThreadParam param = (ThreadParam)obj;
try {
param.Result = param.Instance.GetDbAllObjectScript(param.Instance.ConnectionInfo, param.DbName, param.DbOjbectType);
}
catch( Exception ex ) {
param.Exception = ex;
}
}
public static DbOjbectType GetDbOjbectTypeByFlag(string flag)
{
if( string.IsNullOrEmpty(flag) )
return DbOjbectType.None;
DbOjbectType types = DbOjbectType.None;
if( flag.IndexOf('T') >= 0 )
types |= DbOjbectType.Table;
if( flag.IndexOf('V') >= 0 )
types |= DbOjbectType.View;
if( flag.IndexOf('P') >= 0 )
types |= DbOjbectType.Procedure;
if( flag.IndexOf('F') >= 0 )
types |= DbOjbectType.Function;
return types;
}
public static List<CompareResultItem> CompareDB(string srcConnId, string destConnId, string srcDB, string destDB, string flag)
{
BaseBLL instance1 = BaseBLL.GetInstance(srcConnId);
BaseBLL instance2 = BaseBLL.GetInstance(destConnId);
if( instance1.GetType() != instance2.GetType() )
throw new Exception("數據庫的種類不一致,比較沒有意義。");
DbOjbectType types = GetDbOjbectTypeByFlag(flag);
ThreadParam param1 = new ThreadParam(instance1, srcDB, types);
ThreadParam param2 = new ThreadParam(instance2, destDB, types);
Thread thread1 = new Thread(ThreadWorkAction);
Thread thread2 = new Thread(ThreadWorkAction);
thread1.Start(param1);
thread2.Start(param2);
thread1.Join();
thread2.Join();
if( param1.Exception != null )
throw param1.Exception;
if( param2.Exception != null )
throw param2.Exception;
List<ItemCode> list1 = param1.Result;
List<ItemCode> list2 = param2.Result;
List<CompareResultItem> result = new List<CompareResultItem>();
ItemCode dest = null;
// 按數據庫對象類別分次比較。
for( int typeIndex = 0; typeIndex < 4; typeIndex++ ) {
ItemType currentType = (ItemType)typeIndex;
foreach( ItemCode item1 in list1 ) {
// 如果不是當前要比較的對象類別,則跳過。
if( item1.Type != currentType )
continue;
dest = null;
foreach( ItemCode item2 in list2 ) {
if( item1.Type == item2.Type && string.Compare(item1.Name, item2.Name, true) == 0 ) {
dest = item2;
break;
}
}
if( dest == null ) {
CompareResultItem cri = new CompareResultItem();
cri.ObjectType = item1.TypeText;
cri.ObjectName = item1.Name;
cri.LineNumber = -1;
cri.SrcLine = string.Empty;
cri.DestLine = string.Empty;
cri.Reason = "源數據庫中存在,而目標數據庫中不存在。";
result.Add(cri);
continue;
}
else {
if( item1.SqlScript == dest.SqlScript )
continue;
// 開始比較代碼了。
CompareResultItem cri = null;
string[] lines1 = instance1.SplitCodeToLineArray(item1.SqlScript);
string[] lines2 = instance1.SplitCodeToLineArray(dest.SqlScript);
for( int i = 0; i < lines1.Length; i++ ) {
if( i >= lines2.Length ) {
// 目標對象的代碼行數比較少
cri = new CompareResultItem();
cri.ObjectType = item1.TypeText;
cri.ObjectName = item1.Name;
cri.LineNumber = i + 1;
GetNearLines(lines1, lines2, i, cri);
cri.Reason = "目標對象中已沒有對應行數的代碼。";
result.Add(cri);
break;
}
string s1 = lines1[i].Trim();
string s2 = lines2[i].Trim();
if( string.Compare(s1, s2, true) != 0 ) {
cri = new CompareResultItem();
cri.ObjectType = item1.TypeText;
cri.ObjectName = item1.Name;
cri.LineNumber = i + 1;
GetNearLines(lines1, lines2, i, cri);
cri.Reason = "代碼不一致。";
result.Add(cri);
break;
}
}
if( cri != null )
continue; // 比較下一個對象
if( lines2.Length > lines1.Length ) {
// 目標對象的代碼行數比較少
cri = new CompareResultItem();
cri.ObjectType = item1.TypeText;
cri.ObjectName = item1.Name;
cri.LineNumber = lines1.Length + 1;
GetNearLines(lines1, lines2, lines1.Length, cri);
cri.Reason = "源對象中已沒有對應行數的代碼。";
result.Add(cri);
break;
}
}
}
foreach( ItemCode item2 in list2 ) {
// 如果不是當前要比較的對象類別,則跳過。
if( item2.Type != currentType )
continue;
dest = null;
foreach( ItemCode item1 in list1 ) {
if( item1.Type == item2.Type && string.Compare(item1.Name, item2.Name, true) == 0 ) {
dest = item2;
break;
}
}
if( dest == null ) {
CompareResultItem cri = new CompareResultItem();
cri.ObjectType = item2.TypeText;
cri.ObjectName = item2.Name;
cri.LineNumber = -2;
cri.SrcLine = string.Empty;
cri.DestLine = string.Empty;
cri.Reason = "目標數據庫中存在,而源數據庫中不存在。";
result.Add(cri);
continue;
}
}
}
return result;
}
private static void GetNearLines(string[] lines1, string[] lines2, int index, CompareResultItem cri)
{
int firstLine;
cri.SrcLine = GetOneNearLines(lines1, index, out firstLine);
cri.SrcFirstLine = firstLine;
cri.DestLine = GetOneNearLines(lines2, index, out firstLine);
cri.DestFirstLine = firstLine;
}
private static string GetOneNearLines(string[] lines, int index, out int firstLine)
{
firstLine = -1;
System.Text.StringBuilder sb = new System.Text.StringBuilder();
int start = index - 5;
for( int i = 0; i < 11; i++ )
if( start + i >= 0 && start + i < lines.Length ) {
if( firstLine < 0 )
firstLine = start + i + 1;
sb.AppendLine(lines[start + i]);
}
return sb.ToString();
}
}
查看表結構定義
工具可以讓您輕松地查看一個表結構的定義:

也可以一次查看多個表的定義:

還可一下子得到整個數據庫的所有對象的創建腳本:

此功能的核心部分實現代碼:
public class AjaxDataTable
{
[Action]
public object TableDescribe(string connectionId, string dbName, string tableName)
{
if( string.IsNullOrEmpty(connectionId) || string.IsNullOrEmpty(dbName) || string.IsNullOrEmpty(tableName) )
throw new ArgumentException("connString or tableName is null.");
BaseBLL instance = BaseBLL.GetInstance(connectionId);
DataTable table = instance.GetTableFields(instance.ConnectionInfo, dbName, tableName);
return new MyMvcEx.DataTableResult(table);
}
[Action]
public object MultiTableDescribe(string connectionId, string dbName, string tableNames)
{
if( string.IsNullOrEmpty(connectionId) || string.IsNullOrEmpty(dbName) || string.IsNullOrEmpty(tableNames) )
throw new ArgumentException("connString or tableName is null.");
BaseBLL instance = BaseBLL.GetInstance(connectionId);
DataSet ds = instance.GetTables(instance.ConnectionInfo, dbName, tableNames);
return new MyMvcEx.DataSetResult(ds);
}
}
修改運行環境
到目前為止,這個工具還只能在Visual Studio中運行,顯然它與我們經常見到的【工具】有較大的差別。
如果您希望可以方便地運行這個工具,那么可以安裝我的另一個小工具來快速地啟動當前這個工具, 那個工具的下載地址:【ASP.NET程序也能像WinForm程序一樣運行】
然后,就可以在Windows資源管理器中啟動這個小工具:


現在是不是很像一個桌面程序了?

您甚至也可以創建一個開始菜單項,或者一個快捷方式來啟動這個小工具, 具體方法可參考博客:【ASP.NET程序也能像WinForm程序一樣運行】
關於此工具的補充說明
這個小工具只實現了一些簡單的功能,而且主要集中在查看數據庫的定義這塊。
這個工具的早期版本中,有些人提到了要求實現查看數據表的功能。
在今天的版本中,我並沒有實現,但我提供了實現這個功能所必要的一些基礎代碼。
例如,我提供了二個ActionResult (注意:前面小節的Action代碼中,就使用了下面的二個實現類):
public class DataTableResult : IActionResult
{
private DataTable _table;
public DataTableResult(DataTable table)
{
if( table == null )
throw new ArgumentNullException("table");
_table = table;
}
void IActionResult.Ouput(HttpContext context)
{
context.Response.ContentType = "text/html";
string html = DataTableHelper.TableToHtml(_table);
context.Response.Write(html);
}
}
public class DataSetResult : IActionResult
{
private DataSet _ds;
public DataSetResult(DataSet ds)
{
if( ds == null )
throw new ArgumentNullException("ds");
_ds = ds;
}
void IActionResult.Ouput(HttpContext context)
{
List<DataSetJsonItem> list = new List<DataSetJsonItem>();
for( int i = 0; i < _ds.Tables.Count; i++ ) {
DataTable table = _ds.Tables[i];
string html = DataTableHelper.TableToHtml(table);
list.Add(new DataSetJsonItem { TableName = table.TableName, Html = html });
}
JsonResult json = new JsonResult(list);
(json as IActionResult).Ouput(context);
}
public class DataSetJsonItem
{
public string TableName;
public string Html;
}
}
public static class DataTableHelper
{
public static string TableToHtml(DataTable table)
{
if( table == null )
throw new ArgumentNullException("table");
StringBuilder html = new StringBuilder();
html.AppendLine("<table cellpadding=\"2\" cellspacing=\"1\" class=\"myGridVew\"><thead><tr>");
for( int i = 0; i < table.Columns.Count; i++ )
html.AppendFormat("<th>{0}</th>", HttpUtility.HtmlEncode(table.Columns[i].ColumnName));
html.AppendLine("</tr></thead><tbody>");
object cell = null;
for( int j = 0; j < table.Rows.Count; j++ ) {
html.AppendLine("<tr>");
for( int i = 0; i < table.Columns.Count; i++ ) {
cell = table.Rows[j][i];
if( cell == null || DBNull.Value.Equals(cell) )
html.Append("<td></td>");
else
html.AppendFormat("<td>{0}</td>", HttpUtility.HtmlEncode(cell.ToString()));
}
html.AppendLine("</tr>");
}
html.AppendLine("</tbody></table>");
return html.ToString();
}
}
為了方便地在客戶端將表格顯示地漂亮些,我還提供了一個JS函數:
function SetGridViewColor(){
$("table.myGridVew").each(function(){
$(this).removeClass("myGridVew").addClass("GridView")
.find(">thead>tr").addClass("GridView_HeaderStyle").end()
.find(">tbody>tr")
.filter(':odd').addClass("GridView_AlternatingRowStyle").end()
.filter(':even').addClass("GridView_RowStyle");
});
}
如果您認為很有必要在這個工具中集成數據表的查看(或者查詢數據)的功能,那么可以自行實現(工具是開源的)。
友情提示:使用上面的代碼以及MyMVC框架,實現一個簡單的查看數據是會比較容易的。
今天就寫到這里,希望大家能喜歡這個小工具,以及 MyMVC框架。
點擊此處下載全部代碼
如果,您認為閱讀這篇博客讓您有些收獲,不妨點擊一下右下角的【推薦】按鈕。
如果,您希望更容易地發現我的新博客,不妨點擊一下右下角的【關注 Fish Li】。
因為,我的寫作熱情也離不開您的肯定支持。
感謝您的閱讀,如果您對我的博客所講述的內容有興趣,請繼續關注我的后續博客,我是Fish Li 。