|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
J2EE比较成熟一点,一些比较出名的企业应用软件都是基于J2EE的。以后的发展就不好说了。不过java比较烦,学.net的话,微软把很多工具都封装好了,学起来可能容易一点。ado|工具|数据|优化毫无疑问,ADO.NET向人们供应了一种功效壮大、仿真数据库的工具模子,它能够将数据纪录保留到内存中。特别是ADO.net的DataSet类,它不仅在功效上相称于数据库表的会合存储器(centralrepository),并且撑持表间的各类束缚和逻辑干系。进一步说来,DataSet工具实际上是一种离线数据容器。乍一看,只需把DataSet类的一切特征团结起来,就可以打消SQL查询命令中的庞大子句,好比那些众多成灾且层层嵌套的INNERJOIN子句大概GROUPBY子句等。庞大的子句能够分化成两个或更多个互相自力的复杂子句,而将每一个复杂子句的查询了局分离保留在分歧的DataTable工具中;今后只需剖析这些内存数据之间的束缚和逻辑干系,就可以重修本来表之间需要的“参照完全性”(referentialintegrity)。
举个例子:你能够把客户(Customers)表与定单(Orders)表分离保留到两个分歧的DataTable工具中,然后经由过程DataRelation工具举行绑定(bind)。如许,SQLServer(或别的DBMS体系)就免去了INNERJOIN子句带来的极重包袱;更主要的是,收集传输负荷也因而而年夜年夜加重。象如许简化SQL查询的计划当然卓有成效,却其实不必定老是最好选择,特别是当你的数据库范围复杂并且更新频仍时。
本文将为人人先容另外一种用于简化SQL查询的手艺,它充实使用ADO.NET的内存数据工具加重了用户和DBMS体系的包袱。
分化SQL查询命令
很多有关ADO.NET的书本,好比DavidSceppa的高文《ProgrammingADO.NETCoreReference》(微软出书社),都倡议把庞大的SQL查询命令分化成多少复杂的子查询,然后把各个子查询的前往了局分离保留到统一个DataSet容器外部的多少个DataTable工具中。请看一个实例。
假定你必要猎取一些客户定单信息,请求定单是提交于指定年份并且按客户举行分组,还请求定单中最少包括30件商品。同时,你还但愿猎取每一个定单的提交者(employee)名字和客户(customer)的公司名。你能够用以下SQL查询语句来完成它:
DECLARE@TheYearint
SET@TheYear=1997
SELECTo.customerid,od.orderid,o.orderdate,o.shippeddate,
SUM(od.quantity*od.unitprice)ASprice,
c.companyname,e.lastnameFROMOrdersASo
INNERJOINCustomersAScONc.customerid=o.customerid
INNERJOINEmployeesASeONe.employeeid=o.employeeid
INNERJOIN[OrderDetails]ASodONo.orderid=od.orderid
WHEREYear(o.orderdate)=@TheYearANDod.orderid=o.orderid
GROUPBYo.customerid,c.companyname,od.orderid,
o.orderdate,o.shippeddate,e.lastname
HAVINGSUM(od.quantity)>30
ORDERBYo.customerid
临时抛开你所用的ADO大概ADO.NET吧。用最原始的命令提交体例实行上述SQL查询,能够看到如所示的了局集:
.第一个SQL查询命令的输入了局,由SQLServerQueryAnalyzer天生并显现。
在本次查询中,以一便条句为中心,而别的两条INNERJOIN子句起帮助感化。中心子句的功效是从数据库中查询一切提交于指定年份、最少包括30件商品的定单。中心子句以下:
SELECTo.customerid,o.orderid,o.orderdate,
o.shippeddate,SUM(od.quantity*od.unitprice)ASprice,o.employeeid
FROMordersASo
INNERJOIN[OrderDetails]ASodONo.orderid=od.orderid
WHEREYear(o.orderdate)=@TheYearANDod.orderid=o.orderid
GROUPBYo.customerid,o.orderid,o.orderdate,o.shippeddate,
o.employeeid
HAVINGSUM(od.quantity)>30
ORDERBYo.customerid
在前往了局会合,客户和提交者均用ID来暗示。但是,本例必要的是客户的公司名(compayname)和提交者的名字(lastname)。开端的ORDERBYo.customerid语句显得出格复杂,但是其功效却很主要:因为客户公司名和提交者名字所含的字符较多,利用该语句就可以制止它们的反复呈现,从而失掉更松散的了局集。
综上所述,全部SQL查询能够被分化成3便条查询命令――1条中心子查询,用于猎取定单纪录;2条帮助子查询,用于创建提交者ID-提交者名字和客户ID-客户公司名两个对比表,即:
SELECTemployeeid,lastnameFROMEmployees
SELECTcustomerid,companynameFROMCustomers
以下ADO.NET代码演示了怎样把这3便条查询的前往了局集保留到DataSet工具中。
DimconnAsSqlConnection=NewSqlConnection(connString)
DimadapterAsSqlDataAdapter=NewSqlDataAdapter()
conn.Open()
adapter.SelectCommand=NewSqlCommand(cmdCore,conn)
adapter.SelectCommand.Parameters.Add("@TheYear",1997)
adapter.SelectCommand.Parameters.Add("@TheQuantity",30)
adapter.Fill(ds,"Orders")
adapter.SelectCommand=NewSqlCommand(cmdCust,conn)
adapter.Fill(ds,"Customers")
adapter.SelectCommand=NewSqlCommand(cmdEmpl,conn)
adapter.Fill(ds,"Employees")
conn.Close()
请注重:在一连实行SQL查询命令时,你一般都要自行操纵数据库毗连,以避免呈现过剩的open/close操纵。本例的adapter.Fill办法会主动实行open/close操纵,除非你设置adapter.SelectCommmand属性把某个毗连显式联系关系到adapter工具之上。
为了创建内存数据表之间的干系链,你能够创立两个干系,把employeeid(提交者的ID)联系关系到lastname(提交者的名字),把customerid(客户的ID)联系关系到companyname(客户的公司名)。一样平常情形下,能够用DataRelation工具在统一DataSet工具内创立两个自力表之间的一对多干系。但是,本例却必要创建多对一干系,这是很少见的。实在,只需把一对多干系中的父表(Orders)酿成子表,而把子表(Employees、Customers)酿成父表就好了。
.干系中的父表与子表脚色交换
ADO.NET中的DataRelation工具相称天真,足以构建多对一干系。每天生一个DataRelation工具,ADO.NET城市在背景为之创建分歧性束缚,以避免父表内的键值反复。固然了,一旦反复的键值呈现,ADO.NET就会抛出(throw)一个破例。请看以下代码:
DimrelOrder2EmployeesAsDataRelation
relOrder2Employees=NewDataRelation("Orders2Employees",_
ds.Tables("Orders").Columns("employeeid"),_
ds.Tables("Employees").Columns("employeeid"))
ds.Relations.Add(relOrder2Employees)
此处的DataRelation工具机关器初始化了三个参数:第一个是干系称号,前面两个是DataColumn工具,分离代表组成干系的两个列(column):前一个DataColumn工具代表父列,后一个DataColumn工具代表子列。一旦机关器发明父列中不存在正当纪录,便会激活(raise)一个ArgumentException破例。打消此破例最复杂的办理计划是在机关器中增加一个布尔值作为第四参数:
relOrder2Employees=NewDataRelation("Orders2Employees",_
ds.Tables("Orders").Columns("employeeid"),_
ds.Tables("Employees").Columns("employeeid"),_
False)
当机关器的第四参数值为false时,ADO.NET就不会创建分歧性束缚,尔后者恰是激发ArgumentException破例的祸首罪魁。
设置了数据干系以后,你就能够用列表达式(computedcolumn)给Orders表增加两列以显现其内容了。实际上,这么做完整切合逻辑:
DimordersAsDataTable=ds.Tables("Orders")
orders.Columns.Add("Employee",GetType(String),_
"Child(Orders2Employees).lastname")
orders.Columns.Add("Customer",GetType(String),_
"Child(Orders2Customers).companyname")
惋惜,它基本行欠亨。更糟的是,当它运转到包括Child的代码时,就会抛出(throw)一条“句法毛病”信息,而这条堕落信息很简单误导程序员。(有关列表达式的更多信息,请参阅《lastmonthscolumn》。)
为何会堕落?由于只要当父列存在分歧性束缚时,才同意在列表达式中利用Child关头字。这一点在开辟文档中并未明白指出,但是它倒是现实,并且十分主要。使人不解的是,你不仅能够顺遂地会见Orders表任一行的子元素,还能间接会见Employees表或Customers表的任一列。以下代码能够证实这一点:
DimordersAsDataTable=ds.Tables("Orders")
DimemployeeAsDataRow=orders.Rows(0).GetChildRows(relOrder2Employees)
MsgBoxemployee("lastname")
因而,所谓的“句法毛病”,其实不代表你没法创建多对一干系。它只是提示你:除非事前创建分歧性束缚,不然就不克不及在列表达式中利用Child关头字。
在初始干系中,Orders表是父表。但是,为了从ID猎取提交者名字或客户公司名,你就必需改动诸表所饰演的脚色:让Orders表充任子表,而让Employees表、Customers表充任父表。为了确保做到这一点,你必需改动DataRelation工具机关器代码中的列名,而且象如许利用列表达式:
DimordersAsDataTable=ds.Tables("Orders")
orders.Columns.Add("Employee",GetType(String),_
"Parent(Orders2Employees).lastname")
orders.Columns.Add("Customer",GetType(String),_
"Parent(Orders2Customers).companyname")
小结:在本例中,我们把一个庞大的SQL查询分化成3个较为复杂的子查询,从而打消了两个INNERJOIN语句,加重了数据库服务器的包袱;更主要的是,年夜年夜削减了从服务器到客户真个收集传输负荷。看来,这仿佛是最好的办理计划了?
替换计划
后面的办理计划是以对比表为基本的,并且在对比表的天生过程当中没有举行数据过滤。一旦对比表的范围过年夜,会有甚么成果呢?岂非你乐意为了失掉戋戋数百个提交者的名字就从服务器下载10,000笔记录?岂非你宁愿下载那一年夜堆冗余数据?更况且,那些冗余数据对你毫无用途!
但是,请换个角度想想。对比表在使用程序的全部性命周期中常常都是有代价的。也换言之,固然对独自一次查询来讲,下载很多纪录以构建完全的对比表不免过于奢靡,可是它对全部使用程序来讲也一定不是公允买卖。
既然如许,我们何不实验用另外一种手艺来缩减对比表的范围呢?最简单想到的计划莫过于借助WHERE子句来减少了局集了。十分不幸,此计划要末难以完成,要末效果欠佳,特别是在对比表诸列其实不包含你所要查询的工具时。比方:为了对提交者的名字举行过滤,你就必需对别的表举行团结查询――好比Order表和OrderDetails(定单细节)表。我以为,最好计划是从头猎取前次SQL查询的前往了局集,并从中剖析出每一个提交者的信息。也就是说,完成前述SQL查询以后,再次发送一个几近不异的查询命令,令数据库服务重视新运转分化后的子查询。如许,数据库将以最小的查询价值前往完整不异的数据。更妙的是,SQL服务器还出格设置了查询优化引擎,使得此类反复查询的价值减到最低。
SELECTDISTINCTt.customerid,t.companynameFROM
(SELECTo.customerid,o.orderid,o.orderdate,o.shippeddate,
SUM(od.quantity*od.unitprice)ASprice,
c.companyname,o.employeeid
FROMordersASo
INNERJOINCustomersAScONc.customerid=o.customerid
INNERJOIN[OrderDetails]ASodONo.orderid=od.orderid
WHEREYear(o.orderdate)=@TheYearANDod.orderid=o.orderid
GROUPBYo.customerid,o.orderid,c.companyname,
o.orderdate,o.shippeddate,o.employeeid
HAVINGSUM(od.quantity)>30)ASt
总而言之,以多少复杂查询为基本而计划的材料检索代码最年夜的长处是:它把数据联合(joining)的重担由服务器转移到了客户端。另外一方面,因为客户端把数据纪录散布于几个互相自力、易于链接的表内,因此数据查询操纵非常天真。
用一些短小、复杂的SQL查询从数据库读取纪录并分离保留到分外的DataTable工具中,这对客户端使用程序来讲是一个好动静。但是,假如这些子查询前往的部分数据不切合分歧性束缚,那又会怎样呢?
事件的使用
一般,每一个查询命令,不管它有多庞大,都是在统一个默许的事件(transaction)中实行的。正由于云云,它才干确保在实行过程当中不会由于别的代码的搅扰而损坏数据的全体分歧性。但是,假设你把作为逻辑全体的查询命令分化成为多少子查询,了局又会怎样?
无妨假定你的软件必要处置一个富于变更的情况,并且数据库中的纪录也在敏捷更新。也许你的子查询还在实行过程当中,数据就已被某个用户交换了。在后面的典范程序中,这类不测尚且不会形成多年夜的丧失,由于第二个和第三个子查询命令仅仅处置已天生的对比表。只管云云,一旦有人在你猎取定单数据后把某个提交者的纪录删除,你所查询到的数据仍是大概违反分歧性束缚。因而,必需把分化失掉的子查询及相干的处置代码全体放进统一个事件中运转。除此以外,你别无选择。
所谓事件(Transaction),是指一组操纵,它在实行时严厉恪守以下划定规矩:
・不成分性(原子性Atomicity)
・分歧性(Consistency)
・自力性(Isolation)
・延续性(Durability)
人们一般提取这4条划定规矩(4种属性)的首字母合称ACID。对本例而言,最主要的划定规矩(属性)是自力性。所谓自力性,是指数据库有才能确保每一个正在运转中的事件不被任何别的并行事件所搅扰。当你的查询命令阃在某个事件中运转的时分,假如别的用户的数据库操纵也在其余事件中同时运转,则你终极失掉的了局将与事件的自力性品级有关。一般情形下,数据库能依据每一个事件中的操纵公道分派自力性品级。假如使用程序请求数据坚持相对的分歧性,毫不允许呈现“虚幻行”(phantomrows)时,它就必需取得“可串行”(serializable)自力品级。
当一个“可串行”的事件在运转时,它将锁定统统相干表,避免任何其他用户更新或拔出字段。只要当事件运转终了时,表才会解锁。在此自力性品级下,“读净化”(dirtyreads,即读进未受权的数据)和“虚幻行”(phantomrows,即还没有纪录的行,大概已被别的事件删除的行)天然不会呈现;但是,数据的全体分歧性仍旧没法确保。
既然你的子查询命令在运转过程当中大概呈现提交者纪录被删除、定单纪录被修正等情形,那末你固然应当把一切子查询都包裹到一个“可串行”的事件中。
SETTRANSACTIONISOLATIONLEVELSERIALIZABLE
BEGINTRANSACTION
--getOrders
--getCustomers
--getEmployees
COMMITTRANSACTION
再夸大一次,假如前提同意,你的事件就应当具有“可串行”自力性品级。假如你忧虑锁定全体表会带来倒霉影响的话,请无妨尝尝ADO.NET的内存数据工具。稍后我们将会商这些工具。
排除数据联合
让我们回过火来看一看本文开首谁人查询典范。它的方针是从数据库中读取在指定年份提交且所含商品件数切合前提的一切定单纪录;我们也必要晓得定单的总数、客户的公司名和定单提交者的名字。
DECLARE@TheYearint
DECLARE@TheAmountint
SET@TheYear=1997
SET@TheAmount=30
SELECTo.customerid,od.orderid,o.orderdate,o.shippeddate,
SUM(od.quantity*od.unitprice)ASprice,
c.companyname,e.lastnameFROMOrdersASo
INNERJOINCustomersAScONc.customerid=o.customerid
INNERJOINEmployeesASeONe.employeeid=o.employeeid
INNERJOIN[OrderDetails]ASodONo.orderid=od.orderid
WHEREYear(o.orderdate)=@TheYearANDod.orderid=o.orderid
GROUPBYo.customerid,c.companyname,od.orderid,
o.orderdate,o.shippeddate,e.lastname
HAVINGSUM(od.quantity)>@TheAmount
ORDERBYo.customerid
以上SQL查询命令的确能够一次性地前往全体所需材料。只需让它们在统一个事件中运转,就可以确保前往数据的分歧性和“可串行”性。但是此计划已过期,我们也没有选用它。为何呢?
现实上,它存在两个成绩:第一,前往了局集各行分离来自3个分歧的表:
・定单(Orders)
・客户(Customers)
・提交者(Employees)
这还不包含OrderDetails表。
第二,INNERJOIN语句形成一些不用要的数据挪动。我们没法办理第二个成绩,但是某些ADO.NET代码却有助于办理第一个成绩。因而,我们仍旧无机会进步全部办理计划的可行性和无效性。
详细思绪以下:起首实行SQL查询,将前往了局集保留到一个DataTable工具中;然后把DataTable中的数据分离到3个分歧却又相干的DataTable工具中。终极输入的了局与分离查询诸表没甚么区分,可它却节俭了界说和设置“可串行”事件的开支,同时也制止了从数据库下载过剩纪录;十全十美是每行都大概包括大批冗余信息。
什么时候能够接纳本计划呢?我发明,当客户端必要借助group-by函数及各类过滤器来组建一个庞大的“主从复合布局”(master/detail)视图时,本计划是一个不错的选择。特地提一句,此时接纳多个分歧而相干的表长短常无效的,ADO.NET也为此供应了很多优化特征。
我们来会商详细操纵。以下代码树模了它的次要流程:
FunctionSplitData(ByValdsAsDataSet)AsDataSet
Dim_datasetAsNewDataSet()
MakeafullworkercopyoftheDataSet
_dataset=ds.Copy()
CreateCustomers(_dataset,ds)
CreateEmployees(_dataset,ds)
RemovecolumnsfromOrders(companyname[2]andlastname[4])
_dataset.Tables("Orders").Columns.RemoveAt(1)
_dataset.Tables("Orders").Columns.RemoveAt(3)
Return_dataset
EndFunction
代码起首完全地复制了DataSet工具(ds),以它作为新DataSet工具(_dataset)中的Orders表。接上去,代码又在新DataSet工具中静态增加了customers表和employees表。最初,它又重新DataSet工具的Orders表中删除别的两个子表所包括的列。下图显现了新DataSet工具中customers表的内容。瞧,它只留下了Orders表中(一切)定单的客户ID和公司名两列。因为这两个表都有customerid列,故它们仍旧能够创建干系。
.依据第一次查询的前往了局重生成的Customers表
上面来复杂谈谈用于创立和添补customers表与employees表所必须的代码。
一入手下手,你必需挪用clone办法克隆本来的定单表以创立一个新的DataTable工具。与Copy办法分歧,Clone办法仅仅复制元数据(metadata)。因为DataTable接口不同意克隆单个列,以是本办法是天生对等表的最复杂路子。但是,如许天生的表将包括某些过剩列,我们必需删除之。
只需剖析第一个DataSet工具的布局,你就会发明customerid列和companyname列恰是前往了局集的第一列和第二列。
Dim_customersAsDataTable=orig.Tables("Orders").Clone()
_customers.TableName="Customers"
Removeunneededcolumns
DimiAsInteger
Fori=2To_customers.Columns.Count-1
_customers.Columns.RemoveAt(2)
Next
创建表布局以后,还得载进数据。但是,在Orders表中大概屡次呈现统一个提交者。别的,你必需对源DataSet工具中的数据加以过滤。幸亏Orders表已依据customerid列举行排序,以是你只需轮回遍历一切行,从当选出切合前提者便可。
DimrowAsDataRow
DimcustomerKeyAsString=""
ForEachrowIn_dataset.Tables("Orders").Rows
AlreadysortedbyCustomerID
IfcustomerKeyrow("customerid")Then
selectdistinct
_customers.ImportRow(row)
customerKey=row("customerid")
EndIf
Next
AddtotheDataSet
_dataset.Tables.Add(_customers)
ImportRow是从数据库导出指定行到新表的最快路子。一般,ImportRow办法会依据形式(schema)的请求来选择被导出的列。
准绳上,employeess表的创立和customers表的创立大致不异。固然了,你应当删除的列有所分歧。从Orders表的布局来剖析,我们必需保存第3列与第4列。以下代码起首删除第1列和第2列,然后用一个轮回办理别的列。
Dim_employeesAsDataTable=orig.Tables("Orders").Clone()
_employees.TableName="Employees"
Removeunneededcolumns
_employees.Columns.RemoveAt(0)
_employees.Columns.RemoveAt(0)
DimiAsInteger
Fori=2To_employees.Columns.Count-1
_employees.Columns.RemoveAt(2)
Next
最初,你还必需扫除employees表中的反复行。在本例中,对Orders表的排序有助于简化该操纵。你能够先创立Orders表的已排序视图(sortedview),然后轮回遍历一切行。
DimemployeeKeyAsInteger=0
DimviewAsDataView=NewDataView(_dataset.Tables("Orders"))
view.Sort="employeeid"
DimrowViewAsDataRowView
ForEachrowViewInview
IfemployeeKeyConvert.ToInt32(rowView("employeeid"))Then
selectdistinct
_employees.ImportRow(rowView.Row)
employeeKey=Convert.ToInt32(rowView("employeeid"))
EndIf
Next
AddtotheDataSet
_dataset.Tables.Add(_employees)
总结
本文树模了一个庞大的SQL查询实例,并会商了3种进步其效力的计划。不能不供认,典范的ADO关于此类成绩的办理匡助无限,而ADO.NET却能让你机关一种功效壮大的离线数据工具模子以进步程序功能。本文提到了几种办理计划,事实哪一种是最好选择?很难说。影响运转效力的要素十分多,好比:收集的无效带宽,数据的承继分歧性,程序对数据不乱性的请求,等等。为了断定最好计划,你就必需一一实验各类计划,分离测试其功能。
据说很厉害,甚至可以把C#也干掉^_^,不过也很复杂,本来C++已经够复杂的。有人甚至还提出把这个东东引进标准,我觉得基本上不可能的。 |
|