DA01 - Data Abstract 总览

 
DA01 - Data Abstract 总览       
数据接口的艰难选择
数据接口框架 (DAF) 是一组在常见的数据库中查询数据源 , 执行命令的组件 ( 通常由供应商提供 ). 常见的 Delphi 数据接口框架包含 Borland dbExpress, 微软的 ADO, Jason Wharton IBObjects, CoreLabs SDAC/ODAC 等等 .
数据接口框架通常包括两个类型 :
  1. 多驱动: 而可以使用特定的驱动操作不同数据库(ADO, BDE, dbExpress).
  2. 专用的:为目标数据库提供高效操作,支持数据库所有特性 (i IBObjects, IBExpress, CoreLabs SDAC/ODAC).
如果需要使用多种数据库 ( Oracle 和微软的 SQL Server), 应该选择多驱动的 DAF. 当只用一种数据库时 , 很明显最好选择专用的 DAF. 选择是否很简单明了 ? 当然不是 .
使用常用数据接口架构的问题
当多驱动 DAF 实现操作多数据库的同时 , 他们仍然要面对处理多种 SQL 方言的负担 . 当在 Oracle7 MS SQL Server 中查询 Orders 表并 Join Customers 表时 , 你应该可以意识到这对于你的项目组生产力意味着什么 . 另外 , 多驱动 DAF 操作数据库时要比专用驱动效率底下 ; 这意味着很多驱动都能运行良好 , 但有些却缺乏灵活和可靠性 . 依赖于你选择的通用 DAF, 可能会发现特殊的驱动不存在 , 甚至你的系统已经开始开发了 .
专用数据接口架构的问题
专用 DAF 之间差距很大 . 当你安装了 IBObjects ,Delphi 组件面板上将有不少于 7 个新标签 , IBExpress 则只有两个 . 这意味着如果你真的使用了特殊架构的所有特性将会绑定的非常牢固 , 相互之间切换不重做几乎无法实现 . 明显 , 你可能不需要转换而且当前的 DAF 可能也很完美 , 但是如果可以转换不是更好吗 ?
ODAC DAO 是另外一个好的范例 : 假设你开始时使用 DAO, 后来客户要求你解决存取安装在客户端的 OCI. DAO 转换到 ODAC 需要多少时间 ?
这还没有考虑如果三方供应商停止提供产品支持时的情况 .
业务讨论
如果一个系统能运行在多数据库是一个很明显的优势 .
系统可能一开始很小使用 Firebird, 假设从来没有遇到速度问题 , 或需要运行在 Oracle . 突然 , 产品开始受到重视一个大公司要求它支持 Oracle. 在你及竞争者之间选择 . 你会放弃这个客户吗 ? 如果你的最近的和最大的客户要求数据库可以容纳 300G 数据而你原来的数据库没有这个能力怎么办 ?
可运行于多数据库可以增加你的商用潜能 . 直到如今问题是这种系统相对于其功能代价高昂 .
DA01 - Data Abstract 总览_第1张图片
 
Data Abstract 解决方案
Data Abstract 设计用来解决上面列出的所有问题 以及 其他一些通常涉及多层应用设计的重点 .
Data Abstract 封装多驱动和专用 DAF, 提供它们所有的优点而没有引入它们的缺点 .
Data Abstract 驱动
Data Abstract 驱动通过封装专用驱动实现 . 这意味着我们不用在去发明车轮 , 而是使用现有的最好的方式 .
如果你决定使用 Interbase, 你可以使用安装在 IBObjects, IBExpress dbExpress 中的驱动 .
如果你需要存取 Microsoft SQL Server, 你需要选择 ADOExpress, CoreLab's SDAC dbExpress.
如果你不喜欢使用它们 , 你可以自己写一个驱动 !
产品对发展中的驱动提供了简单的接口 . 要求你去实现 4 个基类 . 一个驱动的包含在单元中的源代码大约有 300 .
例如 , 基于 ADOExpress 驱动单元 , 有一个方法必须要写 :
procedure TDAEADOConnection.DoCommitTransaction;
begin
  ADOConnection.CommitTrans();
end ;
你可以为所有的列表数据创建存取驱动 , 如逗号间隔的文件或硬盘数据 . 事实上 , Data Abstract 同时也提供了一个简单的磁盘驱动允许查询计算机磁盘上的文件名 .
Data Abstract 驱动可以在运行时动态的从 DLL 文件中加载 , 或静态编译到你的模块 . 一个特别的组件 TDADriverManager, 提供所有驱动管理方面的全面控制 .
这是一个包含在 Data Abstract 中的驱动管理的简单应用程序 . 静态连接到 ADOExpress IBExpress 驱动并在运行时动态加载 IBObjects, DiskDrive SDAC 驱动 :
DA01 - Data Abstract 总览_第2张图片
下面的对话框更详细的说明内部情况 :
DA01 - Data Abstract 总览_第3张图片
Data Abstract 创建广泛的用户接口
接口非常强大同时又非常少的依赖 Delphi 特性 . 因为 Delphi 的数据存取库定义在最初版本 , 那时语言还没有接口的定义 . 重新设计 VCL 支持它们又会是很多程序无法运行 .
Delphi.Net 也支持 .NET 框架中的基于接口的 ADO.Net. 在那之前 , 我们只能使用组件或包含于 MIDAS IProviderSupport.
为了抽象 DAF 并简化应用程序代码 , 我们决定设计 Data Abstract . 连接 , 数据集 , 存储过程仍然存在 , 但是不再像以前一样拖放到窗体 ; 你需要在特殊的控件 ConnectionManager Schema 中声明它们 .
让我们比较一下代码 . 本例我们打开一个数据集并将一个字段的值显示在 TListBox .
标准的代码方式
var myds : TDataset;
    i : integer;
begin
  myds := TDataset.Create(NIL);
 try
    myds.Connection := MyConnection; // MyConnection was open elsewhere
    myds.SQL.Text := 'SELECT Name FROM Customers' ;
    myds.Open;
    while not myds.EOF do begin
      ListBox.Items.Add(myds.Fields[ 0 ].AsString);
      myds.Next;
    end;
 finally
    myds.Free;
 end;
end ;
标准的 RAD 方式
var i : integer;
begin
 try
    myds.Open;
    while not myds.EOF do begin
      ListBox.Items.Add(myds.Fields[ 0 ].AsString);
      myds.Next;
    end;
 finally
    myds.Close;
 end;
end ;
Data Abstract 方式
var myds : IDADataset;
    i : integer;
begin
  // The last boolean parameter instructs to opens it
  myds := DASchema.NewDataset( 'CustomerList' , MyAbstractConnection, TRUE);
 while not myds.EOF do begin
    ListBox.Items.Add(myds.Fields[ 0 ].Value);
    myds.Next;
 end;
end ;
可见 , 第一个范例代码多于另外两个 , 但是 RAD 方式和 Data Abstract 方式代码量相同 . 事实上 , 如果你注意最后一个片段 , 你将看到操作数据集的关闭打开代码都省略掉了 , 这将减少错误倾向 .
你封装过 TDataSet 使其更容易使用 ? 当然必须你自己做 : 避免购买三方组件 .
现在我们做一个复杂一点的范例 , 让我们比较一下 RAD 方式和 Data Abstract 方式 . 假设我们想依靠方法传递过来的参数改变 Select Where 子句 .
RAD 方式
procedure SelectCustomers(const aCity, anAddress : string);
var whre : string;
begin
 try
    whre := '' ;
    myds.SQL.Text := 'SELECT Name FROM Customers'
 
    if (aCity<> '' ) then whre := '(City=' +aCity+ ')' ;
 
    if (anAddress<> '' ) then begin
      if (whre<> '' ) then whre := whre+ ' AND ' ;
      whre := '(Address=' +anAddress+ ')' ;
    end;
 
    myds.Open;
    [..]
 finally
    myds.Close;
 end;
end ;
Data Abstract 方式
procedure SelectCustomers(const aCity, anAddress : string);
var myds : IDADataset;
begin
 try
    myds := DASchema.NewDataset( 'Customers' , MyAbstractConnection, FALSE);
    // If a City is an empty string, the condition is not added
    myds.Where.AndIfNotEmpty(aCity, 'City' );
    myds.Where.AndIfNotEmpty(anAddress, 'Address' ); // Same...
    myds.Open;
    [..]
 finally
    myds.Close;
 end;
end ;
明显 , 也支持参数 .
现在我们看看最后的范例 .
最后的方法是如果你同时支持三种数据库 ( Oracle, SQL Server Interbase,) 应该怎么修改 ?
无论是你的数据库要做 JOIN 或其他操作都不只是需要修改一行代码 . 为了理解如何实现我们讨论两个 Data Abstract 的新控件 ConnectionManager Schema.
Data Abstract ConnectionManager
Data Abstract 假设同时只有一个数据模块用于连接到多个数据库 . 业务逻辑只能写一次并要尽量独立于具体的数据库 ( 例如 . 表和字段名 ).
第一件事情是要抽取数据库连接的定义 .
TDAConnectionManager 正是做这个的 : 保存 Data Abstract 连接字符串指向不同数据库 . 每个连接字符串都有一个标识名称和可选的描述 :
DA01 - Data Abstract 总览_第4张图片
记住你不需要在 Data Abstract 中实现多数据库运行的目标 . 但是如果你需要时可以更有弹性的修改 .
Data Abstract 的连接字符串使用基于架构和数据库的标准格式 . 看如下范例 :
使用 ADOExpress 连接 Microsoft SQL Server Northwind 数据库 :
ADO ?AuxDriver=SQLOLEDB.1;Server=localhost;Database=Northwind;UserID=sa
使用 IBExpress 连接 Interbase Employee 数据库 :
IBX?Server=localhost;UserID=sysdba;Password=masterkey;Database=C:/Program Files/Borland/InterBase/examples/Database/Employee.gdb
使用 IBObjects 连接 Interbase Employee 数据库 :
IBO?Server=localhost;UserID=sysdba;Password=masterkey;Database=C:/Program Files/Borland/InterBase/examples/Database/Employee.gdb
 
定义好连接后就可以如下方式使用了 :
begin
  connection := DAConnectionManager.NewConnection( 'EmployeeIBO' , TRUE);
end ;
可见为了抽象并隔离它们 , 连接使用其名称获取 .
连接属性
方法 TDAConnectionManager.NewConnection 的声明 :
function NewConnection(const aConnectionName :string;
 OpenIt : boolean = FALSE) : IDAConnection;
可见 , 返回类型是一个接口 , 自动实现引用计数而不用去手动释放 . 同时 IDAConnection 接口已经为所有的数据库开放了一个最小操作集合 .
接口 IDAConnection 的定义 :
IDAConnection = interface
[ '{6D9C806F-65A5-43B3-8F07-4ED782A13A0A}' ]
  // Properties readers/writers
  function GetConnectionString : string;
 procedure SetConnectionString(Value : string);
 function GetConnected : boolean;
 procedure SetConnected(Value : boolean);
 function GetName : string;
 
  // Transaction support
  function BeginTransaction : integer;
 procedure CommitTransaction;
 procedure RollbackTransaction;
 
  // Connection
  procedure Open;
 procedure Close;
 
  // Metadata
  procedure GetTableNames(out List : IROStrings);
 procedure GetStoredProcedureNames(out List : IROStrings);
 procedure GetTableFields(const aTableName : string; out Fields :
 TDAFieldCollection);
 procedure GetStoredProcedureParams(const aStoredProcedureName :
 string; out Params : TDAParamCollection);
 
  // Commands
  function NewStoredProcedure(const StoredProcedureName : string) :
 IDAStoredProcedure;
 function NewDataset(const SQL : string) : IDADataset;
 
  // Properties
  property ConnectionString : string read GetConnectionString write
  SetConnectionString;
 property Connected : boolean read GetConnected write SetConnected;
 property Name : string read GetName;
end ;
现在问题是 : 难道框架只支持部分功能集而没有提供对 IBObjects IBExpress 的弹性 ? 可以想象 , 答案是否定的 .
Data Abstract 为你连接到的特定数据库定义了附加接口 . , Interbase 连接支持如下附加接口 :
IIBConnectionProperties = interface
[ '{5F001B6F-4FB6-46B7-BC27-3326C4658F75}' ]
 function GetRole : string;
 procedure SetRole(const Value : string);
 function GetSQLDialect : integer;
 procedure SetSQLDialect(Value : integer);
 
 procedure Commit;
 procedure CommitRetaining;
 procedure Rollback;
 procedure RollbackRetaining;
 
 property Role : string read GetRole write SetRole;
 property SQLDialect : integer read GetSQLDialect write SetSQLDialect;
end ;
所以 , 为了设置连接的 SQLRole SQLDialect 需要如下代码 :
var ibprops : IIBConnectionProperties;
begin
  connection := DAConnectionManager.NewConnection( 'EmployeeIBO' , TRUE);
 if Supports(connection, IIBCOnnection, ibprops) then begin
    ibprops.Role := 'ADMIN' ;
    ibprops.SQLDialect := 3 ;
 end;
end ;
附加接口是不断发展的 , 你可以轻松的为一个已存在的驱动增加附加接口 . 你可以完全控制你的驱动支持什么 .
其他被所有驱动开放的接口是 IDAConnectionObjectAccess:
IDAConnectionObjectAccess = interface
[ '{FF8F2319-4EAE-4A2B-8713-A6E6B3F5E48A}' ]
  // Properties readers/writers
  function GetConnectionObject : TObject;
 function GetConnectionProperties(const aPropertyName : string) :
 Variant;
 procedure SetConnectionProperties(const aPropertyName : string;
 const aValue : Variant);
 
  // Properties
  property ConnectionObject : TObject read GetConnectionObject;
 property ConnectionProperties[const aPropertyName : string] :Variant
           read GetConnectionProperties write SetConnectionProperties;
end ;
IDAConnectionObjectAccess 提供了建立连接的 VCL 控件的存取接口 ( TIBDatabase, TADOCOnnection ).
Data Abstract Schema
Data Module 很容易由于存在过多的控件而变得混乱 , 尤其是 TDatasets.
当你需要支持多种数据库时 , 你可能会采用如下方式 :
  1. 动态创建TDataSet并通过代码调整不同数据库的SQL方言.
  2. 为每个要连接的数据库复制一份DataModule.
  3. 使用像ADO一样的DAF和一些TDatasets, 但是仍然要在.pas文件中写SQL代码.
这些情形都不是最好的方案 .
围绕着这个问题 , Data Abstract 引入了 Schemas 概念 .
Schema 是一组指向你系统中特定领域的逻辑业务 DataSet 和业务命令 ( 可能连接到一个业务对象 ) .
在下图展示的 Data Abstract Schema Modeler 中你可以可视化的建立业务 Schema.
DA01 - Data Abstract 总览_第5张图片
左上部的 Datasets 列表是一系列返回表格数据集的查询 . 左下部的 Commands 列表 , 是一系列无数据集返回的 INSERTs, DELETEs, UPDATEs 或调用存储过程的操作 .
每个数据集和命令都可能在你的每个目标数据库中执行特定的 SQL 命令 . 注意 , 下面的截图中 SQL 属性与上面的不同 :
DA01 - Data Abstract 总览_第6张图片
业务数据集不必从相关的数据库中精确复制 ; 它们可以独立定义 . 在本例中 Customer 7 个列 , 而在对应数据库中的表要多得多 . 基于逻辑数据集的 JOIN 也是合法的 .
如果你注意两个截图的列名 , 你可能会猜到 : 我们不只使用不同的数据库引擎 ( 通过 ADOExpress 使用 MSSQL 通过 IBExpress 使用 Interbase) 同时我们也使用两个完全不同的数据库结构 (Northwind Employees.gdb)!!
看到 Schema 的优点我们现在马上返回到代码中 .
让我们再次注意 SelectCustomers 方法 :
procedure SelectCustomers(const aCity, anAddress : string);
var myds : IDADataset;
begin
 try
    myds := DASchema.NewDataset( 'Customers' , MyAbstractConnection, FALSE);
 
    myds.Where.AndIfNotEmpty(aCity, 'City' );
    // If a City is an empty string, the condition is not added
    myds.Where.AndIfNotEmpty(anAddress, 'Address' ); // Same...
 
    myds.Open;
    [..]
 finally
    myds.Close;
 end;
end ;
第一行代码自动抽取出匹配 MyAbstractConnection 的正确 SQL 表达式 . 因此 , 如果 MyAbstactConnection 初始化为 "IBEmployees" 连接 , myds.SQL 将如下 :
SELECT CUST_NO, CUSTOMER, PHONE_NO [..] FROM CUSTOMER
如果连接到 "MSSQL" 则如下 :
SELECT CustomerID, CompanyName, Phone [..] FROM Customers
下面两行代码在 Select 语句中加入 WHERE 子句 ( 如果 aCity anAddress 不是空字符串 ).
myds.Where.AndIfNotEmpty(aCity, 'City' );
// If a City is an empty string, the condition is not added
myds.Where.AndIfNotEmpty(anAddress, 'Address' ); // Same...
'City' 'Address' 将通过 ColumnMappings 映射为适当的列名组合在 SQL 命令中 .
如上面所提到的 , 不管你使用什么数据库或 DAF 都使用同样的代码工作 !
连接池
到现在为止 , 上面的范例已经通过 ConnectionManager 组件获取连接 . ConnectionManager 只能创建活动连接不能提供池支持 . 通常情况下 ( 没有用 -ADO) 将需要你自己去实现连接缓冲并在请求线程之间共享 ( 例如 ISAPI RemObjects 服务模块 ).
Data Abstract 引入叫做 TDAConnectionPool 特殊组件实现这个功能 .
DA01 - Data Abstract 总览_第7张图片
为了受益于连接缓冲池你需要设置它的属性并替换代码
connection := DAConnectionManager.NewConnection( 'EmployeeIBO' , TRUE);
connection := DAConnectionPool.NewConnection( 'EmployeeIBO' , TRUE);
主要是就是取代 ConnectionManager 而从 ConnectionPool 组件中获取连接 .
 

你可能感兴趣的:(Data,Abstract文档翻译)