On my form I have a number of TMyQuery Components. Their names identify which MySQL Tables they work with. For example, COMPONENTSTABLE works with the COMPONENTS TABLE, etc.
There are about 30 tables, but that might change in the future.
I also use a basic String List to read field names from a Table called TIMESTAMPS. This table is updated via triggers when an UPDATE, INSERT, or DELETE occurs. Each field within the TIMESTAMPS Table refers to which Table was modified. There's only one record in the table! Based on the field values I can see which table changed so I can refresh it rather than refreshing all of them.
I don't want to do this;
If fieldbyname['COMPONENTSTABLE'] <> CurrentTimeStamp
then ComponentsTable.Refresh;
If fieldbyname['ORDERSTABLE'] <> CurrentTimeStamp
then OrdersTable.Refresh;
{ and so on forever }
What I want to do is;
Right now I have a String List with "Names / Values". Each "Name" is the Fieldname within the Table and "Value" is the TIMESTAMP provided by MySQL Triggers.
I've got the following;
For Idx := 0 to MyStringList.Count -1 do
Begin
If MyStringlist.ValueFromIndex[Idx] <> SomethingElse then
Begin
with (MyStringList.Names[Idx] as tMyQuery).Refresh;
End;
End;
I've got the String List functioning, the Names, the Values etc are all correct.
My question is this;
Is there a way I can use a String ("Names" column in the list) to refer to an Object if that Object exists?
I already have a function I use to refresh individual tables by passing an Object to it, but that's an Object and easy to work with. I'd like to pass the "Object" based on it's name retrieved from a String.
I hope this makes sense and you can follow what I'm after.
I am not sure what your question actually is. In the first part of the answer I assume that you don't really care about names of the objects but rather want some automated way of getting all the tables available refer to a field in another table. Below that, I answer your question about referring to an object if you know its name.
Automated way of handling all tables
It depends on what class your objects are.
From your description, I assume your TMyQuery are TComponent descendants owned by the form. Then the solution is very simple, as each TComponent has both a public Name and a list of owned components Components. You can then use something like this:
var
i: integer;
MyQuery: TMyQuery;
begin
for i := 0 to Pred(MyForm.ComponentCount) do
if MyForm.Components[i] <> TimeStampsTable then
if MyForm.Components[i] is TMyQuery then
begin
MyQuery := TMyQuery(MyForm.Components[i]);
if TimeStampsTable.FieldByName(MyQuery.Name).AsDateTime >= LastAccess then ...
end;
end;
Note that you may want to add extra checks, e.g. to make sure that MyQuery.Name is not empty or that it exists as a field in TimeStampsTable.
If your objects are only TObjects, then there is no "standard" name property and no standard registration of these objects. Name can be handled, apparently your component already has one so it's just a question of a proper type coercion, but object registration is a different matter. You may have to create some kind of a global list for all your created TMyQuery instances.
Getting an object instance based on that object's name
function TMyForm.GetQueryByName(const Name: string): TMyQuery;
var
Obj: TObject;
begin
Result := nil;
Obj := Self.FindComponent(Name);
if Obj <> nil then
if Obj is TMyQuery then
Result := TMyQuery(Obj);
end;
Or you could simply loop over all Components and use your own Name matching.
While the first part of the accepted Answer from #pepak isn't what I was looking for ( I've used similar code in the app previously and found it slow ), the second part of the Answer pointed my in the right direction.
My (thanks to Pepak) eventual solution was;
Function RefreshQueryByName(Const Name: String): Boolean;
Var
Obj: TComponent;
Begin
Result := False;
Obj := Self.FindComponent(Name);
If Obj <> nil Then
If Obj Is TMyQuery Then
With Obj As TMyQuery Do
If Active Then
Begin
Refresh;
Result := True;
End;
End;
Which I use by by passing a String I get from a Field Value that identifies which table I want to refresh.
Now, my Database App automatically refreshes a table changed by other users. It will now refresh any of the 30 tables of they are modified by another user without refreshing all tables.
Thanks for your help Pepak, I've accepted your answer and hope it is useful to others.
I have this simple code to check if a record exists in a table, but it always returns a runtime error :
Arguments are of the wrong type, are out of acceptable range, or are
in conflict with one another.
my code is this :
function TDataModuleMain.BarCodeExists(barCode: string): boolean;
begin
if ADOQuerySql.Active then
ADOQuerySql.Close;
ADOQuerySql.SQL.Clear;
ADOQuerySql.SQL.Text := 'select count(1) from Card where BarCode = (:TestBarcode)';
ADOQuerySql.Parameters.ParamByName('TestBarcode').Value := barCode;
ADOQuerySql.Open; // HERE THE RUNTIME ERROR APPEARS
Result := ADOQuerySql.Fields[0].AsInteger = 1;
ADOQuerySql.Close;
ADOQuerySql.Parameters.Clear;
end;
The field BarCode in table Card is of type nvarchar(100)
In debug I see that the parameter is created, and gets populated with the correct value.
Running the query in sql server management studio also works.
I also found this How to pass string parameters to an TADOQuery? and checked my code with the code in the answer but I don't see any problems here.
Also this AdoQuery Error using parameters did not help me.
It will no doubt be something very simple that I have missed but I just dont see it now.
EDIT : things I tried from suggestions in the comments:
.ParamCheck := True (default)
.Parameters.ParamByName('TestBarcode').DataType := ftString
.Parameters.ParamByName('TestBarcode').DataType := ftWideString
None of these worked however.
What did help was using a non-shared AdoQuery for this, and that one did the job without any errors. I am using that now as the solution but I am still looking at the shared AdoQuery out of curiousity what the exact problem is.
EDIT: the source of the problem is found.
I used the function provided by MartinA to examine both the dynamic created query and the shared AdoQuery and I found one difference.
The shared AdoQuery had the this property filled :
ExecuteOption := [eoExecuteNoRecords]
and the dynamic created query does not.
Since this property is not set in designtime I did not see it.
After clearing the property to [] the shared AdoQuery worked again.
I am going to switch to using non shared AdoQuery for this kind of work as been suggested.
Thanks everyone for your assistance.
The following isn't intended to be a complete answer to your q, but to follow up my comment that "all you have to do is to inspect your form's DFM and compare the properties of your original ADoQuery with the unshared one. The answer should lie in the difference(s)" and your reply that the unshared query is created dynamically.
There is no "voodoo" involved in the difference in behaviour between your two ADOQuerys. It's just a question of capturing what the differences actually are.
So, what you need, to debug the problem yourself, is some code to compare the properties of two components, even if one or both of them is created dynamically. Using the following routine on both components will enable you to do exactly that:
function TForm1.ComponentToString(AComponent : TComponent) : String;
var
SS : TStringStream;
MS : TMemoryStream;
Writer : TWriter;
begin
// Note: There may be a more direct way of doing the following, without
// needing the intermediary TMemoryStream, MS
SS := TStringStream.Create('');
MS := TMemoryStream.Create;
Writer := TWriter.Create(MS, 4096);
try
Writer.Root := Self;
Writer.WriteSignature;
Writer.WriteComponent(AComponent);
Writer.FlushBuffer;
MS.Position := 0;
ObjectBinaryToText(MS, SS);
Result := SS.DataString;
finally
Writer.Free;
MS.Free;
SS.Free;
end;
end;
Over to you ...
I am using Delphi 7 with the FibPlus components . One of them being TpFIBQuery.
I am loading data from a table with the generic
select * from TableName where Key = 1
One of the fields returned is of type BLOB(Text).
I can't seem to get the value into a string list informatie using either of the following 3 ways :
Informatie.Text := FieldByName('Informatie').AsString // Returns the string 'BLOB'
Informatie.Text := BlobAsString('Informatie') // Returns ''
BlobToStrings('Informatie',Informatie) // Returns ''
I have confirmed using Database Workbench that the field in the table indeed contains the saved text.
Anyone ?
usualy, i do like this
var
sl: TStrings; // blob IS NOT string!
ms: TMemoryStream;
begin
sl := TStringList.Create;
ms := TMemoryStream.Create;
try
q.FieldByName('x').SaveToStream(ms);
ms.Position := 0;
sl.LoadFromStream(ms);
// do what ever you want with sl here
// and here too
finally
sl.Free;
ms.Free;
end; // try..finally
end;
note that q is your TpFibQuery object.
also
select * from table is bad bad practice.
that habit eventually will lead you continuous headache.
After trying the solution of #jiang, which produced the same error, I finally have found the culprit.
Turns out it was an error due to my part ( it usually is, you just have to find it ).
Turns out I had set the read transaction to False sometime during the processing/reading of the fields of the original query.
I perform a lookup in another table to get the description of a integer value in the query.
This lookup query uses the same read transaction , and sets this transaction to False after looking up the description.
AFter returning to the original query, reading integer and string fields pose no problem (although the read transaction has been set to False), but reading a BLOB field into a string with the ...AsString method produces an error or returns 'BLOB'.
Obviously I need to set the read transaction to True at the start of the read actions and set it to False after ALL read transactions. This is the major change when you convert a Paradox BDE application to a Firebird of Sqlserver application.
Anyway, I am glad I have found the solution. Hopefully it will help others too.
Delphi - NOVICE programmer - (meaning don't expect advanced concepts to be already known )
I am working on an application, part of which is a scheduling function. I look at a group of appointments. These appointments fall on a specific date. I need to create a consolidated view of all the appointments on each date. By this, I mean that I create a "grouping" of data about Jan 17th, this is going to be how many appointments are on that date, when they start, when they stop, etc. This may be an array, a Record, a class, don't know at this point. I may have one for the 17th, then the next one is for the 22nd, then the next is for the 24, and then I may have one every day for the next 35 days... I will have either 0 or 1 structures/containers per day, and I can see having 3 to 5 months months of these...Which ones exist and which ones don't exist will be very fluid. This structure is a consolidated view, meaning that I get some of the information from one appointment, and then some of the information from the next appointment, so as I am reading each appointment, I need to be able to find this structure quickly.
I need these structure to be memory based, so they are fast. They MAY be of different sizes (depending on the number of appointments on that day.
REQUIREMENTS
So, I need to be able to create these structures on the fly. (for example, I have just read an appointment for Jan 22, and need to update the structure, but it doesn't exists yet, so I need to make it on the fly).
I need to be able to find them them quickly. (Hash on TDate maybe).
Each one of the structures will hold multiple data types (boolean, TDateTime, TStringlist, etc).
I have Delphi 2010 if that helps...
What structure am I looking for? Easy, fast, already included with D2010 (or free) are all important.
Thanks
GS
UPDATED INFO:
So it appears the TDictionary or TObjectDictionary is the way to go...
I have decided (I think...) to use a record to hold my base information, and then store those records in either TDictionary or TObjectDictionary. I am running into a challenge in both of them. In TDictionary, I cannot figure out how to free the records when I am done with them (since they are pointers), and with TObjectDictionary, I cannot create it with a record type... Any help appreciated. Code samples are...
// Create the base record definition that will be put in the TObjectDictionary/TDictionary
type
TSummaryAppt = record
Date : TDate;
SA_ID : Integer;
BusyFlag : Array[1..36] of Boolean; // 15 minute periods...
PCTFree: Double;
LargestFreeMinutes : integer;
end;
If I go TObjectDictionary, this will not work...
var
Dic : TObjectDictionary<Integer,TSummaryAppt>;
begin
Dic := TObjectDictionary<Integer,TSummaryAppt>.Create([doOwnsKeys, doOwnsValues]);
The Create line fails on execution (compiles fine) with Invalid Class Typecast.
TDictionary appears to be a little friendlier, but I have to deallocate my memory....
var
Dic : TDictionary<Integer,TSummaryAppt>;
rec : ^TSummaryAppt;
p : TSummaryAppt;
i : Integer;
begin
Dic := TDictionary<Integer,TSummaryAppt>.Create;
// Now add some records. THese have to be created dynamically
// because I dont know at compile time how many there are.
new(rec);
rec.Date := now;
rec.SA_ID := 3;
Dic.Add(1, rec^);
new(rec);
rec^.Date := now;
rec^.SA_ID := 5;
Dic.Add(2, rec^);
new(rec);
rec^.Date := now;
rec^.SA_ID := 7;
Dic.Add(3, rec^);
// Test ...
for p in Dic.Values do begin
ShowMessage(IntToStr(p.SA_ID));
end;
// Now free everything. HERE IS WHERE I AM HAVING PROBLEMS...
// What should I be doing?
for p in Dic.values do
p.dispose;
Dic.Values.Free;
Dic.Keys.Free;
Dic.Free;
end;
Any and all help appreciated. What should I be doing different?
Thanks so much!
GS
Regarding your update to the question (which really needs to be a new question), here's what you need to do.
First of all, since both your key and value for the dictionary are value types you need to use TDictionary<K,V>. When you add items to the dictionary, a copy is made of both key and value so this means you need not do any dynamic allocation.
Your code should look like this:
type
TSummaryAppt = record
Date: TDate;
SA_ID: Integer;
BusyFlag: Array[1..36] of Boolean; // 15 minute periods...
PCTFree: Double;
LargestFreeMinutes: Integer;
end;
....
var
Dic: TDictionary<Integer, TSummaryAppt>;
rec: TSummaryAppt;
....
// create the dictionary
Dict := TDictionary<Integer, TSummaryAppt>.Create;
....
// initialise rec, in your code you would put real values in
FillChar(rec, SizeOf(rec), 0);
rec.Date := now;
rec.SA_ID := 3;
Dict.Add(1, rec);
rec.Date := now;
rec.SA_ID := 5;
Dict.Add(2, rec);
//etc.
When you have finished with the dictionary all you need to do is free it. The dictionary owns all the contents and will clean up.
Dict.Free;
You may well prefer to wrap up the functionality of the dictionary and expose it through a higher-level interface. So you may have an Add method that received as parameters all the fields of the value to be added. And you may want an update method that received just the mutable fields.
Do you know the generic TDictionary collection?
class TAppointmentCalendar = TDictionary<TDate, TAppointments>
end;
TAppointments also can be a generic class, based on Generics.Collections.TObjectList:
class TAppointments = TObjectList<TAppointment>
end;
These classes can be extended as needed, for example to add properties or data aggregation methods.
Then instantiate a Calendar
Cal := TAppointmentCalendar.Create;
Cal.Add(MyDate, AppointmentsForThisDay);
or retrieve Appointments
var
Appointments: TAppointments
begin
Cal.TryGetValue(ADate, Appointments);
...
TDictionary performs hash based Key lookups.
First of all, as a novice in data structures, it is worth buying and reading this great book: The Tomes of Delphi: Algorithms and Data Structures - By Julian Bucknall.
Here are some potential layout to implement your application.
A. NoSQL database. For instance, take a look at our BigTable Open Source components.
The root component TSynBigTableis available to records within a file-based database. It is light, and very optimized for speed.
Two of their children do handle fields within records. The field layout can change on the fly. See TSynBigTableRecord and TSynBigTableMetaData.
B. Use a regular SQL database, either like TClientDataSet or direct via SQL (you'll find some Open Source components on a static SQLite3 engine (static, i.e. with no external dll needed).
Using SQLite3 as application data, is a very good idea. It is the main purpose of this library - it is used by a lot of programs, like FireFox or Chrome, or even in most Cell phones OS.
In order to make queries fast, you'll have to create indexes on some columns (e.g. date field), and therefore query results will be immediate.
I do not recommend "to be able to create these structures on the fly". It is not a good programming practice IMHO - or it will become very complex: it would need to store the field layout within the records, so it is not a good path for a novice Delphi programmer.
You should better make your data structures open enough to handle any kind of data. With SQLite3 you can serialize your data as BLOB or text (e.g. with JSON). This is e.g. what we allow in our Open Source mORMot ORM - it is client-server, but can be used stand alone. I'd recommend taking a look at our framework documentation, especially the SAD document which tries to present some design approach, like test-driven, ORM or SOA.
C. If you want a TDictionary kind of storage, take a look at our TDynArray wrapper, which handle the same methods but has some unique features like automated serialization or multiple indexes (not handled by TDictionary), which are mandatory for your request.
AFTER QUESTION UPDATE
Some code using TDynArray:
type
TSummaryAppt = record
Date : TDate;
SA_ID : Integer;
BusyFlag : Array[1..36] of Boolean; // 15 minute periods...
PCTFree: Double;
LargestFreeMinutes : integer;
end;
TSummaryApptDynArray = array of TSummaryAppt;
var rec: TSummaryAppt;
SAs: TSummaryApptDynArray;
SA: TDynArray;
F: TFileStream;
begin
SA.Init(TypeInfo(TSummaryApptDynArray),SAs);
rec.Date := now;
rec.SA_ID := 3;
SA.Add(rec); // rec is now added in SAs[]
assert(length(SAs)=1); // or SA.Count=1
assert(SAs[0].SA_ID=3);
for rec in SAs do // will work like any dynamic array
ShowMessage(IntToStr(p.SA_ID));
F := TFileStream.Create('datafile',fmCreate);
SA.SaveToStream(F); // a TDictionary won't do that
F.Free;
SA.Clear;
assert(length(SAs)=0); // or SA.Count=0
F := TFileStream.Create('datafile',fmOpenRead);
SA.LoadFromStream(F); // a TDictionary won't do that
F.Free;
assert(length(SAs)=1); // or SA.Count=1
assert(SAs[0].SA_ID=3);
for rec in SAs do // will work like any dynamic array
ShowMessage(IntToStr(p.SA_ID));
// you need nothing to free the memory, since both are handled by the compiler
end;
Of course, your array may be stored directly in one block, since it contains only plain data (double, integer, booleans); but our TDynArray wrapper is able to handle any string or other dynamic array within.
Some code using our ORM:
type
TBusyFlag = set (1..36);
TSummaryAppt = class(TSQLRecord)
private
fDate : TDate;
BusyFlag : TBusyFlag; // 15 minute periods...
PCTFree: Double;
LargestFreeMinutes : integer;
published
// already contains an ID: integer field
property Date : TDate read fDate write fDate;
property BusyFlag : TBusyFlag read fBusyFlag write fBusyFlag ; // 15 minute periods...
property PCTFree: Double read fPCTFree write fPCTFree;
property LargestFreeMinutes : integer read fLargestFreeMinutes write fLargestFreeMinutes;
end;
// then initialize the database model and use your database:
Model := TSQLModel.Create([TSummaryAppt]);
Client := TSQLRestClientDB.Create(Model,nil,'FileName',TSQLRestServerDB);
Client.Server.CreateMissingTables(0); // will create the database if needed
...
rec := TSummaryAppt.Create;
rec.Date := Now;
rec.ID := 3; // but the ORM may create one unique ID for you
Client.Add(rec);
rec.Date := 0;
Client.Retrieve(3,rec);
...
rec.Free;
Client.Free;
Model.Free;
Our ORM is here used locally, all in one executable, creating a SQLite3 database for data storage. But if you change TSQLRestClientDB into TSQLite3HttpClient and TSQLRestServerDB+TSQLite3HttpServer, you'll be able to use your data remotely, via standard JSON (and also from an AJAX application). Without modifying your client code. And if you want to store your data with something else than SQLite3 (even up to Oracle, or an in-memory database), you can.
I am working at the moment with the advantage database server from sybase. I am programming in delphi and I am using a local server. My problem is, if I make a sql query inside my own source code the query is 3 times slower (especially for nested Select queries) as if I would use exactly the same query inside the Advantage Database Architect delivered by sybase, too.
The database is also the same. Is there any optimization step I am missing?
My Source Code looks like this:
//Initializing Connection and Query
FADSConnection.LoginPrompt := false;
FADSConnection.ConnectPath := Filename;
FADSConnection.AdsServerTypes := [stADS_LOCAL];
FADSConnection.Name := 'ADB';
FADSConnection.EncryptionOptions.DataEncryptionType := etAdsAES256;
FADSConnection.IsConnected := true;
FADSQuery.DatabaseName := FADSConnection.Name;
FADSQuery.SourceTableType := ttAdsADT;
FADSQuery.AdsTableOptions.AdsCharType := GERMAN_VFP_CI_AS_437;
s := 'SELECT *'
+ 'FROM ADB_Table1 WHERE No IN'
+'(SELECT No'
+ 'FROM ADB_Table2 WHERE V=0.4 AND N=26 AND No IN'
+ '(SELECT No FROM ADB_Table2 WHERE V=0.6 AND N=8)) AND Count=2'
FADSQuery.sql.Text := s;
FADSQuery.Open;
FADSQuery.first;
The Query takes 600 ms in my program. In the advantage database architect it needs 200 ms. The database has around 18000 entries.
Thank you in advance!
So, I found my mistake:
The Problem is the initialization of the query instance.
Somehow overhanding of the Source Table Type and the AdsCharType isn't necessary.
FADSQuery.SourceTableType := ttAdsAdt;
FADSQuery.Tableoptions.AdsCharType := GERMAN_VFP_CI_AS_437;
I just removed the above lines and my queries are nearly as fast as the Advantage Data Architect.