65.9K
CodeProject 正在变化。 阅读更多。
Home

通过Web应用程序动态访问数据库

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.70/5 (19投票s)

2002年9月6日

8分钟阅读

viewsIcon

288916

downloadIcon

4022

使用 ASP.NET Web 应用程序远程访问您的数据库。

引言

在异地维护数据库可能会很困难,尤其是在您不在家的时候。我想学习 C# 和 ASP.NET,于是我创建了一个 .NET Web 服务,利用 OLE DB 接口,让您可以动态地查看和编辑数据库!

它允许您:-

  • 登录到数据库服务器(例如 MS SQL、Access、Oracle)。
  • 动态构建连接字符串。
  • 获取数据库或模式列表。
  • 获取表、视图或存储过程列表。
  • C++ 调用提供程序尚未完全实现。
  • 查看、编辑、插入、删除和排序条目。
  • 编辑模式下动态添加的复选框。
  • 快速访问另一个数据库/表。

登录网页

OLE DB 连接字符串是从 UserID 和 Password 输入字段、ProviderListItem 框以及 Server/Filename 'INPUT' 字段构建的。我在 The Provider ListItem Collection Editor 的 'Value' 属性中添加了一个额外的字段。如果需要向连接字符串添加一个额外的参数(例如,Oracle 需要 'OLEDB.NET=TRUE'),则将其插入到提供程序标签之后,并用 '|' 字符分隔。Server/Filename 具有一个浏览按钮,允许浏览 MDB 文件。出于安全原因,Server/Filename 'INPUT' 和 Password 字段不会保存其值。

连接字符串是从 DLL WebAppUtil 中的 C++ 例程 DBConnstr() 的 API 调用派生的。这段代码是从帮助文档中的一些示例代码修改而来的。它获取提供程序的 CSLIDIDBProperties 填充 User ID、Password 和 Server/Filename;设置 DBPROMPT_NOPROMPT 值以防止 OLE DB 连接对话框弹出。使用 IDataInitialize 创建数据库实例,然后用 IDBProperties 指针查询接口。设置 IDBProperties 属性,初始化 IDBInitialize 属性,然后提取初始化字符串。这样就可以为每个接口动态生成连接字符串。getHRtext() 例程也来自帮助文档,并在发生错误时从 HRESULT 返回码中提取文本消息。错误消息通过使用两个页面上的 HIDDEN 字段,并由 HTML 代码中的 Java 脚本调用,显示在弹出窗口中。

Get Databases/Schemas 按钮将获取服务器上可用的数据库/模式列表,并将其插入到 DBDropDownList 框中。在填充 DropDown ListBox 之前,需要确定是否支持模式(Schema)或目录(Catalog)。代码首先检查 Catalog 名称,因为 Catalog 并非总是被支持。如果支持 Catalog,则使用 Catalog,否则将 Schema 加载到 DropDown Listbox 中。名称存储在字符串数组中,然后存储在 Session 变量中,以用于页面重新加载和往返。

OleDbConnection statConn = new OleDbConnection(connStr);
...

statConn.Open();
// get schema

schema = statConn.GetOleDbSchemaTable(
    OleDbSchemaGuid.Schemata,new object[] {null});
// check if Databases supported		

if (schema.Rows[0]["CATALOG_NAME"].ToString() != "")  
{   // there are catalogs, use instead of schema

    DB = statConn.GetOleDbSchemaTable(OleDbSchemaGuid.Catalogs,
                                      new object[] {null});
    LBNames = new string[DB.Rows.Count];    
    // insert Catalog names into drop down list 

    // and string array for Session variable

    while (j < DB.Rows.Count) 
    {	
       LBNames[j] = DB.Rows[j]["CATALOG_NAME"].ToString();
       DBDropDownList.Items.Add(LBNames[j++]);
    }
    ...
}
else    // use schema names

{   
    // need to re-inquire with user name

    schema.Dispose();	
    schema = statConn.GetOleDbSchemaTable(OleDbSchemaGuid.Schemata,
                new object[] {null,UserIDTextBox.Text.ToString()});

    // insert Schema names into drop down list 

    // and string array for Session variable

    LBNames = new string[schema.Rows.Count];
    while (j < schema.Rows.Count) 
    {
        LBNames[j] = schema.Rows[j]["SCHEMA_NAME"].ToString();
        DBDropDownList.Items.Add(LBNames[j++]);
    }
    ...
}
Session["myDBs"] = LBNames; // saved for return trip

“Select Item”RadioButtonList 会导致“Get ITEM”按钮的文本以及 ItemsDropDownList 的 Label 发生变化。所选的项目决定了将调用哪个 API 来填充 ItemsDropDownListArgumentsTextBox。当显示存储过程参数时,每个文本字段都应该被一个参数替换。

“use C++ code to get Item info”复选框会导致“Get ITEMS”按钮使用 DLL 中的 C++ 例程,即 DBGetTables()DBGetViews()DBGetProcs()。“Execute”按钮使用 DBGetIndexes()DBQuote()。一些提供程序(例如 MS Jet)不支持所有新 API,似乎是这样。这些例程使用 CTableCViewCProceduresCProcedureParametersCIndexes 类来获取信息。此外,还使用了 CComQIPtr<span class="code-keyword"></span><span class="code-keyword">IDBInfo</span><span class="code-keyword"></span> 模板的 GetLiteralInfo() 来获取引号字符。我将把这段代码留给您探索。

“Get ITEMS”按钮将获取该数据库/模式下可用的项目(例如表、视图或存储过程)列表,并将其插入到 ItemsDropDownList 框中。代码首先获取引号后缀和前缀字符,然后获取项目列表。此时 Catalog 或 Schema 名称之一为 null。如果项目名称中包含空格,则会添加引号字符。如果选中了“System Tables”复选框,则仅检索系统项目(存储过程除外)。

queryType = TypeRadioButtonList.SelectedItem.ToString();
...

// get DB's Quote chars

schemaTable = statConn.GetOleDbSchemaTable(
                        OleDbSchemaGuid.DbInfoLiterals,null);
DataRow[] dr = schemaTable.Select("LiteralName LIKE 'Quote_*'");
quotePrefix = dr[0]["LiteralValue"].ToString();
quoteSuffix = dr[1]["LiteralValue"].ToString();
Session["myquoteprefix"] = quotePrefix;
Session["myquotesuffix"] = quoteSuffix;
schemaTable.Dispose();

// get item names

switch (queryType)
{
    case "Table" :
        columnName = "TABLE_NAME";
        schemaTable = statConn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables,
                       new object[] {selectedDB,selectedSchema,null, 
                        SysTblsCheckBox.Checked ? "SYSTEM TABLE" : "TABLE"});
        break;
    case "View" :
        columnName = "TABLE_NAME";
        schemaTable = statConn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables,
                       new object[] {selectedDB,selectedSchema,null,
                        SysTblsCheckBox.Checked ? "SYSTEM VIEW" : "VIEW"});
        break;
    case "Procedure" :
        columnName = "PROCEDURE_NAME";
        schemaTable = statConn.GetOleDbSchemaTable(OleDbSchemaGuid.Procedures,
                       new object[] {selectedDB,selectedSchema,null,null});
        break;
}
items = new string[schemaTable.Rows.Count];
int i = 0;
while (i < schemaTable.Rows.Count) 
{	
   items[i] = schemaTable.Rows[i][columnName].ToString();
   i++;
}
...
// fill in Item DD listbox

for (int i = 0; i < items.Length; i++) 
{
    itemName = items[i];
    if (queryType == "Procedure")
    {// it item name has a semicolon, reformat it

        index = itemName.IndexOf(';');
        if (index >= 0) 
            itemName = itemName.Substring(0,index);
    }
    // it item name has a space, reformat it

    index = itemName.IndexOf(' ');
    if (index >= 0)
        itemName = quotePrefix + itemName + quotePrefix;
    ItemsDropDownList.Items.Add(itemName);
    // save item names for return to page

    LBNames[i] = itemName;
}
itemName = ItemsDropDownList.Items[0].ToString();
ArgumentsTextBox.Text = "";
procParmNames = null;
switch (queryType)
{
    case "Table" :
    case "View" :						
        itemName = ItemsDropDownList.SelectedItem.Text;
        SQLstatement = "SELECT * FROM " + itemName;
        ArgumentsTextBox.Text = SQLstatement;
        ArgumentsLabel.Text = "SQL Statement";
        break;
    case "Procedure" :
        GetProcedureParms();
        break;
}
...

schemaTable.Dispose();

“Execute”按钮会在项目中查找一个键,并加载 DataWebForm 页面。如果未找到键,则 Insert DataGrid 不会显示,“Update”和“Delete”按钮在“DataWebForm”页面上将不执行任何操作。如果一个键字段返回两个或更多键名,它们将用 ',' 分隔,只使用第一个。如果索引名称中包含空格,则会添加引号字符。

// find primary else secondary index name

indexName = "";
schemaTable = indexConn.GetOleDbSchemaTable(OleDbSchemaGuid.Primary_Keys,
                new object[] {selectedDB,selectedSchema,itemName});
if (schemaTable.Rows.Count > 0) 
    indexName = schemaTable.Rows[0]["COLUMN_NAME"].ToString();
else
    indexName = "";
schemaTable.Dispose();
...

// remove multiple keys, only need one key 'cause 

// we keep track of individual rows

index = indexName.IndexOf(',');
if (index >= 0)
    indexName = indexName.Remove(index,indexName.Length - index);
// it KEY name has a space, reformat it

    index = indexName.IndexOf(' ');
if (index >= 0)
    indexName = quotePrefix + indexName + quotePrefix;  
...

// now display second web page

Server.Transfer("DataWebForm.aspx");

SQL 语句或存储过程参数在每次更改项目选择时生成。AutoPostBack 设置为 True,导致调用 ItemsDropDownList_SelectedIndexChanged() 例程,在那里会重建 Select 语句/存储过程参数。可以修改 SQL 语句以在按下“Execute”按钮之前限制查询,但请自行承担风险。显示存储过程参数时,每个文本字段都应该被一个参数替换。

错误弹出窗口

HTML 中有一个错误弹出窗口,嵌入在 Java 脚本中,它利用了两个页面上的隐藏文本字段。如果字段中有文本,则会显示错误框。

数据网页

有两个 DataGrid,一个用于插入新记录,另一个用于编辑或删除记录。还有一个只读字段代表用于构建网格的 SQL 语句/存储过程。Edit/Delete DataGrid 从一个 OleDataAdapter 填充,该 OleDataAdapter 使用来自 Logon 页面的 SQL 语句或存储过程进行初始化。Insert、Update 和 Delete 命令是使用 OleCommandBuilder 动态构建的,前提是在 LogonWebPage 上找到了索引。引号字符从 Logon 页面检索并设置到 OleCommandBuilder 中。

custCB.QuotePrefix = sourcepage.GetQuotePrefix;
custCB.QuoteSuffix = sourcepage.GetQuoteSuffix;
// create and set dynamically Insert, Update & Delete commands !!

oleDbDataAdapter.InsertCommand = custCB.GetInsertCommand();
oleDbDataAdapter.UpdateCommand = custCB.GetUpdateCommand();
oleDbDataAdapter.DeleteCommand = custCB.GetDeleteCommand();

Update 按钮通过将当前页码乘以分页大小并加上行索引来计算数据集中的偏移量。在遍历该行的每一列数据时,会检查控件类型,看它是否是 "System.Web.UI.WebControls.TextBox""System.Web.UI.WebControls.CheckBox",以便正确地从 TextBoxCheckBox 中提取数据。当数据被放入 DataRow 时,未在 DataGrid 中显示的字段(例如二进制、图像、sql_variant、timestamp、uniqueID、varbinary)将被跳过,它们的数据保持不变。如果行已更改,则执行更新。请注意,现在 DataRow(用于更新)是从 DataViewManager 获取的,因为排序视图在那里找到。

...
int dsItemIndexOffset = (DataGridDB.CurrentPageIndex * 
                          DataGridDB.PageSize) 
                         + e.Item.ItemIndex;

// Gets the value of the key field 

// of the row being updated

string key = DataGridDB.DataKeys[e.Item.ItemIndex].ToString();
...

TextBox tb;

// get original row from the view for update

DataRow dr = dataSet.DefaultViewManager.DataSet.Tables 
              [0].DefaultView[dsItemIndexOffset].Row;
// check type, looking for checkbox

for (int i = dbTableColumnOffset; i < e.Item.Cells.Count; i++)
{
    string c = e.Item.Controls[i].Controls[0].GetType().ToString();
    if (c == "System.Web.UI.WebControls.TextBox") 
    {
        tb = (TextBox)(e.Item.Cells[i].Controls[0]);
        categoryField = tb.Text;
    }
    else if (c == "System.Web.UI.WebControls.CheckBox")
    {
        bool b = 
            ((CheckBox)(
            e.Item.Cells[i].Controls[0])).Checked;
        if (b)
            categoryField = "True";
        else
            categoryField = "False";
    }
    else
    {
        LogErrorMsg("Unkown field type in Grid");   
            // shouldn't happen

        return;
    }

    // check if there is a non supported field 

    // (better allow NULLS), 

    // if so, skip over it in the DataRow

    do 
    {   // loop until a supported row is found

        fieldType = dataSet.Tables[0].Columns 
                     [i-dbTableColumnOffset].DataType.ToString();
        if (fieldType != "System.Byte[]" && 
            fieldType != "System.Object" &&
            fieldType != "System.Guid")
            break;
        i++;
    } while(i < dr.Table.Columns.Count+dbTableColumnOffset);

    // update only if field has changed

    if (!dataSet.Tables[0].Rows[dsItemIndexOffset].ItemArray 
               i-dbTableColumnOffset].Equals(categoryField))
        dr[i-dbTableColumnOffset] = categoryField;
}

if (dataSet.HasChanges())   
{	 // then do update to DataSet and DB

    try 
    {
        oleDbDataAdapter.Update(dataSet);
        dataSet.AcceptChanges();
    }
    catch (Exception er) 
        ...

Insert DataGrid 使用相同的查询进行初始化,但不获取任何记录,这样做是为了动态检索列标题。将一个新的空白 DataRow 添加到其关联的数据集中,并将 Grid 置于编辑模式。Delete 按钮被启用但重命名为 Insert。Insert 按钮创建一个新的 DataRow,并将 Insert Grid 中的数据添加到 DataRow 中。它会跳过不支持的类型,因此这些数据元素需要支持 null!然后将新的 DataRow 插入到其他 DataGrids 的数据集中,并执行 Update。在 Grid 进入编辑模式时,会动态添加 Check-Boxes。查找 Grid 的列类型,并将 TextBox 替换为 CheckBoxDataGridDB_ItemCreated 在编辑结束时调用,但没有 DataItem,原因不明,但放入一个虚拟 CheckBox 可以防止其崩溃。

private void DataGridDB_ItemCreated(object sender, 
    System.Web.UI.WebControls.DataGridItemEventArgs e)
{
   // checkbox only on edit

   if (e.Item.ItemType == ListItemType.EditItem ) 
   {
      // after an edit is completed or canceled, 

      // get called with no DataItem's

      // found that a dummy CheckBox inserted at 

      // the same offset keeps

      // everyone happy ???

      if (e.Item.DataItem == null)    
      {               
         DataSet ds = (DataSet)Session["mydataset"];
         int i = 0;
         do { // if there is a boolean field 

              // in the dataset, add to Controls

            if (ds.Tables[0].Columns[i].DataType.ToString() == 
                  "System.Boolean")
            {
               CheckBox cb = new CheckBox();
               e.Item.Controls[i+dbTableColumnOffset].Controls.Add(cb);
               e.Item.Controls[i+dbTableColumnOffset].Controls.RemoveAt(0);
            }
         } while(++i < ds.Tables[0].Columns.Count);
      }
      else    // normal

      { 
         for (int i = 0; 
              i < e.Item.Controls.Count-insertTableColumnOffset; 
              i++)
         {
            try
            {
               string itemType = dataSet.DefaultViewManager.
                                   DataViewSettings[0].Table.
                                   Columns[i].DataType.ToString();
               if (itemType == "System.Boolean")
               {
                  CheckBox cb = new CheckBox();
                  // put data field name in ID field 

                  // for identification during binddata

                  cb.ID = ((DataTable)((DataView)((DataRowView)e.Item.
                              DataItem).DataView).Table).Columns[i].
                              ColumnName.ToString();
                  cb.DataBinding += new EventHandler(this.BindCheckBoxData);
                  e.Item.Controls[i+insertTableColumnOffset].Controls.Add(cb);
                  e.Item.Controls[i+insertTableColumnOffset].Controls.
                   RemoveAt(0);
                }
            }
            catch (Exception er) 
            {
               LogException(er);
            }
         }
      }
        ...

}

this.BindCheckBoxData() 来自 Shaun Wildes 的 “为 DataGrid 添加 CheckBox 列” CodeProject。列名存储在复选框的 ID 字段中。

private void BindCheckBoxData(object sender, EventArgs e)
{
    CheckBox box = (CheckBox) sender;
    DataGridItem container = (DataGridItem) box.NamingContainer;
    box.Checked = false;
    string data = ((DataRowView)container.DataItem).ToString();
    Type t = ((DataRowView)
     container.DataItem).DataView.Table.Columns.DataType;
    if (data.Length>0)
    {
        switch (t.ToString())
        {
        case "System.Boolean":
            if (( data == "True") || (data == "true"))
            {
                box.Checked = true;
            }
            break;
        default:
            break;
        }
    }
}

排序:点击列名会调用 DataGridDB_SortCommand(),然后将 Sort 字段设置在 DefaultViewManager 中。更新行时,必须通过 DefaultViewManager 找到该行,该管理器拥有匹配 DataGrid 中显示的数据的排序数据集,请参见“get original row from the view for update”注释下的 DataGridDB_UpdateCommand()

private void DataGridDB_SortCommand(...)
{
    // dispose of dataset when changing sort 

    // seems to have problems otherwise, 

    // remembers old sort or else I do

    dataSet.Dispose();
    dataSet = new DataSet();
    oleDbDataAdapter.Fill(dataSet);
    dataSet.DefaultViewManager.DataViewSettings[0].Sort = 
        e.SortExpression.ToString();
    DataGridDB.DataSource = dataSet;
    DataGridDB.DataBind();
    Session["mydataset"] = dataSet;
    Session["mydataadapter"] = oleDbDataAdapter;
}

有一个“New Table”链接,它会重新加载 Logon Page,并重新加载 Session 变量。

部署

包含了一个 WebSetup 项目。它是使用 Majid Shahabfar 的 “使用 Visual Studio .NET 项目部署 Web 应用程序” 创建的。

结论

这是我的第三个,也是可能最后的版本。我一直想把所有这些都变成一个模板,也许等这里的雪下下来的时候 :-D:。请注意,Oracle 提供程序对大小写敏感。您可以添加自己的提供程序到 Provider DropDown ListItems 中,不要忘记在 Value 字段末尾添加两个 '|'。如果您知道如何绕过我做的一些临时解决方法,请告诉我,我会修改代码。请告诉我您的想法。如果您认为本文和代码很有用,请务必投票!

历史

2002 年 9 月 14 日 - 更新了源代码。
2002 年 11 月 25 日 - 添加了 Insert DataGrid,可选的 Provider
2002 年 12 月 5 日 - 添加了视图、存储过程和排序

© . All rights reserved.