一个用于显示、编辑和自动更新多对多关系的基类 Windows 窗体 - 第一部分






3.92/5 (6投票s)
2005年9月29日
5分钟阅读

43965

848
一个用于显示、编辑和自动更新多对多关系的基类 Windows 窗体。
引言
为了感谢我在 .NET 使用过程中所学习到的所有来自互联网的人,我希望能提供这个窗体,用于显示、编辑和自动更新数据库(Access)中的多对多关系。
我试图使其尽可能通用,以便其他人可以轻松地根据自己的需求进行改编。
这个窗体的第一个版本是使用 .NET 1.0 和 1.1 编写的,我遇到了许多问题。特别是,我希望窗体能够自动更新底层数据库,而无需按“提交”按钮,这对我来说似乎是倒退。在编写了使用 VBA for Access 的一些程序后,我体会到数据是立即更新的。[然而,我也理解 .NET 理念中的断开连接性质,并认识到应该有一种方式可以不立即提交更改。]
考虑到这一点,我想编写使用事件来按需触发更新的应用程序。 .NET 1.1 的 DataGrid
的原始结构设计似乎不足以完成这项任务。我不得不寻找变通方法,并深入研究货币管理器和其他一些我不太想涉及的复杂结构,而且我仍然觉得我原始代码中存在 bug(用于一个面向学校的商业在线评估系统)。
随着 .NET 2.0 和 DataGridView
的出现,我想知道设计是否有所增强,所以我决定重写我的代码。
我已经从我的新应用程序中剥离了所有不必要的代码和结构,这里是一个更纯粹的版本。
老实说,最初的代码是在大量试验、阅读海量文档、许多深夜和汗水之后创建的——我现在已经记不清为什么当时要做某些事情的细节了!我不再是专业程序员了,所以我不每天都做这些事,我会忘记。
我希望同事们能够优化代码并修正任何不当的使用,以便这个基础代码能对大家非常有益。
多对多关系是很常见的。在学校环境中,我们有许多老师和许多学生。学生可以属于许多老师所教的课程,而老师有许多学生班级。
所以 Moore 先生的班级可能有 John、Fred、Julie、Usha。Smith 女士的班级可能有 Fred、Usha 和 Ellen。
所以 Fred 和 Usha 都在 Moore 先生和 Smith 女士的班级里,而 John 只在 Moore 先生的班级里,Ellen 只在 Smith 女士的班级里。
为了在关系数据库中映射这一点,我们通常使用一个中间映射表。
Table A
AID AName
1 Mr Moore
2 Ms Smith
3
4
...
Table B
BID BName
1 John
2 Fred
3 Julie
4 Usha
5 Ellen
6
...
Table AB
ABID AID BID
1 1 1
2 1 2
3 1 3
4 1 4
5 2 2
6 2 4
7 2 5
...
AID
是表 A 的唯一 ID,BID
是表 B 的唯一 ID。ABID
是映射表的唯一标识符。[尽管我不确定它是否严格必要。]
表 AB 提供了表 A 和表 B 之间数据的多对多映射。我们可以从两个方向查看这些关系。
在实现方面,我使用了一个 MS Access 数据库。它为唯一标识符提供了自动编号功能,但在与 .NET 结合使用时存在一些棘手的问题——尤其是并发冲突问题,自动编号字段可能会与 DataSet
的编号不同步。虽然我的原始版本(勉强)使用了自动编号,但我决定,因为我可能想将我的设计移植到 SQL Server 或其他数据库,所以我最好不假设存在自动编号功能,而且这样实现就不会局限于 Access。
所以,虽然 Access 数据库表有自动编号 ID 列,并且我允许它们生成,但我并不使用它们。取而代之的是,应用程序将生成并管理它们。[我不太确定这在多用户环境中的影响。我认为可能需要更强大的例程来避免并发冲突。这留给其他人去解决。]
代码的第一部分只是用于显示数据的简单内容。下一部分将添加编辑和数据库更新例程。
我加入了手动显示 DataGridView
列的代码。
我确信可以通过设计器手动完成所有这些工作,但我不知道如何绑定到手动编码的 DataSet
。对于简单的表来说很容易,但这并不是一个简单的 DataSet
。如果有人知道如何做到,请告诉我,我会修改代码。
以下是代码的要点。首先,我们获取连接,并从 Access 数据库 AoB.mdb 加载数据到 DataSet
。
using Data.OleDB
string myDB = "AorB.mdb"; //or whatever your database is called
OleDbConnection conn = null;
DataSet ds;
OleDbDataAdapter daA;
OleDbDataAdapter daB;
OleDbDataAdapter daAB;
OleDbDataAdapter daBA;
DataRelation drAB;
DataRelation drBA;
public void InitialCode() //called from FormLoad or elsewhere.
{
//create a connection string to the access database
conn = new OleDbConnection(@"Provider=Microsoft.Jet.OLEDB.4.0;
User Id=;Password=;
Data Source=" + myDB);
LoadDatabase();
}
public void LoadDatabase()
{
//clear the dataset
ds = null;
ds = new DataSet();
//set up the link
daA = new OleDbDataAdapter("SELECT * FROM A ORDER BY AName", conn);
daB = new OleDbDataAdapter("SELECT * FROM B ORDER BY BName", conn);
//fill the dataset with data
daA.Fill(ds, "A");
daB.Fill(ds, "B");
// Create Data Relation from A to B, ForeignKey constraint
// auto created apparently - a bit complex!
daAB = new OleDbDataAdapter("SELECT AB.ABID, AB.AID, AB.BID," +
" B.BName FROM B INNER JOIN AB ON" +
" B.BID = AB.BID ORDER BY B.BName", conn);
daAB.Fill(ds, "ABx");
drAB = new DataRelation("AToB",
ds.Tables["A"].Columns["AID"],
ds.Tables["ABx"].Columns["AID"], true);
ds.Relations.Add(drAB);
daBA = new OleDbDataAdapter("SELECT AB.ABID, AB.BID, AB.AID," +
" A.AName FROM A INNER JOIN AB ON" +
" A.AID = AB.AID ORDER BY A.AName", conn);
daBA.Fill(ds, "BAx");
drBA = new DataRelation("BToA",
ds.Tables["B"].Columns["BID"],
ds.Tables["BAx"].Columns["BID"], true);
ds.Relations.Add(drBA);
//enforce cascade contraints, auto added, if need change
//then look up ForeignKeyContraints.DeleteRule
ds.EnforceConstraints = true;
BindDataGridViews();
}
然后将数据绑定到 DataGridView
s。我创建了一个带有两个选项卡页的控件,每个选项卡上有两个 DataView
,并手动整理了列。
public void BindDataGridViews()
{
//bind the datagridviews
dgvA.DataSource = ds;
dgvA.DataMember = "A";
dgvAtoB.DataSource = ds;
dgvAtoB.DataMember = "A.AToB";
dgvB.DataSource = ds;
dgvB.DataMember = "B";
dgvBtoA.DataSource = ds;
dgvBtoA.DataMember = "B.BToA";
DisplayColumnsinGrids();
}
//note the form designer sorted out all the form stuff
//I just renamed the default datagridviews to be called dgvA
//dgvAtoB (on tab page 1) dgvB and dgvBtoA (on tab page 2)
DataGridViewTextBoxColumn dataGridViewTextBoxColumn1;
DataGridViewTextBoxColumn dataGridViewTextBoxColumn2;
//...ect
private void DisplayColumnsinGrids()
{
//sort out columns - I'm not sure how or if it's possible
//to use the designer to do this with datarelations etc?
//so I created some unbound columns and manually
//wired it all up - anyone know the correct procedure?
//I know the designer can create unbound columns,
//but I couldn't see how to bind them.
dataGridViewTextBoxColumn1 =
new System.Windows.Forms.DataGridViewTextBoxColumn();
dataGridViewTextBoxColumn1.HeaderText = "AID";
dataGridViewTextBoxColumn1.Name = "AID";
dataGridViewTextBoxColumn1.DataPropertyName = "AID";
dataGridViewTextBoxColumn1.Width = 30;
dgvA.Columns.Add(this.dataGridViewTextBoxColumn1);
//...and so on for all 12 columns - see the full source
//in the download zip
}
看来 C# 的新 Express 版本与我最初使用的 C# 版本有所不同。特别是源代码现在被放在了不同的文件中,所以最好下载 zip 文件并在那里查看所有内容。
特别感谢 George Shepherd 的Windows Form FAQ,以及 Google 上的许多群组,还有这个尊贵的社区 :)