DA03 – Schemas导论

来源:互联网 发布:js实现滚动字幕 编辑:程序博客网 时间:2024/05/18 16:18
 
DA03 – Schemas导论
本文向你介绍Data AbstractSchemas和逻辑服务导向架构.
Schema是一个定义多层应用基础数据表的文件. 定义它们的关系和结构,以及如何与存储数据的终端数据库关联.
SchemaData Abstract的重要特性,是多层架构中的重要部分.
Data Abstract 包含两个版本,只适用于一个版本的地方将用.NETDelphi标识.
导论
For Delphi, Datamodule将用户接口和数据处理分离.涉及到可视控件的事件通常放在窗体单元,TDataset相关的方法和事件存放于datamodule. 然而DataModule中的Dataset数量过多就会带来维护问题.
For .NET, 存在与数据接口相关的事件处理没有与GUI分离.
分离数据接口后,还需要帮助处理不同的SQL表达式或方言. 同时要求字段属性与数据集保持同步.
Data Abstract Schemas 综述
Data AbstractSchemas通过封装SQL命令,字段,参数属性解决这个问题.通过这些抽象数据接口层.
Schemas通过如下类维护:
  • .NET: RemObjects.DataAbstract.Schema.Schema
  • Delphi: TDASchema
这个类的接口非常简单,声明如下:
(.NET)
public class Schema : XmlSerializableObject
{
 public Schema();
 
 public static SchemaDataTable DeriveDataReaderSchema(IDataReader aDataReader,
                                                                  string aName);
 public static SchemaDataTable DeriveDataTableSchema(DataTable aDataTable);
 
 public SchemaCommandsCollection Commands { get; }
 public SchemaDataTableCollection DataTables { get; }
 public SchemaRelationshipCollection Relationships { get; }
 public SchemaUpdateRuleCollection UpdateRules { get; }
}
public class ServiceSchema : RemObjects.DataAbstract.Schema.Schema
{
 // Methods
 public ServiceSchema();
 public void AssignParameterValues(IAbstractConnection aConnection,
                           IDataParameterCollection aParameterCollection,
                           string[] aParameterNames, object[] aParameterValues);
 protected IDbCommand CreateADOCommand(IAbstractConnection aConnection,
                           string aSchemaElementName, bool aIsTable,
                           string[] aParameterNames, object[] aParameterValues,
                           out string[] aOutputParameterNames);
 public static int CreateCommandParameters(IAbstractConnection aConnection,
                           SchemaParameterCollection aParametersDefinition,
                           IDbCommand aCommand);
 public int ExecuteCommand(IAbstractConnection aConnection,
                           string aSchemaCommandName);
 public int ExecuteCommand(IAbstractConnection aConnection,
                           string aSchemaCommandName, string[] aParameterNames,
                           object[] aParameterValues);
 public IDbCommand NewCommand(IAbstractConnection aConnection,
                           string aSchemaCommandName);
 public IDbCommand NewCommand(IAbstractConnection aConnection,
                           string aSchemaCommandName, string[] aParameterNames,
                           object[] aParameterValues);
 public IDbCommand NewCommand(IAbstractConnection aConnection,
                           string aSchemaCommandName, string[] aParameterNames,
                           object[] aParameterValues,
                           out string[] aOutputParameterNames);
 public IDataReader NewDataReader(IAbstractConnection aConnection,
                           string aSchemaTableName);
 public IDataReader NewDataReader(IAbstractConnection aConnection,
                           string aSchemaTableName, string[] aParameterNames,
                           object[] aParameterValues);
 public IDbCommand NewDataReaderCommand(IAbstractConnection aConnection,
                           string aSchemaTableName);
 public IDbCommand NewDataReaderCommand(IAbstractConnection aConnection,
                           string aSchemaTableName, string[] aParameterNames,
                           object[] aParameterValues);
 
 // Properties
 public int DefaultCommandTimeout { get; set; }
}
(Delphi)
IDASchema = interface
 function GetDatasetText(const aConnection: IDAConnection;
                          const aName: string): string;
 function GetCommandText(const aConnection: IDAConnection;
                          const aName: string): string;
 
 function NewDataset(const aConnection: IDAConnection;
                      const aName: string): IDADataset;
 function NewDataset(const aConnection: IDAConnection;
                      const aName: string;
                      const ParamNames: array of string;
                      const ParamValues: array of Variant;
                      OpenIt: boolean = true): IDADataset;
 
 function NewCommand(const aConnection: IDAConnection;
                      const aName: string): IDASQLCommand;
 function NewCommand(const aConnection: IDAConnection;
                      const aName: string;
                      const ParamNames: array of string;
                      const ParamValues: array of Variant;
                      ExecuteIt: boolean = true): IDASQLCommand;
 
 procedure Clear; override;
 
 property ConnectionManager: TDAConnectionManager ...;
 property DataDictionary: TDADataDictionary ...;
 property Datasets: TDADatasetCollection ...;
 property Commands: TDASQLCommandCollection ...;
end;
从上面的属性中可以看到,Schema中包含数据集和命令的集合. 基本概念如下:
In Delphi, TDatasets, TQueries及其它类似组件不用在拖放到DataModule,它们包含在Schema.客户端DataTable从服务端获取数据.
For .NET, 在客户端基于服务端的Schema生成含DataTableDataSet.
你在Schema中设计你的需求和SQL命令.当需要执行命令和读取数据时,请求Schema获取支持.
下图展示一个在Schema Modeler中编辑的简单Schema:
Schema Modeler (简称SM)是编辑Schema和设计所有应用程序需要使用的SQL命令的工具. 正如你希望的,Schema也可以在运行时调用,当然更多的时候还是用于设计时.
为什么Schema这样重要,提供了什么优势?
我们使用一个范例更好的理解Schema提供的优势.
假设你要设计一个可以使用MSSQL,InterBase和老版本Oracle的系统.其中有一个数据需要在Orders表并使用CustomerID字段Join Customers表查询记录以便于获取CompanyName.
SQL Server InterBase查询命令如下:
SELECT C.CompanyName, O.*
 FROM Orders O
    LEFT JOIN Customers C ON (C.CustomerID=O.CustomerID)
 WHERE O.OrderDate=:OrderDate
 ORDER BY O.OrderID
Oracle 7SQL语法如下:
SELECT C.CompanyName, O.*
 FROM Orders O, Customers C
 WHERE C.CustomerID = (+) O.CustomerID
    AND O.OrderDate=:OrderDate
 ORDER BY O.OrderID
注意:这些差异可能会在3个数据库间兼容,但是如果你执行一个非常复杂的查询就可能会使用目标数据库的高级特性并且只能在特定RDBMS中执行.
由于在Delphi.NET中正常的查询只能支持一个Select表达式,你可能要为Oracle使用其它组件,或通过代码在运行时动态组合SQL.
数据库连接也有同样的问题,需要处理这些不同的组件集(ADO VS IBX).我们将在本文的后面封装连接.
通过schemas,我们可以有组织的设计分离数据库连接和SQL表达式支持跨数据库系统.
Data Abstract 中我们定义了3个命名连接 ("MSSQLConn", "IBXConn" "OracleConn") 和一个dataset ("OrdersByDate"). 同时将MSSQLConn标识为默认连接.
"OrdersByDate"中加入两个表达式:一个连接到MSSQLConn并包含上面定义的第一个Select, 另一个连接到OracleConn包含第二个select 表达式.
运行时我们可以使用如下代码打开查询:
(.NET)
lConnection = ConnectionManager. AcquireConnection(“MSSQLConn”, true);
lReader = ServiceSchema.NewDataReader(lConnection, “OrdersByDate”
                       new string[] {“OrderDate”, new object[] {DateTime.Now});
(Delphi)
Connection := Schema.ConnectionManager.NewConnection('MSSQLConn');
Dataset := Schema.NewDataset(Connection, 'OrdersByDate');
Dataset.ParamByName('OrderDate').AsDatetTime := Today;
Dataset.Open();
注意我们如何将连接名称"MSSQLConn" 作为参数传递给AcquireConnection/NewConnection方法(而不是使用默认的连接字符串).这保证你的代码不需要绑带到特定数据库参数,并使在需要连接到不同数据库时代价最小.
事实上,为了使我们的应用程序运行于Oracle,我们只需修改文件的第一行代码:
(.NET)
lConnection = ConnectionManager. AcquireConnection(“OracleConn”, true);
(Delphi)
Connection := Schema.ConnectionManager.NewConnection('OracleConn');
InterBase,这个参数使用"IBConn".
你可能很奇怪由于我们没有为Interbase定义SQL表达式这时是怎么执行的呢.
这就是默认连接的用处:我们将 "MSSQLConn" 设置为默认连接,我们指示Schema对没有明确指定SQL表达式的情况使用默认连接的SQL表达式. 除非我们为IBConn连接指定表达式,否则Schema将一直使用MSSQL连接的表达式.
有时SQL方言差距很大有时有相同,这是都不用去做重复的工作.
Dataset字段和映射
正如你期望的,Schema数据集有一个字段列表. Orders 数据集包含14个字段,如下图:
如果你同时要面向多个数据库设计,你可能会保证使用相同的字段名和表名. 不幸的是,当你使用数据库时很容易忽略一些如字段名和表名的限制, ,名字中间含有空格(Order Details)或在有些数据库中不允许使用关键字作字段名.
DataAbstract允许你独立于你的数据库表结构定义DataSet的字段. 上图的字段列表不必代表底层的表或查询结构. Data Abstractdatasets是从SQL查询中抽取的结果集.你可以将上面的OrdersByDate作为一个业务实体而不是物理实体.
列映射(Column Mappings)将每个字段连接到真正的数据库列. 每个SQL表达式允许有一个独立的列映射.下面是OrdersByDate dataset MSSQL 映射定义:
列映射可以使你在设计客户端和中间层的时候忽略底层数据库结构.
一个有趣的结果是Data Abstract 也允许在结构不同的数据库间切换,而不用去变动客户端. Data Abstract自带了一个范例通过InterBaseEmployeeMSSQL Northwind数据库示范了这个概念.
假设有如下数据集:
你将可以看到字段是如何通过不同的列映射映射到Northwind.CustomersEmployee.CUSTOMERS:
连接管理
在这之前,另外一个支持多数据库的问题是处理不同的连接类型. Data Abstract引入了一种新的方式管理数据库连接: Connection Manager组件.
Connection Manager是一个存储命名连接并用于运行时的组件.
Connection Managers也提供了存储连接池优化执行效率和减少服务资源占用.
Connection Manager定义如下:
(.NET)
public class ConnectionManager : XmlSerializableComponent
{
 public ConnectionManager();
 public ConnectionManager(bool aRegister);
 public ConnectionManager(IContainer aContainer);
 public IAbstractConnection AcquireConnection([DefaultParameterValue("")]
                     string aName, [DefaultParameterValue(true)] bool aConnect);
 public ConnectionDefinition AddDefinition(string aName,
     string aConnectionString, [DefaultParameterValue(false)] bool MakeDefault);
 public void Load();
 public void LoadFromFile();
 public void LoadFromFile(string aFileName);
 public void LoadFromResource();
 public void LoadFromResource(string aResourceName);
 public void LoadFromXml(XmlDocument aXmlDocument);
 public void ReleaseConnection(ref IAbstractConnection aConnection);
 protected void SetMaxPoolSize(int Value);
 protected void SetPoolBehavior(PoolBehavior Value);
 private void SetWaitIntervalSeconds(int value);
 public event ConnectionNotificationDelegate ConnectionAcquired;
 public event ConnectionNotificationDelegate ConnectionCreated;
 public event ConnectionNotificationDelegate ConnectionReleased;
 public event ConnectionNotificationDelegate CustomPoolTransactionBehavior;
 public ConnectionDefinitionCollection ConnectionDefinitions { get; }
 public ConnectionDefinition DefaultConnectionDefinition { get; }
 public string DefaultConnectionName { get; }
 public int MaxPoolSize { get; set; }
 public PoolBehavior PoolingBehavior { get; set; }
 public bool PoolingEnabled { get; set; }
 public int PoolTimeoutSeconds { get; set; }
 public PoolTransactionBehaviour PoolTransactionBehaviour { get; set; }
 public int WaitIntervalSeconds { get; set; }
}
(Delphi)
TDAConnectionManager = class(...)
 [..]
 function GetDefaultConnectionName: string;
 function NewConnection(const aConnectionName: string;
                         OpenConnection: boolean = TRUE;
                         const UserID: string = '';
                         const Password: string= ''): IDAConnection;
 procedure Clear; override;
 property PoolSize: cardinal;
 property MaxPoolSize: cardinal;
 property PoolTimeoutSeconds: cardinal;
 property PoolBehaviour: TDAPoolBehaviour;
 property WaitIntervalSeconds: cardinal;
 property Connections: TDAConnectionCollection;
 property DriverManager: TDADriverManager;
 property PoolingEnabled: boolean read;
end;
Data Abstract Schemas引用一个Connection Manager并允许你为每个连接的SQL命令提供不同的表达式.