使用 Delphi 存储 SQL 字段名称和一般 SQL 用法

发布于 2024-07-14 12:10:55 字数 985 浏览 9 评论 0原文

我开始编写我的第一个 Delphi 应用程序,它使用 ADO 数据库组件连接到 SQL 数据库 (MySQL)。 我想知道是否有任何最好的方法可以将字段名称存储在数据库中,以便稍后创建 SQL 查询时方便参考。

首先,我将它们设置为一个简单的常量,例如c_UserTable_Username、c_UserTable_Password,但后来认为这不是一个特别好的做事方式,所以我现在将它们存储在常量记录中,例如:

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

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

这使我能够创建一个如下语句:

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

并且不必担心对字段名称等进行硬编码。

我现在遇到了问题,但是如果我想循环遍历表字段(例如,如果有 20 个左右的字段),我不能。 我必须手动输入每个字段的记录参考。

我想我想知道是否有一种方法可以一次或单独迭代所有字段名称; 或者我以错误的方式处理这个问题? 也许我根本不应该像这样存储它们?

另外,我创建了一个“Database”类,它基本上包含许多不同 SQL 语句的方法,例如 GetAllUsers、GetAllProducts 等。这听起来正确吗? 我已经看过很多 Delphi/SQL 教程,但它们似乎并没有向您展示如何运行查询。

我想我只是有点迷失,非常欢迎任何帮助。 谢谢 :)

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 :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

巷雨优美回忆 2024-07-21 12:10:55

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

RESOURCESTRING
  rsSelectFromUsers = 'SELECT USERNAME FROM USERS ';

您的数据库类方法非常有效。 我在我的几个项目中就这样做了,返回包含数据集的对象的接口......这样做的优点是当返回的接口变量超出范围时,数据集将被关闭并清除。

You could also store your queries as RESOURCESTRING which would allow editing of them after the fact using a resource editor (if necessary).

RESOURCESTRING
  rsSelectFromUsers = 'SELECT USERNAME FROM USERS ';

Your approach of a database class works very well. I have done just that in several of my projects, returning an interface to an object which contains the dataset...the advantage of this is when the returned interface variable goes out of scope, the dataset would be closed and cleared.

画尸师 2024-07-21 12:10:55

好吧,你正在硬编码字段名称; 您只需将它们硬编码在 const 中,而不是在查询本身中。 我不确定这是否真的能改善任何事情。 至于遍历字段,请尝试以下操作:

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

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

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

Well, you are hard coding field names; you just hardcode them in the const instead of in the query itself. I'm not sure that actually improves anything. As far as iterating through the fields goes, try this:

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

Rather than making a "Database" class, I would probably use a TDataModule. This does almost the same thing as your class, except that it allows you to interactively design queries at design time. You can put any methods you need on the DataModule.

This also makes it really easy to instantiate persistent TFields (see help on that topic), which you may find the solution more to your liking than using consts to store field names.

白昼 2024-07-21 12:10:55

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

另外,有一点免责声明:这篇文章相当长,虽然我试图检查其中的大部分内容,但我可能在某个地方错过了一些东西。

If you're really going to use a database class as illustrated, consider the ability of records to contain functions in D2007 and later.

For instance, your example would become:

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;

which allows:

query.SQL.add(userTable.sqlGetUserName);

or

query.SQL.add(userTable.sqlGetUserName(Format('%s=%s', [userTable.userName,'BOB']));

I don't really recommend using SQL directly as you've illustrated. In my opinion, you should never have direct SQL calls to the tables. That's introducing a lot of coupling between the UI and the database (which shouldn't exist) and prevents you from placing a high level of security on direct table modification.

I would wrap everything into stored procs and have a DB interface class that encapsulates all of the database code into a data module. You can still use the direct links into data-aware components from a data module, you just have to preface the links with the DM name.

For instance, if you built a class like:

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;

You now have the ability to do some form of unit testing on your data interface, you've removed the SQL from the UI and things are looking up. You still have a lot of ugly SQL in your interface code though so move that over to stored procs and you get:

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;

You could now move some of these functions into an ADO Thread and the UI would have no idea that adding or deleting users occur in a separate process. Mind, these are very simple operations so if you want to do handy things like notifying the parent when process are done (to refresh a user list for instance after add/delete/update occurs), you'd need to code that into the threading model.

BTW, the stored proc for the add code looks like:

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

Also, a little disclaimer: this post is pretty long and, while I tried to check most of it, I may have missed something, somewhere.

无声无音无过去 2024-07-21 12:10:55

也许有点偏离主题,但您可以使用 RemObjects 中的数据摘要

Maybe a bit off topic but you could use Data Abstract from RemObjects.

青柠芒果 2024-07-21 12:10:55

获取分析数据集(在 Delphi 和 Kylix 中)

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

Take a loot at Analysing DataSets (in Delphi and Kylix)

The code is a good example of manipulating table metadata. You can get field names and then write a code-generator that can create a base unit/ or the interface part of it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文