如果您真的要使用如图所示的数据库类,请考虑记录在 D2007 及更高版本中包含函数的能力。
例如,您的示例将变为:
type
TUserTable = record
TableName : String;
Username : String;
Password : String;
function sqlGetUserName(where:string=''):string;
end;
const
UserTable : TUserTable =
(
TableName : 'users';
Username : 'Username';
Password : 'Password';
);
function TUserTable.sqlGetUserName(where:string=''): string;
begin
if where='' then result := Format('SELECT %s from %s', [userName, tableName])
else result := Format('SELECT %s from %s where %s', [userName, tableName, where]);
end;
这使得:
query.SQL.add(userTable.sqlGetUserName);
或者
query.SQL.add(userTable.sqlGetUserName(Format('%s=%s', [userTable.userName,'BOB']));
正如您所说明的那样,我真的不建议直接使用 SQL。在我看来,你永远不应该对表进行直接的 SQL 调用。这在 UI 和数据库(不应该存在)之间引入了很多耦合,并阻止您对直接表修改设置高级别的安全性。
我会将所有内容包装到存储过程中,并拥有一个数据库接口类,将所有数据库代码封装到一个数据模块中。您仍然可以使用直接链接到数据模块中的数据感知组件,您只需在链接前加上 DM 名称即可。
例如,如果您构建了一个类似的类:
type
TDBInterface = class
private
function q(s:string):string; //just returns a SQL quoted string
public
procedure addUser(userName:string; password:string);
procedure getUser(userName:string);
procedure delUser(userName:string);
function testUser:boolean;
procedure testAllDataSets;
end;
function TDBInterface.q(s:string):string;
begin
result:=''''+s+'''';
end;
procedure TDBInterface.addUser(userName:string; password:string);
begin
cmd.CommandText:=Format( 'if (select count(userName) from users where userName=%s)=0 '+
'insert into users (userName, password) values (%s,%s) '+
'else '+
'update users set userName=%s, password=%s where userName=%s',
[q(userName), q(userName), q(password), q(userName), q(password), q(userName)]);
cmd.Execute;
end;
procedure TDBInterface.getUser(userName:string);
begin
qry.SQL.Add(Format('select * from users where userName=%s', [q(userName)]));
qry.Active:=true;
end;
procedure TDBInterface.delUser(userName:string);
begin
cmd.CommandText:=Format('delete from users where userName=%s',[userName]);
cmd.Execute;
end;
procedure TDBInterface.testAllDataSets;
begin
assert(testUser);
end;
function TDBInterface.testUser: boolean;
begin
result:=false;
addUser('99TEST99','just a test');
getUser('99TEST99');
if qry.IsEmpty then exit;
if qry.FieldByName('userName').value<>'99TEST99' then
exit;
delUser('99TEST99');
if qry.IsEmpty then
result:=true;
end;
您现在可以在数据接口上进行某种形式的单元测试,您已经从 UI 中删除了 SQL,并且事情正在好转。你的接口代码中仍然有很多丑陋的 SQL,所以把它移到存储过程中,你会得到:
type
TDBInterface = class
public
procedure addUser(userName:string; password:string);
procedure getUser(userName:string);
procedure delUser(userName:string);
function testUser:boolean;
procedure testAllDataSets;
end;
procedure TDBInterface.addUser(userName:string; password:string);
begin
cmd.CommandText:='usp_addUser;1';
cmd.Parameters.Refresh;
cmd.Parameters.ParamByName('@userName').Value:=userName;
cmd.Parameters.ParamByName('@password').Value:=password;
cmd.Execute;
cmd.Execute;
end;
procedure TDBInterface.getUser(userName:string);
begin
sproc.Parameters.ParamByName('@userName').Value:=userName;
sproc.Active:=true;
end;
procedure TDBInterface.delUser(userName:string);
begin
cmd.CommandText:='usp_delUser;1';
cmd.Parameters.Refresh;
cmd.Parameters.ParamByName('@userName').Value:=userName;
cmd.Execute;
end;
您现在可以将其中一些函数移动到 ADO 线程中,并且 UI 不会知道添加或删除用户是在单独的进程中发生的。请注意,这些都是非常简单的操作,因此如果您想做一些方便的事情,例如在处理完成时通知父级(例如在添加/删除/更新发生后刷新用户列表),您需要将其编码到线程中模型。
顺便说一句,添加代码的存储过程如下所示:
create procedure [dbo].[usp_addUser](@userName varchar(20), @password varchar(20)) as
if (select count(userName) from users where userName=@userName)=0
insert into users (userName, password) values (@userName,@password)
else
update users set userName=@userName, password=@password where userName=@userName
另外,一点免责声明:这篇文章很长,虽然我试图检查大部分内容,但我可能在某个地方遗漏了一些东西。