【问题标题】:Delphi 7 SQL Parameter found in Select Statement, but not Insert StatementDelphi 7 SQL 参数在 Select 语句中找到,但不是 Insert 语句
【发布时间】:2015-08-26 17:27:30
【问题描述】:

我目前正在编写一个使用 SQL 和 Access 2003 数据库的 Delphi 7 程序。

该单元通过公共变量(即 frmLogin.sCode)从前一个单元接收 5 位代码。在表单激活时,程序将执行 SQL 查询以显示 tblStudents 中与 sCode 匹配的记录。该语句使用 ParamByName 行并且运行良好。

如果未找到匹配项,则会显示一条消息,并且用户别无选择,只能单击添加用户按钮。然后提示用户将他的所有详细信息输入程序,然后将其传递给设置 SQL 插入语句的类。但是,现在出现了一个问题,因为显示了一条消息,指出找不到参数用户名。我不明白为什么,因为它是在运行 Select 语句时发现的。请问有人可以帮忙吗?

procedure TfrmProfilePage.FormActivate(Sender: TObject);
begin
  //Instantiates the object.
  objProfilePage := TProfilePage.Create;
  sSQL := objProfilePage.SelectSQL;
  ExecuteSQL(sSQl);
end;

procedure TfrmProfilePage.ExecuteSQL(sSQL : String);
begin
  With dmTextbookSales do
    Begin
      dbgrdDisplay.DataSource := dsProfilePage;
      qryProfilePage.SQL.Clear;
      qryProfilePage.SQL.Add(sSQL);
      qryProfilePage.Parameters.ParamByName('Username').Value := frmLogin.sCode;
      qryProfilePage.Open;
      If qryProfilePage.RecordCount = 0
        Then
          Begin
            ShowMessage('Please click on the "Add Details" button to get started.');
            btnChange.Enabled := False;
            btnSelling.Enabled := False;
            btnBuying.Enabled := False;
          End;
    End;
end;

procedure TfrmProfilePage.GetValues(VAR sStudentName, sStudentSurname, sCellNumber, sEmailAddress : String; VAR iCurrentGrade : Integer);
begin
  ShowMessage('Fields may be left blank, but users wishing to sell textbooks should enter at least one contact field.');
  sStudentName := InputBox('Name','Please enter your first name:','');
  sStudentSurname := InputBox('Surame','Please enter your surname:','');
  iCurrentGrade := StrToInt(InputBox('Current Grade','Please enter your current grade:',''));
  sCellNumber := InputBox('Cellphone Number','Please enter your cellphone number:','');
  sEmailAddress := InputBox('Email Address','Please enter your email address:','@dainferncollege.co.za');
end;

procedure TfrmProfilePage.btnAddClick(Sender: TObject);
begin
  GetValues(sStudentName, sStudentSurname, sCellNumber, sEmailAddress, iCurrentGrade);
  sSQL := objProfilePage.InsertSQL;
  ExecuteSQL(sSQL);
  btnChange.Enabled := True;
  btnSelling.Enabled := True;
  btnBuying.Enabled := True;
end;

以下代码是从链接类clsProfilePage中获取的:

function TProfilePage.InsertSQL: String;
begin
  Result := 'INSERT INTO tblStudents (' + '[StudentID]' + ',' + '[StudentName]' + ',' + '[StudentSurname]' + ',' + '[CurrentGrade]' + ',' + '[CellNumber]' + ',' + '[EmailAddress]' + ') VALUES (' + 'Username' + ',' + QuotedStr(fStudentName) + ',' + QuotedStr(fStudentSurname) + ',' + IntToStr(fCurrentGrade) + ',' + QuotedStr(fCellNumber) + ',' + QuotedStr(fEmailAddress) + ')';
end;

function TProfilePage.SelectSQL: String;
begin
  Result := 'SELECT * FROM tblStudents Where StudentID = Username';
end;

【问题讨论】:

  • 您应该在插入语句中使用参数来防止 SQL 注入攻击 - see here
  • 哦,我真的需要一些时间,用this one做一个专门的项目

标签: sql delphi


【解决方案1】:

您的INSERT 声明是错误的。您需要先添加参数,然后才能设置参数值。在 Delphi 中,您可以在 SQL 语句中的参数名称前使用 :

此外,Open 仅在执行 SELECT 时使用。 INSERTUPDATEDELETE 不返回行集,因此您必须使用 ExecSQL(数据集方法,而不是您的函数使用冲突的名称)来代替。

(虽然我们这样做了,但永远不要在 SQL 数据集上使用RecordCount - 它需要检索所有行才能获得计数,并且在执行除 SELECT 无论如何。ExecSQL 执行的操作应该使用 RowsAffected 代替,它告诉您受操作影响的行数。

(如果您需要计算行数,请改为执行SELECT COUNT(*) AS NumRecs FROM YourTable WHERE <some condition>,并使用FieldByName 访问NumRecs 字段。)

将返回INSERT 语句的函数更改为类似这样(Result 中的#13 是一个回车符,它可以避免在每行末尾手动插入空格来分隔 SQL 单词):

function TProfilePage.InsertSQL: String;
begin
  Result := 'INSERT INTO tblStudents ([StudentID],'#13 +
            '[StudentName], [StudentSurname],'#13 +
            '[CurrentGrade], [CellNumber], [EmailAddress])'#13 +
            'VALUES (:Username, :StudentName,'#13 +
            ':StudentSurname, :CurrentGrade,'#13 +
            ':CellNumber, :EMailAddress)';
end;

然后您可以将它与ParamByName 一起使用,而无需通过QuotedStr 和连接跳过所有的环节:

procedure TfrmProfilePage.AddUser;
begin
  with dmTextbookSales do
  begin
    qryProfilePage.SQL.Text := InsertSQL;
    qryProfilePage.Parameters.ParamByName('Username').Value := frmLogin.sCode;
    qryProfilePage.Parameters.ParamByName('StudentName').Value := frmLogin.sUserName;
    // Repeat for other parameters and values - you have to set every
    // single parameter. To skip, set them to a null variant.

    // Execute the INSERT statement          
    qryProfilePage.ExecSQL;    

    // See if row was inserted, and do whatever.
    If qryProfilePage.RowsAffected > 0 then
      ShowMessage('User added successfully');
   // Perform a SELECT to populate the grid contents with the new
   // rows after the update
  end;
end;

我强烈建议您重新考虑这段代码。它非常复杂,完成一项简单的任务(添加新用户)需要太多。

如果是我,我会使用一个单独的查询,专门用于执行INSERT 操作,您可以在设计时设置 SQL,并在对象检查器中为参数设置正确的类型.然后在运行时,您只需设置参数值并对该插入查询调用 ExecSQL,然后刷新您的 SELECT 查询以反映新行。它避免了所有的噪音和混乱(以及一些不必要的函数调用和复杂的 SQL 构建、打开和关闭 SELECT 查询等)。

(它还可以让您删除那个可怕的 with 语句,这会导致难以发现的错误和难以维护的代码。)

您在表单之间也有一些不良链接,您从第二个表单引用frmLogin(特定表单实例)。这意味着您永远不能同时使用任何一种形式的多个实例,因为您已经在该引用中进行了硬编码。我会重新考虑在创建表单时使用传入的参数,或者在创建配置文件页面表单时使用登录表单设置的属性(或任何TProfilePage 是 - 你的帖子没有说)。

最好的解决方案是将所有与 UI 无关的代码移动到一个单独的单元中(例如 TDataModule,它旨在用于处理非可视组件,例如 ADO 查询)并删除无论如何,它来自与用户界面相关的表单,

  • 消除表单之间的耦合,允许代码被重用。
  • 从混乱的表单和表单代码中移除与非可视数据相关的组件。
  • 将业务逻辑(与与用户交互无关的部分)分隔在一个单独的不同位置,使其(以及使用它的代码)更易于维护。

【讨论】:

    【解决方案2】:

    您的插入语句是错误的。您需要替换 [StudentID] 的值。

     'INSERT INTO tblStudents (' 
       + '[StudentID]' + ',' 
       + '[StudentName]' + ',' 
       + '[StudentSurname]' + ',' 
       + '[CurrentGrade]' + ',' 
       + '[CellNumber]' + ',' 
       + '[EmailAddress]' + ') 
     VALUES (' 
       + 'Username' + ',' // <-- UserName will never be replaced by a QuotedStr
                          //     Access is looking for a column with the name
                          //     'UserName' which can not be found  
       + QuotedStr(fStudentName) + ',' 
       + QuotedStr(fStudentSurname) + ',' 
       + IntToStr(fCurrentGrade) + ',' 
       + QuotedStr(fCellNumber) + ',' 
       + QuotedStr(fEmailAddress) + ')';
    

    【讨论】:

    • 错了。不要连接 SQL。不要指导人们连接 SQL。搜索 SQL injection 了解为什么不应该连接 SQL。
    • 我没有。我只是重新格式化了他的插入语句并指出了错误。他问“为什么”它不起作用,我解释了。这是一个答案。当然 SQL 注入是一个很大的话题。但这个问题与一般重构或保护代码无关。这是一个初学者问题:为什么它不起作用。
    • 谢谢你们俩。这是一个学校项目,我在 Delphi 中使用 SQL 的经验为零,所以“对初学者的帮助”是完美的。
    猜你喜欢
    • 1970-01-01
    • 2010-11-09
    • 2012-10-18
    • 2011-12-04
    • 1970-01-01
    • 1970-01-01
    • 2011-03-04
    • 1970-01-01
    • 2016-07-02
    相关资源
    最近更新 更多