Хранение имен полей SQL и общего использования SQL с Delphi - PullRequest
3 голосов
/ 05 марта 2009

Я начинаю писать свое первое приложение Delphi, которое подключается к базе данных SQL (MySQL) с использованием компонентов базы данных ADO. Я задавался вопросом, есть ли лучший способ хранения имен полей в базе данных для удобства использования при создании запросов SQL позже.

Прежде всего я сделал их простой константой, например, c_UserTable_Username, c_UserTable_Password, но потом решил, что это не очень хороший способ ведения дел, поэтому я сейчас храню их в постоянной записи, например ::1004

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 полей), я не могу. Я должен вручную ввести ссылку на запись для каждого поля.

Полагаю, мне хотелось бы знать, есть ли способ итерации по всем именам полей одновременно или по отдельности; или я иду по этому поводу неправильно? Может быть, мне вообще не следует их так хранить?

Кроме того, я создал класс «База данных», который в основном содержит методы для множества различных операторов SQL, например GetAllUsers, GetAllProducts и т. Д. Звучит правильно? Я ознакомился со многими учебными пособиями по Delphi / SQL, но они, похоже, далеко не ушли, показывая вам, как выполнять запросы.

Полагаю, я немного растерялся, и любая помощь приветствуется. Спасибо:)

Ответы [ 5 ]

3 голосов
/ 06 марта 2009

Вы также можете хранить свои запросы как RESOURCESTRING, что позволит редактировать их после факта с помощью редактора ресурсов (при необходимости).

RESOURCESTRING
  rsSelectFromUsers = 'SELECT USERNAME FROM USERS ';

Ваш подход к классу базы данных работает очень хорошо. Я сделал это в нескольких своих проектах, возвращая интерфейс объекту, который содержит набор данных ... Преимущество этого в том, что когда возвращаемая переменная интерфейса выходит из области видимости, набор данных закрывается и очищается.

2 голосов
/ 05 марта 2009

Ну, вы жестко кодируете имена полей; вы просто жестко закодируете их в const, а не в самом запросе. Я не уверен, что на самом деле что-то улучшает. Что касается итерации по полям, попробуйте следующее:

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

Вместо создания класса «База данных» я бы, вероятно, использовал модуль TDataModule. Это делает почти то же самое, что и ваш класс, за исключением того, что позволяет интерактивно разрабатывать запросы во время разработки. Вы можете поместить любые методы в DataModule.

Это также позволяет легко создавать экземпляры постоянных TFields (см. Справку по этой теме), что может оказаться более подходящим решением, чем использование консолей для хранения имен полей.

1 голос
/ 05 июня 2009

Может быть, немного не по теме, но вы можете использовать Аннотация данных из RemObjects.

1 голос
/ 06 марта 2009

Если вы действительно собираетесь использовать класс базы данных, как показано, рассмотрите возможность записей содержать функции в 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 для таблиц. Это создает большую взаимосвязь между пользовательским интерфейсом и базой данных (которая не должна существовать) и не позволяет вам обеспечить высокий уровень безопасности при прямой модификации таблицы.

Я бы обернул все в хранимые процедуры и имел бы класс интерфейса БД, который инкапсулирует весь код базы данных в модуль данных. Вы по-прежнему можете использовать прямые ссылки на компоненты, учитывающие данные, из модуля данных, вам просто нужно предварять ссылки именем 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;

Теперь у вас есть возможность выполнить некоторую форму модульного тестирования на вашем интерфейсе данных, вы удалили 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, и пользовательский интерфейс не будет знать, что добавление или удаление пользователей происходит в отдельном процессе. Имейте в виду, что это очень простые операции, поэтому, если вы хотите сделать такие удобные вещи, как уведомление родителя о завершении процесса (например, для обновления списка пользователей после добавления / удаления / обновления), вам нужно кодировать его в потоке модель.

Кстати, сохраненный процесс добавления кода выглядит следующим образом:

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

Кроме того, небольшой отказ от ответственности: этот пост довольно длинный, и, хотя я пытался проверить большую его часть, я, возможно, что-то пропустил где-то.

0 голосов
/ 05 июня 2009

Взять добычу на Анализ данных (в Delphi и Kylix)

Код является хорошим примером манипулирования метаданными таблицы. Вы можете получить имена полей, а затем написать генератор кода, который может создать базовый блок / или его интерфейсную часть.

...