3

I'm starting to write my first Delphi application that connects to an SQL database (MySQL) using the ADO database components. I wondered whether there was any best way of storing the names of the fields in the database for easy reference when creating SQL queries later on.

First of all I made them a simple constant e.g. c_UserTable_Username, c_UserTable_Password, but then decided that was not a particularly good way of doing things so I am now storing them in a constant record e.g.:

type
   TUserTable = record
     TableName : String;
     Username : String;
     Password : String;
 end;

const
UserTable : TUserTable =
    (
      TableName : 'users';
      Username : 'Username';
      Password : 'Password';
    );

this allows me to create a statement like:

query.SQL.Add('SELECT ' + UserTable.Username + ' FROM ' + UserTable.TableName);

and not have to worry about hard coding the field names etc.

I've now run into the problem however where if I want to cycle through the table fields (for example if there are 20 or so fields), I can't. I have to manually type the record reference for every field.

I guess what I'd like to know is whether there is a way to iterate though all field names at once, or singularly; or am I going about this the wrong way? Perhaps I shouldn't be storing them like this at all?

Also, I've made a “Database” class which basically holds methods for many different SQL statements, for example GetAllUsers, GetAllProducts, etc. Does that sound correct? I've taken a look at a lot of Delphi/SQL tutorials, but they don't seem to go much past showing you how to run queries.

I suppose I'm just a little lost and any help is very welcome. Thanks :)

4

5 回答 5

3

您还可以将查询存储为 RESOURCESTRING,这将允许在事后使用资源编辑器(如有必要)对其进行编辑。

RESOURCESTRING
  rsSelectFromUsers = 'SELECT USERNAME FROM USERS ';

您的数据库类方法效果很好。我已经在我的几个项目中做到了这一点,将接口返回到包含数据集的对象......这样做的好处是当返回的接口变量超出范围时,数据集将被关闭并清除。

于 2009-03-05T21:55:11.050 回答
2

好吧,您是对字段名称进行硬编码;您只需将它们硬编码在 const 中,而不是在查询本身中。我不确定这实际上会改善什么。就遍历字段而言,试试这个:

var
  Field: TField;
begin
  for Field in query.Fields do begin
     // do stuff with Field
  end;
end;

我可能会使用 TDataModule,而不是创建“数据库”类。这与您的类几乎相同,只是它允许您在设计时交互式地设计查询。您可以将所需的任何方法放在 DataModule 上。

这也使得实例化持久 TField 变得非常容易(请参阅有关该主题的帮助),您可能会发现比使用 const 存储字段名称更符合您的喜好的解决方案。

于 2009-03-05T14:51:44.423 回答
1

如果您真的要使用如图所示的数据库类,请考虑记录在 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

另外,一点免责声明:这篇文章很长,虽然我试图检查大部分内容,但我可能在某个地方遗漏了一些东西。

于 2009-03-06T02:01:59.460 回答
1

可能有点离题,但您可以使用RemObjects 中的Data Abstract

于 2009-06-05T19:14:55.040 回答
0

在Analyzing DataSets(在 Delphi 和 Kylix 中)获取战利品

该代码是操作表元数据的一个很好的例子。您可以获取字段名称,然后编写可以创建基本单元/或其接口部分的代码生成器。

于 2009-06-05T13:12:16.437 回答