How can I create and use aggregate field with TADODataset? - delphi

I can create a new aggregate field (TAggregateField ) with TADODataset in design mode,
With expression "Sum(MyPrice)" (MyPrice is a field in this data set).
but this field does not display anything in the binded DBText control (it is simply empty).
Can TAggregateField even be used with TADODataset? (all examples I have seen were related to TClientDataSet)

None of the ADO dataset based components shipped with Delphi supports aggregate fields. They would have to implement aggregate support methods (such as e.g. GetAggregateValue) of the TDataSet ancestor.
To your question, yes, you can use TAggregateField with shipped ADO components (there's nothing what would restrict you) but it's useless because such field will always return NULL value due to missing GetAggregateValue method implementation in ADO components.

Related

Is it safe to share a TDataSource between several controls at the same time?

I have moved all my lookup tables/queries in the main DataModule of my application. Now I wonder if I could also move the associated TDataSource to the Datamodule.
For example, if I have two TLookupCombobox on two different forms (or even on the same form) using the same TDataSource, will this have an impact ? Like when I choose an item in combobox1 will it move to the same item on combobox2 ?
I only want to use these TDataSources read only.
Yes, it is possible for a TDataSource to be placed in a TDataModule and used as the datasource for db-aware components on a number of forms. However, it is generally not a good idea and is one you are likely to come to regret. Doing it creates a maintenance problem for yourself, because it is rather too easy to make some change to its properties which affects how the db-aware components behave and overlook the knock-on consequenes for the various forms (same is true for TDataSet descendants, of course).
Sharing a datasource across forms can impact on performance if instances of the forms exist at the same time, and this is particularly so if you carry out some procedure which iterates the records in the dataset (unless you surround the operation with calls to TDataSet.DisableControls and .EnableControls).
So imo although there are benefits to placing datasets in a datamodule which is used by a number of forms, it is far better to place the datasource(s) on the forms which contain the connected db-aware components.
So far as db-aware compound controls such as TDBComboBox, TDBListBox, etc are concerned, these always display the value from the current record in the related dataset. No amount of coding will permit these components to simultneously display different field values on different forms: if they are fed by the same datasource from the same record field, they will display the same value (though of course, the contents of associated lists in the controls, as in the drop-down list of a DBComboBox, may differ between them). This arises from the way TDataSets are designed to operate: they all implement a dataset "cursor" which makes all data-reading and -writing operations operate on a single, "current" record of the dataset and the value of the dataset field which a db-aware component displays is the value in the record the cursor is on.
You are asking two questions there. - No problem.
On the usage of the Datasource, absolutely. I have all my TTables, TQuerys and DatSources in the one DataModule aand reference them from multiple Forms with multiple components on each.
On the combobox1 v combobox2 interaction, it will depend on the code behind them. Nominally in the OnChange event.
Ian

Implementing a unique surrogate key in Advantage Database Server

I've recently taken over support of a system which uses Advantage Database Server as its back end. For some background, I have years of database experience but have never used ADS until now, so my question is purely about how to implement a standard pattern in this specific DBMS.
There's a stored procedure which has been previously developed which manages an ID column in this manner:
#ID = (SELECT ISNULL(MAX(ID), 0) FROM ExampleTable);
#ID = #ID + 1;
INSERT INTO Example_Table (ID, OtherStuff)
VALUES (#ID, 'Things');
--Do some other stuff.
UPDATE ExampleTable
SET AnotherColumn = 'FOO'
WHERE ID = #ID;
My problem is that I now need to run this stored procedure multiple times in parallel. As you can imagine, when I do this, the same ID value is getting grabbed multiple times.
What I need is a way to consistently create a unique value which I can be sure will be unique even if I run the stored procedure multiple times at the same moment. In SQL Server I could create an IDENTITY column called ID, and then do the following:
INSERT INTO ExampleTable (OtherStuff)
VALUES ('Things');
SET #ID = SCOPE_IDENTITY();
ADS has autoinc which seems similar, but I can't find anything conclusively telling me how to return the value of the newly created value in a way that I can be 100% sure will be correct under concurrent usage. The ADS Developer's Guide actually warns me against using autoinc, and the online help files offer functions which seem to retrieve the last generated autoinc ID (which isn't what I want - I want the one created by the previous statement, not the last one created across all sessions). The help files also list these functions with a caveat that they might not work correctly in situations involving concurrency.
How can I implement this in ADS? Should I use autoinc, some other built-in method that I'm unaware of, or do I genuinely need to do as the developer's guide suggests, and generate my unique identifiers before trying to insert into the table in the first place? If I should use autoinc, how can I obtain the value that has just been inserted into the table?
You use LastAutoInc(STATEMENT) with autoinc.
From the documentation (under Advantage SQL->Supported SQL Grammar->Supported Scalar Functions->Miscellaneous):
LASTAUTOINC(CONNECTION|STATEMENT)
Returns the last used autoinc value from an insert or append. Specifying CONNECTION will return the last used value for the entire connection. Specifying STATEMENT returns the last used value for only the current SQL statement. If no autoinc value has been updated yet, a NULL value is returned.
Note: Triggers that operate on tables with autoinc fields may affect the last autoinc value.
Note: SQL script triggers run on their own SQL statement. Therefore, calling LASTAUTOINC(STATEMENT) inside a SQL script trigger would return the lastautoinc value used by the trigger's SQL statement, not the original SQL statement which caused the trigger to fire. To obtain the last original SQL statement's lastautoinc value, use LASTAUTOINC(CONNECTION) instead.
Example: SELECT LASTAUTOINC(STATEMENT) FROM System.Iota
Another option is to use GUIDs.
(I wasn't sure but you may have already been alluding to this when you say "or do I genuinely need to do as the developer's guide suggests, and generate my unique identifiers before trying to insert into the table in the first place." - apologies if so, but still this info might be useful for others :) )
The use of GUIDs as a surrogate key allows either the application or the database to create a unique identifier, with a guarantee of no clashes.
Advantage 12 has built-in support for a GUID datatype:
GUID and 64-bit Integer Field Types
Advantage server and clients now support GUID and Long Integer (64-bit) data types in all table formats. The 64-bit integer type can be used to store integer values between -9,223,372,036,854,775,807 and 9,223,372,036,854,775,807 with no loss of precision. The GUID (Global Unique Identifier) field type is a 16-byte data structure. A new scalar function NewID() is available in the expression engine and SQL engine to generate new GUID. See ADT Field Types and Specifications and DBF Field Types and Specifications for more information.
http://scn.sap.com/docs/DOC-68484
For earlier versions, you could store the GUIDs as a char(36). (Think about your performance requirements here of course.) You will then need to do some conversion back and forth in your application layer between GUIDs and strings. If you're using some intermediary data access layer, e.g. NHibernate or Entity Framework, you should be able to at least localise the conversions to one place.
If some part of your logic is in a stored procedure, you should be able to use the newid() or newidstring() function, depending on the type of the backing column:
INSERT INTO Example_Table (newid(), OtherStuff)

Add fields to a clientdataset or access it dynamically?

What's the point in adding fields to TClientDataset if and can do Cds.FieldByName('field').Value?
Is it faster to have the reference?
Is it 'clearer'?
The problem with
DataSet.FieldByName('field').Value
is a performance one. Each time this is executed, it causes a serial search through the fields collection of the dataset to locate the one with the required name. This search is not optimised in any way, for instance by using a binary search or hashing algorithm. So, if there are many fields and/or you are doing this access while iterating the records in the dataset, it can have a significant impact on performance.
That's one reason, but not the only one, to define "persistent" TFields using the Object Inspector. You can obtain a reference to a particular TField by using the symbolic name known to the compiler and this happens once only, at compile time. So yes, it is faster than FieldByName. up to you whether you find it clearer.
Other reasons to use persistent TFields include the ease which which calculated fields can be set up and, more importantly, the fact that the calculated field(s) do not need to be accessed via FieldByName in the OnCalcFields event. The performance hit of using FieldByName versus persistent fields is, of course, multiplied by the number of fields referenced in the OnCalcField event and OnCalcFields is called at least once for each record in the dataset, even if you do not iterate the dataset records in your own code.
The above is true of all TDataSet descendants, not just TClientDataSets.

SPROC to update record: how to handle unchanged values

I'm calling a update SPROC from my DAL, passing in all(!) fields of the table as parameters. For the biggest table this is a total of 78.
I pass all these parameters, even if maybe just one value changed.
This seems rather inefficent to me and I wondered, how to do it better.
I could define all parameters as optional, and only pass the ones changed, but my DAL does not know which values changed, cause I'm just passing it the model - object.
I could make a select on the table before updateing and compare the values to find out which ones changed but this is probably way to much overhead, also(?)
I'm kinda stuck here ... I'm very interested what you think of this.
edit: forgot to mention: I'm using C# (Express Edition) with SQL 2008 (also Express). The DAL I wrote "myself" (using this article).
Its maybe not the latest state of the art way (since its from 2006, "pre-Linq" so to say but Linq works only for local SQL instances in Express anyways) of doing it, but my main goal was learning C#, so I guess this isn't too bad.
If you can change the DAL (without changes being discarded once the layer is "regenerated" from the new schema when changes are made), i would recomend passing a structure containing the column being changed with values, and a structure kontaing key columns and values for the update.
This can be done using hashtables, and if the schema is known, should be fairly easy to manipulate this in the "new" update function.
If this is an automated DAL, these are some of the drawbacks using DALs
You could implement journalized change tracking in your model objects. This way you could keep track of any changes in your objects by saving the previous value of a property every time a new value is set.This information could be stored in one of two ways:
As part of each object's own private state
Centrally in a "manager" class.
In the first solution, you could easily implement this functionality in a base class and have it run in all model objects through inheritance.
In the second solution, you need to create some kind of container class that will keep a reference and a unique identifier to any model object that is created and record all changes in its state in a central store.This is similar to the way many ORM (Object-Relational Mapping) frameworks achieve this kind of functionality.
There are off the shelf ORMs that support these kinds of scenarios relatively well. Writing your own ORM will leave you without many features like this.
I find the "object.Save()" pattern leads to this kind of behavior, but there is no reason you need to follow that pattern (while I'm not personally a fan of object.Save(), I feel like I'm in the minority).
There are multiple ways your data layer can know what changed and most of them are supported by off the shelf ORMs. You could also potentially make the UI and/or business layer's smart enough to pass that knowledge into the data layer.
Two options that I prefer:
Generating/hand coding update
methods that only take the set of
parameters that tend to change.
Generating the update statements
completely on the fly.

Multiselect listbox bound to database in Delphi 6

I'm using Delphi 6, and I want a database bound list box with multiselect. I found three types of List boxes: TListBox, TDBListBox and TDBLookupListBox.
As far as I can understand, TListbox is not bound to database. TDBListBox and TDBLookupListBox can't be multiselected.
Is there a way to get a multiselect listbox binded to database?
The problem with databinding components is that they rely on a datasource and a datasource has only a single cursor. That is probably the reason why.
By the way, do you need to change the data? Else you could fill a normal listbox from a dataset. Or even use an invisible data listbox and copy the contents to a normal listbox.
Not as far as I know.
The standard is that you offer with the list a bunch of values in which 1 represents the current record.
Unless you have a multivalued field (against best practices) I can't see how you could multiselect...
Or what you might want is actually a sub-table?
DevExpress TcxDBListBox supports multiselect. I use their multiselect drop down check box bound to a database, it's sweet.
The components have methods you can implement to convert to and from your list; EditValueToStates and StatesToEditValue. While the data I store is not normalized (I store a semi-colon delimited list of version numbers), I created a full text search index on the field, with a semi-colon as a delimiter, and now I can still perform optimized searches on that field.
You could create your own custom listbox component that descends from TCustomListBox and add a Datasource property for your list, and another property such as TStrings to be used as a container to hold selected values. You could then post changes to your database using a button click.
If you fiddle with some of the options of a TDBGrid and restrict the columns it displays, you can make something that looks a whole lot like a listbox. Try setting the Options property to [dgTitles,dgTabs,dgRowSelect,dgAlwaysShowSelection,dgCancelOnExit,dgMultiSelect] and work from there.
In a TDbLookupListBox you have the option to bind two different things to data; first you can bind the list to a dataset (ListSource/ListField/KeyField), second you can bind the selected item to a field in another dataset (DataSource, DataField). There is nothing conceptually wrong with wanting to bind the list of items to a dataset, and then manually manage multiple selections, however I don't think it is possible with the current implementation without subclassing and enabling the required control styles.
Based on your comment to François, I would use a normal TListbox and write code to insert all distinct values into the list, and then handle the multi-select values yourself. Jeremy's solution also works, and the DevExpress Express Quantum Grid has a nice filter system which might even save you some other programming.

Resources