Table.Edit forcing Cursor to the EOF - delphi

Can someone please tell me why this only works one time after the aTbl.Edit.
If I rem out the four lines for editing the table it iterates through all 49 records. It seems that the Edit and Post will position the file-cursor at the end of the file as I am only ever getting one record changed and it quits with the EOF.
I am using D5, Zeos-6 and SQLite3. I even tried grabbing the Auto-inc before the edit and then a Locate after it, but it still quits after the edit.
Thanks for anything you can suggest, but this has been driving me nuts all afternoon. I keep thinking it is something stupid I have done, but I cannot find it. :)
aTbl.First; // Test DB has 49 records
while not aTbl.EOF do
begin
for i := 0 to lbCt.Items.Count-1 do // Currently only two items in the list
begin // and only the second makes a match
aMatch := False; // which then forces the edit
CtStr := lbCt.Items[i]);
case InOut.ItemIndex of
0: aMatch := aTbl.FieldByName(fld_A).AsString = CtStr;
1: aMatch := aTbl.FieldByName(fld_B).AsString = CtStr;
2: aMatch := (aTbl.FieldByName(fld_A).AsString = CtStr) or
(aTbl.FieldByName(fld_B).AsString = CtStr);
3: aMatch := (aTbl.FieldByName(fld_A).AsString = CtStr) and
(aTbl.FieldByName(fld_B).AsString = CtStr);
end;
if aMatch then
begin
aTbl.Edit;
aTbl.FieldByName('Account').AsString := lbCt.Items[i];
aTbl.FieldByName('Folder').AsString := lbCt.Items[i];
aTbl.Post;
end;
end;
aTbl.Next;
end;

The problem is most likely that you have an index active that uses either Account or Folder as one of the index fields. Those are the fields you're changing the value of, and if either of them is in the current index expression it will move the record pointer to the new location for the row (which might in fact be EOF).

Related

Datasnap\FireDAC: Query executed twice

I have the following problem:
1) I use Delphi XE7 to develop a 3-layer system.
2) The server layer, created with datasnap using REST.
3) I use Firebird as database and the access is performed with FireDAC.
4) I have a sequence with value 01.
5) I created the following query in the server layer:
Select GEN_ID (gen_my_sequence, 1) from rdb $ database
6) On the server returns the sequence value in the query is: 02.
7) But the client layer returns 03.
I do not understand why the query is executed twice.
Can anyone help me?
This is the nature of generators (sequences) in firebird. Their value is increased every time you request it and the value of the generator is updated from that request and remains updated. Also generators live outside of transaction control.
See this firebirdsql generatorguide-basics. It doesn't matter where you request it from.
I use technical standards that the Embarcadero indicates.
What I realized was this:
1) The unit Data.FireDACJSONReflect in TFDJSONInterceptor.ItemListToJSONObject routine has this block of code:
if not LActive then
LDataSet.Active := True;
try
LJSONDataSet := DataSetToJSONValue(LDataSet);
// Use AddPair overload that will accept blank key
AJSONObject.AddPair(TJSONPair.Create(LPair.Key, LJSONDataSet))
finally
if not LActive then
LDataSet.Active := False;
end;
See he activates the query once, causing the sequence to be incremented.
But in DataSetToJSONValue (LDataSet) routine; This code block is:
if (LMemTable = nil) then
begin
LMemTable := TFDMemTable.Create(nil);
LAdapter := TFDTableAdapter.Create(nil);
LMemTable.Adapter := LAdapter;
LAdapter.SelectCommand := ADataSet.Command;
LMemTable.Active := True;
end;
See he again activates the query, where the sequence is again incremented.
Now I do not know if I made a mistake or if it is a bug, but I created a new class inherited from TFDMemTable and thought there was some mistake in this class, but did a test with TFDMemTable component, standard component of FireDAC, and even then the activation of any query is performed twice, because the code does not consider any of these two classes, as a TFDCustomMemTable, even though they were inherited directly from this class.
I commented the code of DataSetToString routine (const ADataSet: TFDAdaptedDataSet) that looked like this:
LMemTable := nil;
LAdapter := nil;
try
//if (ADataSet is TFDCustomMemTable) then
LMemTable := TFDCustomMemTable(ADataSet);
{if (LMemTable = nil) then
begin
LMemTable := TFDMemTable.Create(nil);
LAdapter := TFDTableAdapter.Create(nil);
LMemTable.Adapter := LAdapter;
LAdapter.SelectCommand := ADataSet.Command;
LMemTable.Active := True;
end;}
In this way the problem was solved, and the performance of the application seemed to have improved.

Iterating a panels controls results in an index out of bounds error. Why?

I am using Delphi 7 (Yes, I hear the guffaws). I have a tabbed notebook that I want certain controls to appear only in a sequence where the prior control is finished correctly. For each page in the notebook, I have a named sheet. And for the controls on that sheet, I use the tag property to determine whether they are visible at each step. Some steps result in one new control showing, some steps have as many as five controls popping into view. I thought to simply iterate through the controls on any tab sheet that's in view and turn off anything with a tag greater than the current step value. On the page in question, there appear to be 23 controls in all, some labels that are always in view, some edit fields that pop up into view and some arrow-shaped buttons for advancing when a newly popped up field gets changed. Seemed simple enough, except I kept generating Index out of range errors. The sequence would shut down with out a detailed error message for EurekaLog, not anything opened up that should have been. I finally 'resolved' the issue by plugging in a check for the NAME of the control I knew was last in the list and quitting the loop at that point. I also added the extra test for Kounter.tag <> zero to avoid leaving the Submit and Cancel buttons on in some routes. Ideas why the Kounter just kept on past 23?
procedure TFrmMain.VizToggleWTP;
var
kounter: Integer;
kontrol: TControl;
Kontrolz: Integer;
begin
Kontrolz := sheetPrintouts.ControlCount;
for Kounter := 1 to Kontrolz
do begin
// To avoid index error, check for the Cancel Button and exit at that point
if sheetPrintouts.Controls[kounter].Name = 'BtnCancelwtp'
then Break;
if (sheetPrintouts.Controls[Kounter]) is TNXEdit
then begin
kontrol := TNXEdit(sheetPrintouts.Controls[Kounter]);
kontrol.visible := (kontrol.Tag <= wtpStep);
end;
if (sheetPrintouts.Controls[Kounter]) is TJvShapedButton
then begin
kontrol := TJvShapedButton(sheetPrintouts.Controls[Kounter]);
kontrol.visible := ((kontrol.Tag <= wtpStep) and (kontrol.Tag <> 0));
end;
end;
end;
You need to replace
for Kounter := 1 to Kontrolz do
with
for Kounter := 0 to Kontrolz-1 do
since the Controls array is zero-based.
For instance, if there are three controls, they are indexed 0, 1, 2 and not 1, 2, 3.

Change Password Delphi v7

Delphi v7
This code is designed to allow the user to change his password. It appears to execute correctly, but the new password is not saved in the the password data field. I must have done something wrong, but I cannot see it.
procedure TForm4.btnPswordClick(Sender: TObject);
var
I: integer;
begin
tblLogin.First;;
for I := 0 to tblLogin.RecordCount do
Begin
If tblLogin.FieldByName('Username').Value = Edit1.Text then
if tblLogin.FieldByName('Password').Value = Edit2.Text then
sign2.Visible := True; //Test in this case tells the application to make Label1
visible if the //username and password are correct
tblLogin.Next;
end;
I:= I+1; //ends search loop of records so program can move on
If sign2.Visible = False then
begin
MessageDlg('Error Username, or Password not correct',
mtConfirmation, [mbCancel], 0);
end
else
if edit3.Text <> edit4.Text then
begin
MessageDlg('Error New Password does not match',
mtConfirmation, [mbCancel], 0);
end
else
begin
tblLogin.Edit;
tblLogin.FieldByName('Password').Value := Edit3.Text;
tblLogin.Post;
//form1.Show;
//form4.Close;
end;
My comment about formatting your code was just me being snide, but in fact I think it really would have helped you find the error yourself. Properly indented, your first loop is this:
tblLogin.First;;
for I := 0 to tblLogin.RecordCount do
Begin
If tblLogin.FieldByName('Username').Value = Edit1.Text then
if tblLogin.FieldByName('Password').Value = Edit2.Text then
sign2.Visible := True; //Test in this case tells the application to make Label1 visible if the
//username and password are correct
tblLogin.Next;
end;
The next line of code is this:
I:= I+1; //ends search loop of records so program can move on
The comment suggests that you expect that line to cause the loop to terminate. But that line isn't in the loop. If it were in the loop, your code never would have compiled because you're not allowed to modify the loop-control variable inside a loop. Even outside the loop, the compiler should have warned you that the current value of I is undefined. That's because the compiler makes no guarantees about the final value of a loop-control variable once the loop has terminated. Adding 1 to an undefined value is still an undefined value.
Furthermore, your loop iterates over more records than your database table contains. If the upper bound of a for loop hasn't had 1 subtracted from it and isn't the result of High or Pred, then you're probably doing something wrong.
Since your I+1 line doesn't actually terminate the loop, you end up traversing all the way to the end of the table, so when you call tblLogin.Edit, you're putting the final record into edit mode, not the record with the matching account details.
Fixing the indentation will also show that you didn't even include all the code for that function. There's a begin without a matching end somewhere.
You could avoid all this code by using a single UPDATE statement on your database:
UPDATE tblLogin SET Password = ? WHERE Username = ? AND Password = ?

Are unique indices on Access text fields always case insensitive?

I created an MS Access table using the following code:
tbl := Database.CreateTableDef('English', 0, '', '');
try
fld := tbl.CreateField('ID', dbLong, 0);
fld.Attributes := dbAutoIncrField + dbFixedField;
tbl.Fields.Append(fld);
fld := tbl.CreateField('Content', dbText, 255);
fld.Required := true;
fld.AllowZeroLength := false;
tbl.Fields.Append(fld);
Database.TableDefs.Append(tbl);
idx := tbl.CreateIndex('PrimaryKey');
idx.Fields.Append(idx.CreateField('ID', EmptyParam, EmptyParam));
idx.Primary := True;
idx.Unique := true;
tbl.Indexes.Append(idx);
idx := tbl.CreateIndex('IX_Content');
idx.Fields.Append(idx.CreateField('Content', EmptyParam, EmptyParam));
idx.Primary := false;
idx.Unique := true;
tbl.Indexes.Append(idx);
finally
tbl := nil;
end;
This works fine until I try to insert the two strings 'Field type' and 'Field Type' into this table. I get an error telling me that the unique index restricts me from doing that. As you can see they only differ in the case of the second word. Since I did not explicitly make the index case insensitive (I wouldn't even know how to do that), I don't quite understand why this happens. Are indices on text fields always case insensitive in MS Access? If not, what am I doing wrong?
Access Jet databases are fundamentally case insensitive. That is your problem. As far as I know there is no way to make an Access index case sensitive.
Use Binary field
Case insensivity problem in Microsoft Access was long time ago addressed in article KB244693 published by Microsoft and still can be found in Web archive.
Basically, the solution is to add a Binary field into your MS Access table and that one is finally case sensitive (uses Unicode for storing binary content) and still can be used as a text field with operators =, LIKE etc.
The field of type Binary cannot be added via the UI, but you add it to your existing table using SQL statement like this:
ALTER TABLE Table1
ADD COLUMN BinaryField1 BINARY(50)
Then you can manage it normally via the Access UI.

Delphi 5 & Crystal XI Rel. 2 (RDC) how to?

I'm trying to work with the class from JosephStyons but I do get an "Invalid Index" Error on the line where the "User ID" should get set.
FRpt.Database.Tables[i].ConnectionProperties.Item['User ID'] := edUserName.Text;
Here's my environment:
WinXP Sp3, Crystal Reports Developer XI Rel.2 SP4, Delphi 5 Update Pack 1
Any help or ideas greatly appreciated!
Thx,
Reinhard
Your value for [i] could be the culprit...I can't remember for sure but I believe the first table will be Table[1] instead of Table[0] as one would expect.
I altered my loop to use:
CrTables := CrDatabase.Tables;
for crTableObj in crTables do
You might try stepping through the table using a for loop as shown above or by starting with 1 instead of 0.
I hope this helps.
Put a break point on that line and use Evaluate/Modify.
It will return an error if you try something invalid.
Examine FRpt.Database.Tables[i] and see if it's valid for what you think are the min and max values for i.
If Tables is an array, one way to avoid that is to use ...Low(Tables) to High(Tables)
If you get your Table Ok, examine FRpt.Database.Tables[i].ConnectionProperties.Item['User ID'] and see if it's valid.
It might be that the Item getter does not like the space embedded in "User ID". Some products need either to surround by special characters like "[User ID]", other to replace by an underscore like "User_ID"
Are you also setting the password, server name and database name?
procedure TReports.LogonToDBTables(cReport:
CrystalDecisions.CrystalReports.Engine.ReportDocument;
ConnInfo: ConnectionInfo);
var
CrDataBase: Database;
CrTables: Tables;
CrTableObj: TObject;
CrTable: Table;
CrTableLogonInfo: TableLogonInfo;
iSubReportIndex: smallint;
begin
CrDataBase := CReport.Database;
CrTables := CrDatabase.Tables;
cReport.DataSourceConnections[0].IntegratedSecurity := False;
for crTableObj in crTables do
begin
crTable := CrystalDecisions.CrystalReports.Engine.Table(crTableObj);
crTableLogonInfo := crTable.LogOnInfo;
crTableLogonInfo.ConnectionInfo := ConnInfo;
crTable.ApplyLogOnInfo(crTableLogonInfo);
end;
end;
function TReports.GetConnectionInfo(): ConnectionInfo;
var
cTemp: ConnectionInfo;
begin
cTemp := ConnectionInfo.Create();
cTemp.AllowCustomConnection := True;
cTemp.ServerName := GetServerName();
cTemp.DatabaseName := GetDBName();
cTemp.UserID := GetDBUserID();
cTemp.Password := GetDBPassword();
Result := cTemp;
end;

Resources