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

数据提取和操作

2007年4月24日

7分钟阅读

viewsIcon

33825

downloadIcon

388

从数据库中提取数据,操作提取的数据,然后更新数据库

Screenshot - pic1.jpg

引言

这篇文章源于我需要从数据库中提取数据,以某种方式操作数据,然后更新数据库。我本可以写一些短小精悍的代码来快速完成工作,事实上这个应用程序的开发时间并不长。但当我为我正在做的项目解决需求而制定算法(一个用来描述“逻辑流程”的词)时,我想我应该继续开发这个应用程序,向其他人展示如何使用一些基本技术。

背景

在我目前的工作项目中,我是一个团队的成员,我们正在开发一个大型公司数据库。该数据库不仅是一个业务数据库,也是一个GIS数据库。问题出在一个需要规范化的表上。由于该表中目前存在“多对多”关系,这个表需要将数据拆分为两个不同的表,再加上第三个表作为链接表。这是一个表的示例

tblNode
SiteNmNodeIDDeviceNodeNmSiteFuncIDNodeKeyStationKVolt
AUBREY1848 CB_M20CS-3, DSC_M20-3, DSC_M20CS-3_BYPASSAUBREY6803 ABR.138.0001ABR138
AUBREY1849 LD_LD_C, CB_M20CS-3, DSC_M20CS-3_BYPASSAUBREY6803 ABR.138.0002ABR138
AUBREY1850 LD_LD_B, CB_M20CS-2, DSC_M20CS-2_BYPASSAUBREY6803 ABR.138.2109ABR138
AUBREY1851 LD_LD_A, CB_M20CS-1, DSC_M20CS-1_BYPASSAUBREY6803 ABR.138.2110ABR138
AUBREY1852 CB_M20CS-2, DSC_M20-2, DSC_M20CS-2_BYPASSAUBREY6803 ABR.138.3819ABR138
AUBREY1853 DSC_M10, DSC_M20-2, DSC_M20-3, DSC_M60AUBREY6803 ABR.138.3820ABR138
AUBREY1854 CB_M20CS-1, DSC_M20CS-1_BYPASS, DSC_M30, DSC_M60AUBREY6803 ABR.138.3821ABR138
AUBREY1855 DSC_M10, DSC_M70VW, LN_ABR_RECTAUBREY6803 ABR.138.3830ABR138
AUBREY1856 DSC_M30, DSC_M70VW, LN_ABR_KRGRVAUBREY6803 ABR.138.3831ABR138

看看“CB_M20CS-3”是如何属于两个不同记录的。一些快速信息:我们正在处理设备(把它们想象成线路上的一个位置)和节点(把它们想象成线路)。这样说来,一个节点可以有许多不同的设备,设备也可以在许多不同的节点上。所以我们开始吧。

使用代码

应用程序的主要功能

我们的过程(或算法)将是

  1. 使用我们的三个表创建一个数据集
  2. 从“设备”列中检索信息
  3. 从该字符串中提取不同的设备
  4. 流程控制:第一个设备是否位于设备表中
    1. 是:使用设备ID和新节点ID更新链接表
    2. 否:在设备表中创建新记录,然后更新链接表
  5. 所有数据处理完毕后更新数据库。

现在,逻辑控制在方法ExtractDevices()中完成。在这里,我们调用以提取在List devices对象中返回的设备字符串。这是我们过程的第2步和第3步。然后我们继续第4步,然后循环直到数据被处理。这是控制方法的视图

private void ExtractDevices()
{            
    for (int i = 0; i < this.DS.Tables[0].Rows.Count; i++)
    {
        DataRow dr = this.DS.Tables[0].Rows[i];
        GetSingleDevices((string)dr["Device"]);

        for (int j = 0; j < this.devices.Count; j++)
        {
          string SQLFilter = "Station = '" + dr["Station"].ToString().Trim() + 
                "' AND DeviceNm = '" + this.devices[j].ToString().Trim() + "'";
            DataRow[] drcol = this.DS.Tables[1].Select(SQLFilter);
            //Filtering the datatable to find if the device is present

            if (drcol.Length > 0)
            {
                UpdateNodeDeviceLink(Convert.ToInt32(dr["NodeID"]), 
                    Convert.ToInt32(drcol[0]["DeviceID"]));
            }
            else
            {
                NewDevice(j, dr);
            }
        }
    }
}

数据库

我接下来要展示的两个方法调用了数据库支持。在DBLoadDataSet()中,一切都易于查看和使用。我将我的连接、适配器和命令构建器分组到一个类中。这还让我可以随时随地使用它们。然后在DBUpdateDataSet()中,我展示了如何使用object[]数组来传递多个参数。此外,使用命令构建器使编码变得更简单、更快捷。

public static void DBLoadDataSet(ref OleClass OleStuff, ref DataSet DS, 
    MySqlData SqlData, string TableName)
{
    OleStuff.OleConn = new OleDbConnection(SqlData.MyConnectionString);
    OleStuff.OleAdpt = new OleDbDataAdapter(SqlData.MySQLString, 
        OleStuff.OleConn);
    OleStuff.OleCommBld = new OleDbCommandBuilder(OleStuff.OleAdpt);
    OleStuff.OleAdpt.MissingSchemaAction = MissingSchemaAction.AddWithKey;
    OleStuff.OleAdpt.Fill(DS, TableName);
}

public static void DBUpdateDataSet(object[] DataObjects)
{
    OleClass OleStuff = (OleClass)DataObjects[0];
    DataTable DT = (DataTable)DataObjects[1];
    MySqlData SqlData = (MySqlData)DataObjects[2];

    OleStuff.OleAdpt = new 
        System.Data.OleDb.OleDbDataAdapter(SqlData.MySQLString, 
        OleStuff.OleConn);
    OleStuff.OleCommBld = new 
        System.Data.OleDb.OleDbCommandBuilder(OleStuff.OleAdpt);
    OleStuff.OleAdpt.InsertCommand = OleStuff.OleCommBld.GetInsertCommand();
    OleStuff.OleAdpt.InsertCommand.Connection = OleStuff.OleConn;

    OleStuff.OleAdpt.InsertCommand.Connection.Open();
    OleStuff.OleAdpt.Update(DT);
    OleStuff.OleAdpt.InsertCommand.Connection.Close();
}

相当简单的东西。所以我们再添加一点,以展示一些不同的东西是如何工作的。我添加了一个后台线程,它会持续更新表单上的一个标签,这样我们就可以看到多线程。此外,我还添加了委托和事件来更新表单上的文本框,同时我们正在运行算法。至于数据库 - 实际数据存储在SQL服务器上,但对于本文,我已将数据导入Access。对于Ole代码,我将使用我在上一篇文章中编写的库(点击此处)。我在此库中添加了两个方法和一个类,这些是第一个版本中没有的。

多线程

在这里,我创建了一个新线程,当它启动时,将在后台运行RunAnime方法。RunAnime方法将触发委托,该委托将使用Form.Invoke调用方法lblAnimeAddString来更改表单上的标签。我们使用“Invoke”是因为我们正在从一个未创建表单的线程更改表单。

public delegate void FormAnime(string Char);
public FormAnime FormAnimeDelegate;
private Thread AnimeThread;

private void SetUpExtractor()
{     
     …
     FormAnimeDelegate = new FormAnime(this.lblAnimeAddString); 
     AnimeThread = new Thread(new ThreadStart(RunAnime));
     this.Stop = false; 
     AnimeThread.Start();
}

private void lblAnimeAddString(string Char)
{
    lblAnime.Text = Char;
    lblAnime.Refresh();
}

更多委托和事件

Extraction类中,我为表单上的每个文本框创建了一个委托和事件,其中每个文本框都是为我们将要处理的数据创建的。现在,这些委托与多线程部分中使用的委托之间的区别在于,多线程委托在表单上调用(使某些事情发生)。在Extraction委托中,表单正在监听事件的触发,而之前它没有监听。这是一些代码的样子。

public class Extraction
{       
#region Delegates, Events, and Members
    public delegate void DeviceIDEventHandler(int DeviceID);
    public delegate void DeviceNameEventHandler(string DeviceName);
    public event DeviceIDEventHandler DeviceIDEvent;
    public event DeviceNameEventHandler DeviceNameEvent;    
#endregion
}

private void SetUpExtractor()
{        
    Extractor = new Extraction();        
    Extractor.DeviceIDEvent += new Extraction.DeviceIDEventHandler(
        Extractor_DeviceIDEvent);
    Extractor.DeviceNameEvent += new Extraction.DeviceNameEventHandler(
        Extractor_DeviceNameEvent);
    …
}

private void NewDevice(int Indexer, DataRow dr)
{        
    DataRow dr2 = this.DS.Tables[1].NewRow();        
    dr2["DeviceNm"] = this.devices[Indexer];        
    this._Device.DeviceName = this.devices[Indexer];        
    DeviceNameEvent(this._Device.DeviceName);
}

文件结构

让我解释一下我如何以及为什么使用主分部文件、设计分部文件和ExtractorSetup文件构建表单类。我这样做是为了演示代码的一些清晰度,并创建一种将相关代码分组到其自己的文件中的方法。所以你最终会得到一个文件来处理按钮和文本框事件等事情(这是发生类声明和Form继承的主文件)。然后你会有一个文件来设置设计并初始化所有组件。最后,是“ExtractorSetup”文件,其中包含我的所有方法、属性和成员。此外,我还有一个方法SetUpExtractor(),它在Initializecomponent()之后直接调用,它初始化我的所有组件。

关注点

就像我之前说的,我想写一篇演示一些基本技术的文章。您应该注意到的一件事是this的使用。我在我的Extraction类代码中使用了它,但我尽量不在其他类中使用它。this关键字指代类的任何部分或该(this)类中任何全局的东西。

MyDatabaseLibraryClass中值得注意的另一件事是MySqlDtaOleClass的使用。通过使用这些类,我将相关的对象和更逻辑地使用您的类进行分组。我尝试整合一些不必要的其他技术,但它们能让您看到我的工作。例如,在某些方法中使用关键字ref,这将获取对象并直接在被调用的方法中更改它。我还向您展示了在几个方法中如何使用object[] param。这样做是为了展示如何将多个对象传递给一个方法,而无需在方法声明中使用多个参数。

结论

嗯,我希望我能为一些基本技术提供一些启示。如果没什么别的,我希望这篇文章能给其他开发人员提供一些你可能知道但由于缺乏使用而记不住的东西的快速参考。我知道我经常需要做某种处理,但就是完全忘记了某个东西是如何完成的。

花点时间仔细阅读代码,如果您发现有什么地方不仅可以不同,而且可以做得更好,请告诉我。我还附带了一个Access数据库,其中包含此应用程序使用的表。没有安装/设置文件 - 您可以加载源代码或直接运行应用程序。感谢您的时间和上帝保佑。

© . All rights reserved.