59
第第第 第第第第第第第第第 ADO.Net 第第第第第第第第第第第第第第第第 第第第第第 第第第第 第第第第第第第第第第 第 DataSet 第第第第 第第第 第第第 第第第第第 、、、 DataSet 第第第第第第第第第第 DataSet 第第第第第第第 第第第第第第第CommandBuilder 第第第第第 第第第第 -CrystalReport

第十章 数据库应用程序开发

Embed Size (px)

DESCRIPTION

第十章 数据库应用程序开发. ADO.Net 程序实现典型的数据库应用程序开发。 数据库连接 数据加载 数据的简单和复杂绑定 对 DataSet 数据浏览、插入、删除、确认和取消 DataSet 的表达式列和数据检索 DataSet 中数据表的关联 对数据库的更新: CommandBuilder 的更新机制 报表设计- CrystalReport. SQL Server 基本数据准备:. 建立数据库 teaching 建立下列数据表: Students: 学生表( id,name,classid) Grade: 成绩表( id,subid,grade) - PowerPoint PPT Presentation

Citation preview

Page 1: 第十章 数据库应用程序开发

第十章 数据库应用程序开发ADO.Net程序实现典型的数据库应用程序开发。 数据库连接 数据加载 数据的简单和复杂绑定 对 DataSet 数据浏览、插入、删除、确认和取消 DataSet 的表达式列和数据检索 DataSet 中数据表的关联 对数据库的更新: CommandBuilder 的更新机制

报表设计 -CrystalReport

Page 2: 第十章 数据库应用程序开发

SQL Server 基本数据准备:

建立数据库 teaching

建立下列数据表:Students: 学生表( id,name,classid )Grade: 成绩表( id,subid,grade )Classes: 班级表( classid,name )

Page 3: 第十章 数据库应用程序开发

1.1 ADO.NetADO/OLE DB 和 ODBC 关系图

VC++ VB Delphi

ADO

OLE DB

ODBC

RDBMS RDBMS E-Mail DirectoryService

Page 4: 第十章 数据库应用程序开发

ADO.NET 结构.NET DataProvider DataSet

Command

DataReader

Parameters

ConnectionTransaction

DataAdapter

SelectCommand

InsertCommand

UpdateCommand

DeleteCommand

DataTable

DataRowCollection

DataColumnCollection

ConstraintCollection

DataReaderCollection

DataBase XML

Page 5: 第十章 数据库应用程序开发

1.2 实例分析:设计一个 Form 实现对数据表students 、 subjects 和 grade 编辑和浏览功能examplea1

程序设计的两种方式自编程序设计方式:自编程序实例化 dataset等对象、设置属性、动态连接数据库、实现数据绑定、数据表关联等。可视化设计方式:设计阶段通过添加组件,设置组件属性完成数据库的连接、数据绑定以及数据表的关联等,然后由系统生成部分程序。

Page 6: 第十章 数据库应用程序开发

1.2.1 自编程序方式

一)把数据库中数据表数据显示在控件中1 )连接数据库: teaching (SQL Server)

2 )数据库中数据载入 DataSet :加载 students

3 ) DataSet 中数据和控件的绑定: id 和TextBox 的绑定

Page 7: 第十章 数据库应用程序开发

1 )连接数据库: teaching (SQL Server)

using System.Data.OleDb;private DataSet dataSet;private OleDbConnection oleDbConnection;private OleDbDataAdapter oleDbDataAdapter;private void ConnectDB(){ oleDbConnection = new

OleDbConnection("Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=teaching");

oleDbConnection.Open();}

OleDbConnection 中连接字符串:新建 teaching.udl 文件,双击后进入 oleDB 数据链接属性编辑,连接对应数据库并成功进行测试连接后,关闭窗口后 teaching.udl 中包含了连接字符串。

Page 8: 第十章 数据库应用程序开发

2 )数据库中数据载入 DataSet :加载 students

private void LoadData()

{

oleDbDataAdapter = new OleDbDataAdapter("select * from students", oleDbConnection);

dataSet = new DataSet();

oleDbDataAdapter.Fill(dataSet, "students");

}

DataSet 本身没有从数据库加载数据的能力,必须通过OleDbDataAdapter 提供的 Fill 方法进行加载。在 Fill 过程中,若 dataSet 中没有 students ,则会自动建立,并把“ select * from students” 结果载入。

Page 9: 第十章 数据库应用程序开发

3 ) DataSet 中数据和控件的绑定: id和 TextBox 的绑定

private void BindingControls()

{

textBox1.DataBindings.Add("text", dataSet, “students.id");

}

textBox1 的 Text 属性和 dataSet 中 students 表的 id字段进行绑定。绑定的结果是 textBox1 的值和 dataSet 中 students 表的 id 值会同步变化。在 Form 的 load 事件中执行下列语句:ConnectDB();LoadData();BindingControls();

Page 10: 第十章 数据库应用程序开发

二)复杂绑定: classid 数据取自 classes 表,提供选择功能。

LoadData 中增加下列程序oleDbDataAdapter.SelectCommand = new

OleDbCommand("select * from classes",oleDbConnection);

oleDbDataAdapter.Fill(dataSet, "classes");

在 BindControls 中增加下列程序: comboBox1.DataSource = dataSet.Tables["classes"]; ;

comboBox1.DisplayMember="name";

comboBox1.ValueMember = "classid";

comboBox1.DataBindings.Add("selectedValue", dataSet, "students.classid");

Page 11: 第十章 数据库应用程序开发

三)加入浏览功能DataSet 中的 Table 和数据库中数据表一样,没有行顺序的概念,所有没有提供行号定位的方法和属性当一个数据表数据绑定在 Form 上的若干控件后,由其 BindingContext 成员管理从 Control 类继承的任意对象的 BindingManagerBase 对象集合,通过设置其属性 Position 来控制当前显示的行:this.BindingContext[dataSet, "students"].Position += 1;

注意 students 和控件的绑定方式不能写成下列形式,这将使 Form 仅显示首行内容,而无法通过上述方式定位到其他行。 ( 一致性 )textBox1.DataBindings.Add("text", dataSet.Tables["students"], "id");

首行对应 Position=0 ,当 Position=0 ,再 -1 属性值将不变。当 Position= 总行数 -1 ,再加 1 同样值不变。

Page 12: 第十章 数据库应用程序开发

四)使用绑定对 DataSet 数据表进行插入和删除

插入: this.BindingContext[dataSet, "students"].EndCurrentEdit();

this.BindingContext[dataSet, "students"].AddNew();

删除:this.BindingContext[dataSet, "students"].RemoveAt(this.BindingContext[dataSet, "students"].Position);

取消当前行的修改:this.BindingContext[dataSet, "students"].CancelCurrentEdit();

Page 13: 第十章 数据库应用程序开发

五)通过控件输入的 DataSet 数据的取消和确认功能

控件输入数据与 DataSet 数据同步:this.BindingContext[dataSet, “students”].Position 值变化或调用this.BindingContext[dataSet, “students”].EndCurrentEdit() 后,界面控件数据才写入 DataSet 。取消修改:dataSet.RejectChanges() :取消 dataSet 创建或调用dataSet.AcceptChanges() 以后对 dataSet 所作的修改。接收修改: dataSet.AcceptChanges() :接收对 dataSet 中数据所做的修改。dataSet 中各表及表中各行对象均有取消和接受修改的上述两个方法,区别是其作用的对象范围不同。

Page 14: 第十章 数据库应用程序开发

六)把 DataSet 数据存入数据库更新数据库中数据表的唯一途径是执行 SQL 的insert 、 update 或 delete 语句, dataSet 中数据的修改(表的增、删、改),要反映到数据库中,必须根据修改情况产生并执行相应的SQL 语句。所以要根据对 DataSet 数据表的修改更新对应数据库中的数据表,必须完成下列三部分工作: 标记 DataSet 中数据表做过修改的所有行,并记录作

了何种修改(增、删、改)。 编写或生成相应的带参数的 insert 、 delete 或

update 语句。 对每个修改行,根据修改的种类,执行相应的更新数

据库的语句。

Page 15: 第十章 数据库应用程序开发

1 )标记修改行及修改种类

在 dataSet 中表的每一行对象有一个 RowState 属性,记录了该行是否作了修改及作了何种修改的信息:dataSet.Tables["students"].Rows[0].RowState其值可为: DataRowState.Added/Modified/Deleted和 Unchanged 。当 dataSet 中表数据载入后,各行的状态为Unchanged ,当对表中某行进行修改后,会根据修改类型自动改变该行的状态。在调用 AcceptChanges 后,对应行状态恢复到 Unchanged状态。由于更新数据库的 update 方法是依据该状态来确定哪些行要修改和做什么修改,所以在用 dataSet 数据更新数据库前,不要调用用 AcceptChanges 。

Page 16: 第十章 数据库应用程序开发

2 )编写或生成相应的带参数的 SQL 语句( 1 ) 使用 OleDBCommandBuilder 产生更新数据库的带参数的 SQL 语句

OleDbCommandBuilder 可根据 oleDBDataAdapter 中的 Select Command 中的 select 语句,即调用 Fill 方法时执行的 select 语句,生成带参数的 update 、 delete 或 insert 语句。在窗口类中增加成员: OleDbCommandBuilder oleDbCommandBuilder; 在LoadData 加载( Fill ) students 数据语句后加:oleDbCommandBuilder = new OleDbCommandBuilder(oleDbDataAdapter);

实例化 oleDbCommandBuilder ,并把 oleDbDataAdapter 作为其属性 DataAdapter 值,在要生成 SQL 语句前, oleDbCommandBuilder 必须能访问非空的oleDbDataAdapter.SelectCommand 。在生成 SQL 语句前,首先会执行上述的 select 语句,以获得对应表的列名信息,为生成做准备。生成的 SQL 语句将存放在该类的基类对应的三个 private 属性中:它们是 UpdateCommand 、 DeleteCommand 和 InsertCommand 。

Page 17: 第十章 数据库应用程序开发

生成 SQL 语句的条件(如 UpdateCommand ) 基类对应的属性为空(如 UpdateCommand=null ) dataSet 对应数据表中存在对应状态的行(如存在 Modified 状

态的行)在下列调用中会调用 oleDbCommandBuilder 中生成SQL 语句的程序(并非在初始化的时候): 调用其方法 GetUpdateCommand 或 GetDeleteCommand 或

GetInsertCommand 时,返回对应的命令串。 调用其属性 DataAdapter 值对象 oleDbDataAdapter 所包含

的方法: oleDbDataAdapter.Update(dataSet, “students”);该语句用生成的 SQL 语句及 dataSet 修改过的数据更新数据表。比较特别是 oleDbDataAdapter 是 oleDbCommandBuilder的 DataAdapter 属性值,其方法却要调用拥有它的对象方法以生成 SQL 语句,这种调用方法见“ 3 )更新数据库”后的例。

生成带参数的 SQL 语句条件和时机:

Page 18: 第十章 数据库应用程序开发

加入生成 SQL 语句的语句: 在 LoadData 中实例化 oleDbCommndBuilder 后调用下列语句以生成带参数的更新数据库的 SQL 语句。oleDbCommandBuilder.GetDeleteCommand();oleDbCommandBuilder.GetUpdateCommand();oleDbCommandBuilder.GetInsertCommand();

由于 LoadData 中先使用 oleDbDataAdapter 加载了students 表,然后同样使用它加载 classes 表,所以如没有上述语句,则在 update时, oleDbCommandBuilder. DataAdapter指向的 oleDataAdapter 的 SelectCommand属性的 CommandText 为 Select * from classes ,所以将生成对 classes 的更新语句。而上述语句生成了关于 students 的 SQL 语句,在 update时发现 oleDbCommandBuilder 中已存在这些语句,就不会再生成关于 classes 的更新语句。

Page 19: 第十章 数据库应用程序开发

OleDbCommandBuilder 其他说明:由上可知, SQL 语句一但生成,改变oleDbDataAdapter 的 SelectCommand 属性,由于生成 SQL 语句的第一个条件不满足,不会改变oldDbCommandBuilder 中已生成的 SQL 语句。oleDbCommandBuilder.RefreshSchema() 可用于清空生成的 SQL 语句(基类属性),可使生成 SQL 语句的第一个条件满足。使用 OleDbCommandBuilder 的限制: select 语句必须为单表的查询,包含主键,但不包含只读列(如计算列)。为了生成 INSERT 、 UPDATE 或 DELETE 语句, OleDbCommandBuilder 会自动使用 SelectCommand 属性来检索所需的元数据集,以获得如列名等信息,所以会降低执行效率。

Page 20: 第十章 数据库应用程序开发

( 2 )编写更新数据库的 SQL

如果 Fill 一个 DataSet 数据表对应的 select 语句牵涉多个表,就不能使用oleDbCommandBuilder 生成更新数据库的 SQL语句,必须手工编写。目标:在 Form 的下方用 DataGrid 显示当前学生的各门课的名称及成绩,可以修改成绩但不能添加和删除,点击保存按钮后保存修改内容。增加下列 Form 类成员:private DataRelation dataRelation; //students 和 grade 的关联private OleDbDataAdapter oleDbDataAdapter1;// 用于 grade 表

Page 21: 第十章 数据库应用程序开发

把 grade 数据加载到 DataSetprivate void LoadGrade() { oleDbDataAdapter1 = new OleDbDataAdapter(); oleDbDataAdapter1.SelectCommand = new OleDbCommand("select

a.id,a.subid,b.subname,a.grade from grade a,subjects b where a.subid=b.subid", oleDbConnection);

oleDbDataAdapter1.Fill(dataSet, “grade”);//dataSet 中产生 grade 表//update 时调用的 update 语句,其中“?”表示定位参数(非命名参数),依次和 // 下面 oleDbDataAdapter1.UpdateCommand.Parameters 中的参数对应。oleDbDataAdapter1.UpdateCommand = new OleDbCommand("update grade set grade=? where id=? and subid=?",oleDbConnection);// 定义执行 update 时三个参数来自 dataSet.grade 表的那个列

// 定义第一个参数:名为“ grade”, 对定位参数,参数按次序对应,名称无用OleDbParameter gradeParameter = new OleDbParameter("grade", OleDbType.Integer);// 该参数数据来源 grade 列

gradeParameter.SourceColumn = “grade”;// 数据取修改后数据,此语句可省略

gradeParameter.SourceVersion = DataRowVersion.Current;// 缺省

Page 22: 第十章 数据库应用程序开发

// 定义第二个参数 id :数据取修改前数据OleDbParameter idParameter = new OleDbParameter("id", OleDbType.Char,6);idParameter.SourceColumn = “id”; // 数据来源于 id 列idParameter.SourceVersion = DataRowVersion.Original;// 定义第三个参数 subid :数据取修改前数据,同时指定数据来源于 subid 列OleDbParameter subidParameter = new OleDbParameter("subid", OleDbType.Char, 6,"subid");subidParameter.SourceVersion = DataRowVersion.Original;// 将三个参数依次加入参数表,作为 update 语句中的三个参数oleDbDataAdapter1.UpdateCommand.Parameters.Add(gradeParameter);oleDbDataAdapter1.UpdateCommand.Parameters.Add(idParameter);oleDbDataAdapter1.UpdateCommand.Parameters.Add(subidParameter);// 建立名为 students_grade 的两表关系,联结条件 students.id=grade.iddataRelation = new DataRelation("students_grade", dataSet.Tables["students"].Columns["id"], dataSet.Tables["grade"].Columns["id"]);// 将关系加入 dataSet.Relation 中dataSet.Relations.Add(dataRelation);

}

在 LoadData 方法最后加 LoadGrade()

Page 23: 第十章 数据库应用程序开发

设置并显示 DataGrid :private void SetDataGrid()

{

DataGrid dataGrid = new DataGrid();

//dataGrid 与 Panel1 对齐并设置大小相同dataGrid.SetBounds(panel1.Location.X, panel1.Location.Y + panel1.Size.Height, panel1.Size.Width, panel1.Size.Height);

// 把 dataGrid 加入 Form

this.Controls.Add(dataGrid);

dataGrid.CaptionText = “ 成绩” ;

dataGrid.DataSource = dataSet;

//students_grade 为 LoadGrid 中已建立的 students 和 grade 关系名,其 // 中 students 为父表,如此定义使 dataGrid 仅显示 students 的当前学生// 成绩 , 而非所有 grade 行。

dataGrid.DataMember = “students.students_grade”;

Page 24: 第十章 数据库应用程序开发

// 定义两个在 DataGrid 中显示的列:课程名称和成绩 DataGridTextBoxColumn column1=new DataGridTextBoxColumn();

DataGridTextBoxColumn column2=new DataGridTextBoxColumn();

column1.MappingName="subname";

column1.HeaderText=" 课程名称 ";

column2.MappingName="grade";

column2.HeaderText=" 成绩 ";

// 把两个列加入 dataGridTableStyle.GradeColumnStyles

DataGridTableStyle dataGridTableStyle=new DataGridTableStyle();

dataGridTableStyle.GridColumnStyles.Add(column1);

dataGridTableStyle.GridColumnStyles.Add(column2);

// 若不设置下列属性, dataGrid 将显示 select 所有列,上述设置不起作用dataGridTableStyle.MappingName = "grade";

dataGrid.TableStyles.Add(dataGridTableStyle);

}

在 Form 的 Load 事件中加入: SetDataGrid()

Page 25: 第十章 数据库应用程序开发

3)更新数据库使用下列语句更新数据库(保存按钮的 click 事件):

// 确保把和 dataSet 中 students 及 grade 绑定的控件数据写入 dataSetthis.BindingContext[dataSet,“students”].EndCurrentEdit(); this.BindingContext[dataSet, "grade"].EndCurrentEdit();oleDbDataAdapter.Update(dataSet, "students");oleDbDataAdapter1.Update(dataSet,"grade");

Update 执行流程如下: 如 oleDbDataAdapter 关联了一个 oleDbCommandBuilder (即

前者为后者的一个属性,判断方法如下例 owner!=null ) ,若后者尚未生成更新数据库的 SQL 语句(其基类对象相应属性为空),则生成。

执行更新语句:若程序已设置 oleDbDataAdapter 中的属性如UpdateCommand 中的 update 语句,则执行该语句(即由程序员编写数据库的更新语句),否则则执行 oldDbCommandBuilder 基类对象属性所存储的 update 语句

下页示例如何在一个对象( oleDbCommandBuilder )的属性值对象( oleDbDataAdapter )的方法中调用拥有它的对象( oleDbCommandBuilder )的方法(生成 SQL 的方法)

Page 26: 第十章 数据库应用程序开发

例: commandBuilder 对象的属性值对象 dataAdapter的 update 方法调用 commandBuilder 的方法 GetSQL

public class DataAdapter { public object owner; public string update() {

if (owner!=null) return ((CommandBuilder)owner).GetSQL();

else return null;

} } public class CommandBuilder { private DataAdapter dataAdapter; private string UpdateText; public CommandBuilder(DataAdapter dataAdapter) { this.dataAdapter = dataAdapter; this.dataAdapter.owner = this; } public string GetSQL() { UpdateText="update students set name='wang'where id='01'"; return UpdateText; } }

调用示例:若没有第二个语句,即dataAdapter 不和 commandBuilder关联,仍能调用 update ,但返回null 。DataAdapter dataAdapter = new DataAdapter();

CommandBuilder commandBuilder = new CommandBuilder(dataAdapter);

MessageBox.Show(dataAdapter.update());

Page 27: 第十章 数据库应用程序开发

七) DataSet 表的表达式列目标:在窗口上动态显示该学生的平均成绩。对 DataSet 中的 students ,增加计算列 avggradec, 其值为子表 grade 中 grade 字段值的平均值,并动态绑定到对应的 TextBox 。

private void AddAvgGradeToStudents() {

if (dataSet.Tables["students"].Columns["avggradec"] == null) { dataSet.Tables["students"].Columns.Add("avggradec"); dataSet.Tables["students"].Columns["avggradec"].Expression

="avg(child.grade)"; textBox3.DataBindings.Add("text", dataSet, "students.avggradec"); } }

在 LoadData最后加 AddAvgGradeToStudents()设置 textBox3 为 readonly 。修改成绩,平均成绩即刻更新。

Page 28: 第十章 数据库应用程序开发

DataColumn.Expresion

获取或设置表达式,用于筛选行、计算列中的值或创建聚合列,假设在 grade 表中已增加列c 。计算列:dataSet.Tables["grade"].Columns[" c"].Expression= "grade*0.9";

聚合列:支持 sum 、 max 、 min 和 count 等聚合函数,见上例。为筛选器指定表达式:值为 true/false 。dataSet.Tables["grade"].Columns["c"].Expression= "subid=‘sub001’";

Page 29: 第十章 数据库应用程序开发

Expression 中对父表或子表关系引用

通过在列名称前面加 Parent ,就可以在表达式中引用父表。例如, Parent.Price 引用父表的名为 Price 的列。通过在列名称前面加一个 Child ,就可以在表达式中引用子表中的列。因为子关系可以返回多行,所以必须在聚合函数中包括对子列的引用。例如, Sum(Child.Price) 将返回子表中名为 Price 的列的总和。如果某个表有多个子表,则语法是:Child(RelationName) 。例如,如果某个表有两个子表,父表名为 Customers ,一个子表名为 Orders ,则 DataRelation 对象被命名为 Customers2Orders ,引用将为: Avg(Child(Customers2Orders).Quantity)

Page 30: 第十章 数据库应用程序开发

八)使用 Tag 属性绑定列

目标:使用 RadioButton 选择性别,但 Students.sex 类型为 bit , 0 表示男, 1 表示女。需要在一个 GroupBox 中放两个 RadioButton ,其 Text分别为“男”和“女”,而实际只需要获得一个RadioButton 的 Checked状态就能确定性别。由于值不同,所以不能使用 RadioButton 的 Text 属性和 students.sex 绑定,可用 Tag 属性与其绑定。这样的设置必须完成两部分工作: Tag 值改变时(浏览时)要对 RadioButton 的状态作相应改变 RadioButton状态改变时要改变 Tag 值

Page 31: 第十章 数据库应用程序开发

RadioButton状态改变时改变 Tag 值

在 RadioButton 的 CheckedChanged 事件中实现:

if (radioButton1.Checked)

radioButton1.Tag = false;

else

radioButton1.Tag = true;

Students 的 sex 属性类型为 bit ,其值只能取0 和 1 ,与 radioButton1.Tag 绑定后, Tag 的值为 true 和 false 。

Page 32: 第十章 数据库应用程序开发

Tag 值改变时要对 RadioButton 的状态作相应改变:

首先实现下列方法: private void SetSexRadioButton(object sender, EventArgs e) { if (radioButton1.Tag.ToString() == "") { radioButton1.Checked = false; radioButton2.Checked = false; return; } if (radioButton1.Tag.ToString() == "False") { radioButton1.Checked = true; radioButton2.Checked = false; } else { radioButton1.Checked = false; radioButton2.Checked = true; } }

Page 33: 第十章 数据库应用程序开发

何时调用该方法:方案一:在窗口打开后 (Load 事件 ) 及按浏览按钮时,调用 5次。方案二:在窗口打开后 (Load 事件 ) 和this.BindingContext[dataSet, “students”] 对象的 PositionChanged 事件中调用,调用 2次。 在 BindingControls最后加:

radioButton1.DataBindings.Add("tag", dataSet, "students.sex");this.BindingContext[dataSet, "students"].PositionChanged += SetSexRadioButton;

在 Form 的 Load 事件中加:SetSexRadioButton(this, null);

Page 34: 第十章 数据库应用程序开发

九)使用程序对 dataSet 中数据表的插入、删除、修改和检索。

以上对 dataSet 的数据表的操作是通过绑定来实现的,修改控件中数据则自动更新数据表中数据,插入和删除则是通过 this.BindingContext[dataSet, “students”]的 AddNew 方法和 RemoveAt 方法实现。直接对 DataSet 中数据表操作方法如下:修改: dataSet.Tables["students"].Rows[0]["name"] = "zhp";

插入: dataSet.Tables["students"].Rows.InsertAt(DataRow row,int pos)

删除: dataSet.Tables["students"].Rows.RemoveAt(int pos)

Page 35: 第十章 数据库应用程序开发

检索:DataSet 中的 Table 提供了下列两种方法检索符合条件的行,返回符合条件的行。Select 方法:DataRow[] dataRow = dataSet.Tables["students"].Select("id='" + textBox4.Text + "'") ; // 使用 dataRow.GetLength(0)获得符合条件的行数

Find 方法:检索主键值,必须先建立主键,见下例。DataRow dataRow = dataSet.Tables[“students”].Rows.Find(textBox1.Text);//找不到返回 null

方法比较: Select 检索条件任意,可返回多行, Find方法只能对建立的主键值检索,返回最多一行,由于表总关于主键排序,所以检索速度较 select快很多。

Page 36: 第十章 数据库应用程序开发

检索实例:目标:输入学生编号,按“检索”按钮后若该学生不存在,则提示,若存在,则显示该学生信息:分析:定位控件显示的行的唯一方法是设置Binding Context 的 Position 属性,而 Find 和Select返回的是 dataRow ,余下的问题是如何由已知行 dataRow获得该行在 table 中的行序号, .Net 提供了该方法:

dataSet.Tables["students"].Rows.IndexOf(dataRow);以下使对学生编号检索的程序:

Page 37: 第十章 数据库应用程序开发

private void button13_Click(object sender, EventArgs e)

{

int idx;

DataColumn[] dataColumn;

dataColumn = new DataColumn[1];

dataColumn[0] = dataSet.Tables["students"].Columns["id"];

dataSet.Tables[“students”].PrimaryKey = dataColumn; // 至此为设置主键 DataRow dataRow=dataSet.Tables["students"].Rows.Find(textBox4.Text);

if (dataRow == null)

MessageBox.Show(" 没有发现符合条件的行! ");

else

{

idx=dataSet.Tables["students"].Rows.IndexOf(dataRow);

this.BindingContext[dataSet, "students"].Position = idx;

}

}

Page 38: 第十章 数据库应用程序开发

1.2.2 可视化设计构造 DataSet :前面的设计方式,必须在程序运行后,才能确定 DataSet 的结构(包含哪些 Table 和Relation 等), .Net 可以使用 XML 文件构建 DataSet类,这使程序设计和运行可不依赖于和数据库的连接,并且使数据库结构修改后,只要修改对应的 XML 文件,而不必修改程序。类型化 DataSet 和非类型化 DataSet :前者在程序设计时定义(可编写或生成) DataSet 类的内部结构,编译时进行类型检查,所以类型安全,后者则在程序运行时产生 DataSet 对象的内部结构( 1.2.1 中例子的方式),可能由于数据类型不一致而使运行时更容易发生错误。

Page 39: 第十章 数据库应用程序开发

设计步骤:根据数据库中表结构生成 DataSet1.XSD 文件:使用菜单 Add New Item/ DataSet 进入可视化地构建 DataSet模式( schema ),可把 Server Explorer 中数据表拖入及使用工具栏中的 Relation 建立数据表之间的联系,然后保存( XML格式)。依据上面生成的 XSD 文件产生 DataSet 类和对象定义:在 Form 中加入工具栏中 DataSet ,选择 Typed dataset 。Name 选择 examplea1. DataSet1 (即对应上面 XSD 文件),系统根据 XSD 文件内容在 dataSet11. Designer. cs 文件中产生类定义 DataSet1 ,并在Form1.Designer. cs 中定义了该类的对象 dataset11 。通过设置控件的 DataBindings 属性(可选 dataset11 数据项)实施绑定等设置,对应程序也在 Form1.Desig ner. cs 文件中生成。

Page 40: 第十章 数据库应用程序开发

两种类型 DataSet 数据的访问的区别:

非类型化 DataSet (弱类型DataSet )dataSet.Tables[“students”].Columns[“id”] 或dataSet.Tables[0].Columns[0]

类型化 DataSet (强类型DataSet )dataSet.students.idColumn

Page 41: 第十章 数据库应用程序开发

1.2.3 报表设计: CrystalReport

基本原理:使用报表设计器设计报表,设计内容存入特殊格式的CrystalReport.rpt 文件中,该报表文件也可供其他语言( C++ 或 VB )开发的程序使用。同时生成了一个继承于 ReportClass 的报表类定义文件 CrystalReport.cs ,其中包含了该类与 rpt 文件的关联,用户通过该类实现对上述设计报表的操作。在该类定义文件中可以加入工具箱中的组件(把组件拖入CrystalReport.cs[Designer]页面)。报表通过可视化控件 CrystalReportViewer 显示。用户只要实例化一个报表类对象,并把包含数据的DataSet 对象传给它,由报表类完成对 rpt 文件的解析,获取数据并在 CrystalReportViewer 中显示报表。

Page 42: 第十章 数据库应用程序开发

报表的数据来源 -用非类型化 DataSet 产生 XSD

使用 CrystalReport ,在设计阶段必须确定报表的数据来源,即必须构建 DataSet框架,报表设计时各数据项可选自构建好的 DataSet 。在程序运行时,通过报表类的 SetDataSource 方法把实例化的 DataSet (包含数据的)对象传递给报表,该 DataSet 必须包含报表设计时所用到数据项,或保证两者数据模型完全一致。由于本例 DataSet为非类型化,可使用下列语句把DataSet模型输出到 XSD 文件,供报表设计用:dataSet.WriteXmlSchema("dataset1.xsd");

Page 43: 第十章 数据库应用程序开发

一)设计 List 报表:学生基本信息表\ ( CryStalReport1 )

构建报表数据源: 在项目中加入 DataSet1.XSD :在 Solution Explorer 中右击项目名,选择 Add/Existing Item… ,选择前面生成 DataSet1.XSD 文件。

系统会生成 DataSet1.Designer.cs ,其中定义了名为NewDataSet 类,该类为依据 XSD 文件构造的类型化 DataSet类,报表设计时可由此获得数据项

新建报表:选择菜单 Project/Add New Item ,选Reporting /CrystalReport 。

1 )简要介绍 Report Wizard :选择 Using the Report Wizard :以下是 Wizard 各步: 在 Project Data/ADO.NET DataSets/examplea1. DataSet1 中选

择报表数据相关的表 维护表之间关系:对 DataSet1 中定义的表间关系进行删除,或

根据列名( name )或主键自动建立表间关系。 选择报表中要输出的列:

Page 44: 第十章 数据库应用程序开发

选择分组信息: 选择统计信息: 分组排序: 报表中是否要包含图表 过滤条件 报表的风格: table 含有表格线

2 )从空白报表开始设计:选择 As a Blank Report出现一个空白报表,有 5 个部分组成,分别是 Report Header 、 Page Header 、 Details 、 Report Footer 和 Page Footer 。Report Header 和 Footer只出现在报表头一页和最后一页, Page Header 和 Footer出现在每一页上, Details为报表的主体 - 数据部分。

( 1 )加入要使用的数据项:右击报表,选择 Field Explorer ,在打开的窗口中右击 DataBase Fields 选择 DataBase Expert ,出现的对话框的 Data页框中可选择 Available Data Sources ,其中列出了Project Data/ ADO.NET DataSets/examplea1.NewDataSet 中的所有数据表,把它们全部加入 Selected Tables 。在 Link页框中,以图形方式显示了被选中表在 NewDataSet 所包含的表间关系,可增加通过classid 列建立的 Students 和 classes 表的关联。

Page 45: 第十章 数据库应用程序开发

( 2 )设置 Page Head:

在工具栏选择 Text Object ,放入 Page Header 。右击该 Text Object ,选择 Edit Text Object ,输入“学生基本信息表”右击该 Text Object ,选择 Format Object ,设置其字体和对齐方式在 Field Explorer 中的 Special Fields ,选择Print Date ,并把它拖到上述的标题下。

Page 46: 第十章 数据库应用程序开发

( 3)设置 Details

把 Field Explorer 中 students 的 id 、 name 、 sex 和Height 和 classes 的 name拖入 Details 中。编辑 Page Header 中自动产生的 Text Object ,输入对应中文,使用 Format Object 设置对齐方式为居中。右击报表,选择 Preview Report ,可预览报表,由于报表设计不需要连接数据库,所以数据为根据类型自动生成的模拟数据。调整数据项位置、尺称、字体、对齐方式等使用工具栏中的 Box Object 和 Line Object为表格加上线框,线和框可跨越报表的不同区域。

Page 47: 第十章 数据库应用程序开发

( 4)使用 Formula Field :性别输出

数据表中性别为 bit 类型,输出为 True 和 fase ,要求输出“男”,女。在 Field Explorer 中右击 Formula Fields ,选 New ,输入 Name 为 SexText ,然后按“ Use Expert” ,进入 Formula Editor 。通过选择 Report Fields 、 Functions 和 Operators 或输入if({students.sex}=true)

then "女 "

else "男 "

在报表中删除 Sex ,然后加上 SexText 。

Page 48: 第十章 数据库应用程序开发

( 5)分组统计:总平均身高和分组平均身高

插入总平均身高:右击报表,选择 Insert/Summary ,选择统计字段 Height 和统计函数 average ,统计字段自动被安排在 Report Footer 的 Height 列下。班级分组平均身高: 在报表中插入分组块:右击报表,选择 Insert/Group ,选择依据哪个列值分组(选 classes.classid )及对该列值排序方式(升 /降),确认后报表上出现 GroupHeaderSection1 和Group FooterSection1 ,并在 GroupHeaderSection1 中出现字段 Group #1 Name ,删除它,为避免在每组开始时显示一个空行,右击该灰色条带选择 Hide ,则该块内容不输出。

在分组块中插入分组的平均身高:操作基本同插入总平均身高的操作,但 Summary location 选择 Group #1 。确认后统计字段被自动安排在 GroupFootSection1 中的 Height 列下。

Page 49: 第十章 数据库应用程序开发

分组修改:右击 GroupHeaderSection1 的灰色条带,选择 Group Expert ,可以对分组进行增加、删除和修改。(要删除 Group Header Section必须先在 Group Expert 中移除对应分组)可以对每个班级分组中再对性别分组,分别统计班级男同学和女同学的平均身高。第二个分组一定是在第一个分组下的分组,不可能同时存在两个并列的分组,就如同不可能存在两个并列的排序一样。

Page 50: 第十章 数据库应用程序开发

( 6)排序和过滤:每个分组要求按姓名排序。

右击报表选择 Report/Record Sort Expert ,其中已经

存在按分组 1 和分组 2 的排序(分组必须排序),把 students.name 加入 Sort Fields 。

选择 Report/Selection Formula/Record 或Group 对行或分组加入过滤条件。

Page 51: 第十章 数据库应用程序开发

二)报表的预览和打印在 Project 中加入一个 Form2 ,在工具栏选择 CrystalReportViewer放入。Form2 对应程序中定义:public DataSet dataSet;// 用于接收 Form1 中的 dataSet 。

Form2 的 Load 事件:显示报表CrystalReport1 crystalReport1=new CrystalReport1();

crystalReport1.SetDataSource(dataset);

crystalReportViewer1.ReportSource=crystalReport1;

crystalReportViewer1.Zoom(2);

Page 52: 第十章 数据库应用程序开发

主窗体的“打印预览”按钮的click 事件中程序:

Form2 form2=new Form2();

form2.dataset=dataSet;

form2.ShowDialog();

Page 53: 第十章 数据库应用程序开发

三)设计包含子报表的报表:学生及成绩( CryStalReport2 和 CryStalReport3 )

要求打印学生基本信息及每个学生各门课的成绩,每个学生各门课的成绩即为子报表。建立子报表:新建 CrystalReport2 ,在 Details中包含 Grade 中的两个列:课程名称和成绩。建立报表: 新建 CrystalReport3 ,在 Details 中插入 students 的两个列 id

和 name 。 插入子报表:在 Details 中再插入一个 Subreport ,在 Insert

Subreport 对话框中 Subreport 选择 CrystalReport2 。 报表数据和子报表数据的关联:在 Insert Subreport 对话框的

Link页框中,在 Available Fields 中把 Report Fields/Students. id 加入 Field(s) to Link 。也可以右击该子报表框,选择 change Subreport Links… 进行设置。

Page 54: 第十章 数据库应用程序开发

输出每个学生的选课数:

在子报表中定义 Formula Field :subcnt ,其对应公式为 Count ({grade.id})

在子报表的 Report Header 加入 subcnt及相应的 Text Object 。设置其他 Section 为 Hide 或Suppress 。

Page 55: 第十章 数据库应用程序开发

设置子报表右击报表,选择 Format Object 。其中包含了对子报表边框、字体和对齐方式等的设置。Suppress Blank Subreport :若子报表数据为空,输出空白。On Demand Subreport :预览时子报表显示子报表名,点击后打开子报表。子报表是以复制形式加入主报表的,所以对子报表修改后,必须对子报表进行 Re-import subreport操作,或者在子报表的 Format Object 中,打开 Re-import when opening 选项。

Page 56: 第十章 数据库应用程序开发

解决子报表问题 -使用主从表输出同类型报表: ( CryStalReport4 )

用子报表的问题:以下问题均牵涉到主报表取子报表数据问题,使用子报表较难解决: 没有选课程的学生不输出 把课程数放在学生姓名后,即放在主报表中

新建报表:利用 students 和 grade 通过 id 的关联 插入分组,按 students.id 分组 在 Details 中加入 grade.subname 和 grade.grade 在 GroupHeaderSection 中加入 students.id 和 name ,然后插入

Summary : count(grade.id) (即课程数) 在 Report/Selection Formula/Group 中输入 Count

({grade.id})>0 (分组过滤条件:至少选一门课) 在 GroupFooterSection 中插入 Summary : average(grade.grade)

Page 57: 第十章 数据库应用程序开发

细节问题:文字左对齐过于靠左:使用 Format Object 在Paragraph 中 Identations 的 Left 设置为 0.1

相同长度纵横线条可使用复制和粘贴

Page 58: 第十章 数据库应用程序开发

知识点:

DataSet 数据与数据库数据同步方法OleDbCommandBuilder

Page 59: 第十章 数据库应用程序开发

思考和练习建立下列 4 个数据表,其关系类似students 、 subjects 、 grade 和 classes ,完成课程中类似界面和程序的设计:Books(id,name,language,publicdate,price,classid)

=书(书号,中 /外文 bit ,出版日期,价格,分类号) Bookshop(shopid,name)

=书店(编号,名称)SalePoint(id,shopid,qty)

=销售点(书号,书店编号,存量) Classes(classid,name)

= 图书分类(分类号,分类名称)