590 likes | 717 Views
第十章 数据库应用程序开发. ADO.Net 程序实现典型的数据库应用程序开发。 数据库连接 数据加载 数据的简单和复杂绑定 对 DataSet 数据浏览、插入、删除、确认和取消 DataSet 的表达式列和数据检索 DataSet 中数据表的关联 对数据库的更新: CommandBuilder 的更新机制 报表设计- CrystalReport. SQL Server 基本数据准备:. 建立数据库 teaching 建立下列数据表: Students: 学生表( id,name,classid) Grade: 成绩表( id,subid,grade)
E N D
第十章 数据库应用程序开发 • ADO.Net • 程序实现典型的数据库应用程序开发。 • 数据库连接 • 数据加载 • 数据的简单和复杂绑定 • 对DataSet数据浏览、插入、删除、确认和取消 • DataSet的表达式列和数据检索 • DataSet中数据表的关联 • 对数据库的更新:CommandBuilder的更新机制 • 报表设计-CrystalReport
SQL Server基本数据准备: • 建立数据库teaching • 建立下列数据表: • Students:学生表(id,name,classid) • Grade:成绩表(id,subid,grade) • Classes:班级表(classid,name)
1.1 ADO.NetADO/OLE DB和ODBC关系图 VC++ VB Delphi ADO OLE DB ODBC RDBMS RDBMS E-Mail Directory Service
.NET DataProvider DataSet Connection DataAdapter DataTable Transaction DataRowCollection SelectCommand Command DataColumnCollection InsertCommand Parameters UpdateCommand ConstraintCollection DeleteCommand DataReader DataReaderCollection DataBase XML ADO.NET结构
1.2 实例分析:设计一个Form实现对数据表students、subjects和grade编辑和浏览功能 examplea1 程序设计的两种方式 • 自编程序设计方式:自编程序实例化dataset等对象、设置属性、动态连接数据库、实现数据绑定、数据表关联等。 • 可视化设计方式:设计阶段通过添加组件,设置组件属性完成数据库的连接、数据绑定以及数据表的关联等,然后由系统生成部分程序。
1.2.1 自编程序方式 一)把数据库中数据表数据显示在控件中 1)连接数据库:teaching (SQL Server) 2)数据库中数据载入DataSet:加载students 3)DataSet中数据和控件的绑定:id和TextBox的绑定
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中包含了连接字符串。
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”结果载入。
3)DataSet中数据和控件的绑定:id和TextBox的绑定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();
二)复杂绑定:classid数据取自classes表,提供选择功能。二)复杂绑定: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");
三)加入浏览功能 • 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同样值不变。
四)使用绑定对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();
五)通过控件输入的DataSet数据的取消和确认功能五)通过控件输入的DataSet数据的取消和确认功能 • 控件输入数据与DataSet数据同步: this.BindingContext[dataSet, “students”].Position值变化或调用this.BindingContext[dataSet, “students”].EndCurrentEdit()后,界面控件数据才写入DataSet。 • 取消修改: dataSet.RejectChanges():取消dataSet创建或调用dataSet.AcceptChanges()以后对dataSet所作的修改。 • 接收修改: dataSet.AcceptChanges():接收对dataSet中数据所做的修改。 • dataSet中各表及表中各行对象均有取消和接受修改的上述两个方法,区别是其作用的对象范围不同。
六)把DataSet数据存入数据库 • 更新数据库中数据表的唯一途径是执行SQL的insert、update或delete语句,dataSet中数据的修改(表的增、删、改),要反映到数据库中,必须根据修改情况产生并执行相应的SQL语句。所以要根据对DataSet数据表的修改更新对应数据库中的数据表,必须完成下列三部分工作: • 标记DataSet中数据表做过修改的所有行,并记录作了何种修改(增、删、改)。 • 编写或生成相应的带参数的insert、delete或update语句。 • 对每个修改行,根据修改的种类,执行相应的更新数据库的语句。
1)标记修改行及修改种类 • 在dataSet中表的每一行对象有一个RowState属性,记录了该行是否作了修改及作了何种修改的信息: dataSet.Tables["students"].Rows[0].RowState 其值可为:DataRowState.Added/Modified/Deleted和Unchanged。 • 当dataSet中表数据载入后,各行的状态为Unchanged,当对表中某行进行修改后,会根据修改类型自动改变该行的状态。在调用AcceptChanges后,对应行状态恢复到Unchanged状态。 • 由于更新数据库的update方法是依据该状态来确定哪些行要修改和做什么修改,所以在用dataSet数据更新数据库前,不要调用用AcceptChanges。
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。
生成带参数的SQL语句条件和时机: • 生成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语句的语句: • 在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的更新语句。
OleDbCommandBuilder其他说明: • 由上可知,SQL语句一但生成,改变oleDbDataAdapter的SelectCommand属性,由于生成SQL语句的第一个条件不满足,不会改变oldDbCommandBuilder中已生成的SQL语句。 • oleDbCommandBuilder.RefreshSchema()可用于清空生成的SQL语句(基类属性),可使生成SQL语句的第一个条件满足。 • 使用OleDbCommandBuilder的限制:select语句必须为单表的查询,包含主键,但不包含只读列(如计算列)。 • 为了生成 INSERT、UPDATE 或 DELETE 语句,OleDbCommandBuilder 会自动使用 SelectCommand 属性来检索所需的元数据集,以获得如列名等信息,所以会降低执行效率。
(2)编写更新数据库的SQL • 如果Fill一个DataSet数据表对应的select语句牵涉多个表,就不能使用oleDbCommandBuilder生成更新数据库的SQL语句,必须手工编写。 • 目标:在Form的下方用DataGrid显示当前学生的各门课的名称及成绩,可以修改成绩但不能添加和删除,点击保存按钮后保存修改内容。 • 增加下列Form类成员: private DataRelation dataRelation; //students和grade的关联 private OleDbDataAdapter oleDbDataAdapter1;//用于grade表
把grade数据加载到DataSet private 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;//缺省
//定义第二个参数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.id dataRelation = new DataRelation("students_grade", dataSet.Tables["students"].Columns["id"], dataSet.Tables["grade"].Columns["id"]); //将关系加入dataSet.Relation中 dataSet.Relations.Add(dataRelation); } • 在LoadData方法最后加LoadGrade()
设置并显示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”;
//定义两个在DataGrid中显示的列:课程名称和成绩 //定义两个在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()
3)更新数据库 • 使用下列语句更新数据库(保存按钮的click事件): //确保把和dataSet中students及grade绑定的控件数据写入dataSet this.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的方法)
例:commandBuilder对象的属性值对象dataAdapter的update方法调用commandBuilder的方法GetSQL例:commandBuilder对象的属性值对象dataAdapter的update方法调用commandBuilder的方法GetSQL 调用示例:若没有第二个语句,即dataAdapter不和commandBuilder关联,仍能调用update,但返回null。 DataAdapter dataAdapter = new DataAdapter(); CommandBuilder commandBuilder = new CommandBuilder(dataAdapter); MessageBox.Show(dataAdapter.update()); 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; } }
七)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。 • 修改成绩,平均成绩即刻更新。
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’";
Expression中对父表或子表关系引用 • 通过在列名称前面加 Parent,就可以在表达式中引用父表。例如,Parent.Price引用父表的名为 Price的列。 • 通过在列名称前面加一个 Child,就可以在表达式中引用子表中的列。因为子关系可以返回多行,所以必须在聚合函数中包括对子列的引用。例如,Sum(Child.Price)将返回子表中名为 Price的列的总和。 • 如果某个表有多个子表,则语法是:Child(RelationName)。例如,如果某个表有两个子表,父表名为 Customers,一个子表名为Orders,则 DataRelation对象被命名为 Customers2Orders,引用将为:Avg(Child(Customers2Orders).Quantity)
八)使用Tag属性绑定列 • 目标:使用RadioButton选择性别,但Students.sex类型为bit,0表示男,1表示女。 • 需要在一个GroupBox中放两个RadioButton,其Text分别为“男”和“女”,而实际只需要获得一个RadioButton的Checked状态就能确定性别。 • 由于值不同,所以不能使用RadioButton的Text属性和students.sex绑定,可用Tag属性与其绑定。这样的设置必须完成两部分工作: • Tag值改变时(浏览时)要对RadioButton的状态作相应改变 • RadioButton状态改变时要改变Tag值
RadioButton状态改变时改变Tag值 • 在RadioButton的CheckedChanged事件中实现: if (radioButton1.Checked) radioButton1.Tag = false; else radioButton1.Tag = true; • Students的sex属性类型为bit,其值只能取0和1,与radioButton1.Tag绑定后,Tag的值为true和false。
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; } }
何时调用该方法: • 方案一:在窗口打开后(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);
九)使用程序对dataSet中数据表的插入、删除、修改和检索。九)使用程序对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)
检索: • 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快很多。
检索实例: • 目标:输入学生编号,按“检索”按钮后若该学生不存在,则提示,若存在,则显示该学生信息: • 分析:定位控件显示的行的唯一方法是设置Binding Context的Position属性,而Find和Select返回的是dataRow,余下的问题是如何由已知行dataRow获得该行在table中的行序号,.Net提供了该方法: dataSet.Tables["students"].Rows.IndexOf(dataRow); • 以下使对学生编号检索的程序:
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; } }
1.2.2 可视化设计 • 构造DataSet:前面的设计方式,必须在程序运行后,才能确定DataSet的结构(包含哪些Table和Relation等),.Net可以使用XML文件构建DataSet类,这使程序设计和运行可不依赖于和数据库的连接,并且使数据库结构修改后,只要修改对应的XML文件,而不必修改程序。 • 类型化DataSet和非类型化DataSet:前者在程序设计时定义(可编写或生成)DataSet类的内部结构,编译时进行类型检查,所以类型安全,后者则在程序运行时产生DataSet对象的内部结构(1.2.1中例子的方式),可能由于数据类型不一致而使运行时更容易发生错误。
设计步骤: • 根据数据库中表结构生成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文件中生成。
两种类型DataSet数据的访问的区别: • 非类型化DataSet(弱类型DataSet) dataSet.Tables[“students”].Columns[“id”]或 dataSet.Tables[0].Columns[0] • 类型化DataSet(强类型DataSet) dataSet.students.idColumn
1.2.3 报表设计:CrystalReport 基本原理: • 使用报表设计器设计报表,设计内容存入特殊格式的CrystalReport.rpt文件中,该报表文件也可供其他语言(C++或VB)开发的程序使用。 • 同时生成了一个继承于ReportClass的报表类定义文件CrystalReport.cs,其中包含了该类与rpt文件的关联,用户通过该类实现对上述设计报表的操作。在该类定义文件中可以加入工具箱中的组件(把组件拖入CrystalReport.cs[Designer]页面)。 • 报表通过可视化控件CrystalReportViewer显示。用户只要实例化一个报表类对象,并把包含数据的DataSet对象传给它,由报表类完成对rpt文件的解析,获取数据并在CrystalReportViewer中显示报表。
报表的数据来源-用非类型化DataSet产生XSD • 使用CrystalReport,在设计阶段必须确定报表的数据来源,即必须构建DataSet框架,报表设计时各数据项可选自构建好的DataSet。 • 在程序运行时,通过报表类的SetDataSource方法把实例化的DataSet(包含数据的)对象传递给报表,该DataSet必须包含报表设计时所用到数据项,或保证两者数据模型完全一致。 • 由于本例DataSet为非类型化,可使用下列语句把DataSet模型输出到XSD文件,供报表设计用: dataSet.WriteXmlSchema("dataset1.xsd");
一)设计List报表:学生基本信息表\(CryStalReport1)一)设计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)或主键自动建立表间关系。 • 选择报表中要输出的列:
选择分组信息: • 选择统计信息: • 分组排序: • 报表中是否要包含图表 • 过滤条件 • 报表的风格: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表的关联。
(2)设置Page Head: • 在工具栏选择Text Object,放入Page Header。 • 右击该Text Object,选择Edit Text Object,输入“学生基本信息表” • 右击该Text Object,选择Format Object,设置其字体和对齐方式 • 在Field Explorer中的Special Fields,选择Print Date,并把它拖到上述的标题下。
(3)设置Details • 把Field Explorer中students的id、name、sex和Height和classes的name拖入Details中。 • 编辑Page Header中自动产生的Text Object,输入对应中文,使用Format Object设置对齐方式为居中。 • 右击报表,选择Preview Report,可预览报表,由于报表设计不需要连接数据库,所以数据为根据类型自动生成的模拟数据。 • 调整数据项位置、尺称、字体、对齐方式等 • 使用工具栏中的Box Object和Line Object为表格加上线框,线和框可跨越报表的不同区域。
(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。
(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列下。
分组修改: • 右击GroupHeaderSection1的灰色条带,选择Group Expert,可以对分组进行增加、删除和修改。(要删除Group Header Section必须先在Group Expert中移除对应分组) • 可以对每个班级分组中再对性别分组,分别统计班级男同学和女同学的平均身高。 • 第二个分组一定是在第一个分组下的分组,不可能同时存在两个并列的分组,就如同不可能存在两个并列的排序一样。
(6)排序和过滤:每个分组要求按姓名排序。(6)排序和过滤:每个分组要求按姓名排序。 • 右击报表 • 选择Report/Record Sort Expert,其中已经存在按分组1和分组2的排序(分组必须排序),把students.name加入Sort Fields。 • 选择Report/Selection Formula/Record或Group对行或分组加入过滤条件。