The language used in Data Providers can be defined as a very simple, output-based declarative language. It has three main components: Groups, Elements and Variables. For example:
Customers // this is a Group { &StartTime = now() // this is a Variable Customer // this is a Group { Code = CustomerId // this is an Element Name = CustomerName // this is an Element
} }
'Customers' and 'Customer' are Groups, 'Code' and 'Name' are Elements and '&StartTime' is a variable.
Remember that the output will be hierarchical data, loaded in an SDT or BC that has to be specified in the Output Property. The hierarchical structure that is obtained with the names of groups and elements must exactly match this structure.
In this article:
Groups
As indicated by its name, a Group sorts a set of subordinate
Elements or Groups. A Group may or may not be a collection; in the
example above, Customers is a collection and Customer is not (in
the sense that it will be one 'Customers', made up by many 'Customer'
items. Customer is a repetitive group). Anyway, GeneXus is intelligent
enough to figure out when the Group is a repetitive one and when it is
not.
With repetitive groups, all For Each clauses like Order and Where can be used. For example: Customers { Customer Order CustomerName Where CustomerBalance > 1000
{ Code = CustomerId Name = CustomerName } }
Notes:
- Order and Where clauses apply to Input attributes, not to Elements.
For example: 'Order CustomerName' is valid but 'Order Name' is not. In
fact, the order will cause an access to the Database.
- The behavior is the same as in a For Each. This includes the way to determine the base table, and the fact that conditional order clauses as well as conditional where clauses are valid. The same happens with the 'USING' Data Selector clause and the 'IN'
operator used in a where clause (that is, Data Selectors can also be
used in a Data Provider group, in the same way, same syntax, as in a
For each. See some examples).
- To see an example with order and defined by clauses, read Example: comparison between a Break in a Procedure and in a Data Provider
Elements
An Element is an atomic value in the Output. Code and Name are
Elements in the above example. Each Element must be assigned and the
syntax used is that of Formulas: SampleOfElements { Constant = 'ABC' Complex = 1 if CustomerBalance > 1000; 0 otherwise; }
Note: IIf clause can be used too.
If an Element is assigned with some attributes, GeneXus will infer
how to obtain them from the database in the same way it does in the For
Each clause.
Elements and Attributes usually have the same name, as follows: Customers { Customer { CustomerId = CustomerId CustomerName = CustomerName
} }
In order to allow a more compact writing, this code is the same as the following: Customers { Customer { CustomerId CustomerName
} }
And we can still make it more compact. We can omit the Item name and GeneXus will infer it.

The Item name is necessary if any element of the collection will have different inputs. Samples { //Fixed Data
SampleItem { SampleId = 0 SampleDsc = 'Fix sample' }// Data retrieved from the data base
SampleItem { SampleId = SampleIdInTable SampleDsc = SampleIdInTable } }
Variables
Sometimes it is
necessary to make internal calculations that do not necessarily have
to go in the Output itself. Variables are used in this case. For
example: Customers { &TotalCustomers = 0 Customer { CustomerId = CustomerId CustomerName = CustomerName
&TotalCustomers = &TotalCustomers + 1 } Summary { Total = &TotalCustomers } }
Subgroups
A Subgroup is the declarative equivalent of a subroutine in a procedural language. Customers { Customer { Code = CustomerId Name = CustomerName AddressGroup.Insert(CustomerAddress, CityName)
} } SubGroup AddressGroup(&Street, &City) Address { Street = &Street City = &City
} EndSubGroup
A Subgroup can be internal (like this one) or external (defined as
another Data Provider). For example, if we have an 'Address' SDT with
Street and City as its members, and a Data Provider 'GetAddress' : Output: Address Collection: False Rules: parm( &Street, &City ); Address { Street = &street
City = &city }
We can declare the previous 'GetCustomers' Data Provider like: Customers { Customer { Code = CustomerId Name = CustomerName Address = GetAddress( CustomerAddress, CityName )
} }
The Address member of the 'Customer' output SDT must have the
'Address' SDT data type (the output of 'GetAddress'). Otherwise, an
error will appear.
Note the difference between 'inserting' a subgroup and 'assigning' a member calling a Data Provider.
One interesting use of this is the recursive one.
Advanced Group Options
In addition to the Order and Where clauses, a Group can have many other options designed to have better control of the Output.
Default Clause
If the Default clause is present, the Group will go to the Output
only if the preceding Group (with the same name) is not present. For
example, if you have a Taxes table: Taxes { TaxInitialDate* TaxFinalDate* TaxVAT TaxIncome }
A Data Provider that returns the current tax values can be as follows: CurrentTaxes Where TaxInitialDate >= today() Where TaxFinalDate <= today() { VAT = TaxVAT Income = TaxIncome
} CurrentTaxes [Default] { VAT = 0.7 Income = 0.3 }
Here, the last Group will return the default values if there aren't any taxes in the period.
Note that the Default clause is equivalent to a When None in a For Each.
Paginate Clauses
In order to handle a potentially large number of records, the Count
and Skip clauses let you control how many records will go to the
Output. For example: Customers { Customer [Count = 20] [Skip = 100] { Code = CustomerId Name = CustomerName } }
It will skip the first 100 customers and Output the next 20. This is the clause used to handle all the paging, for example: parm(&PageNumber, &PageSize) Customers { Customer [Count = &PageSize] [Skip = (&PageNumber - 1) * &PageSize]
{ Code = CustomerId Name = CustomerName } }
This will handle any number of page lines and any page size.
NoOutput Clause
The NoOutput clause in a Group means that the Group itself will not be present in the Output, only its subordinate elements.
Suppose that you want to Output the list of Employees, but showing salary information only to authorized users: Employees parm(&UserId) { Employee { Id = EmployeeId
Name = EmployeeName EarningInfo Where IsAutorized(&UserId) { Salary = EmployeeSalary Bonus = EmployeeBonus } } }
The Output (in XML) will be: <Employees> <Employee> <Id>123</Id> <Name>John Doe</Name> <EarningInfo> <Salary>30000</Salary>
<Bonus>5000</Bonus> </EarningInfo> </Employee> ... </Employees>
But if the Output needs to be 'flat' like this: <Employees> <Employee> <Id>123</Id> <Name>John Doe</Name> <Salary>30000</Salary>
<Bonus>5000</Bonus> </Employee> ... </Employees>
The NoOutput is sufficient: Employees parm(&UserId) { Employee { Id = EmployeeId Name = EmployeeName EarningInfo [NoOutput] Where IsAutorized(&UserId)
{ Salary = EmployeeSalary Bonus = EmployeeBonus } } }
OutputIfDetail Clause
To solve the typical situation when we need a collection as output,
when each member structure has a header and some lines (i.e. all
documents with headers and lines, but only for those documents which
have lines) we have the OutputIfDetail clause.
Now, we will see an example: Documents { DocumentHeader [OutputIfDetail] {
DocId DocDate DocLines { DocLineId DocLineDetail DocLineQuantity } } }
Input Clause
Up to this point, it was assumed that the Input comes from the
Database, but it is usually necessary to have other kinds of Input data
as well. For example, the output of some Procedure or Data
Provider. The obvious way to work with that is through variables
that, once assigned, can be treated as usual. For example, we
need a Data Provider that outputs a collection of clients that live in
the same neighborhood as the customer returned by another Data
Provider. Suppose 'DPSpendsMoreClient' is an already declared Data
Provider that returns a customer SDT with the country, city and neighborhood information of the higher spending customer in the Database.
Clients { &Customer = DPSpendsMoreClient() Client where CountryId = &Customer.CountryId where CityId = &Customer.CityId
where CustomerNeighborhood = &Customer.Neighborhood { Name = CustomerName Address = CustomerAddress Phone = CustomerPhone } }
As you can see, the &customer was taken from another Data Provider and used as usual.
But
what if we need to work not with a simple data type, but with a
collection returned by a Procedure or Data Provider? Or if we need to
iterate at certain fixed times? The Input clause lets you work with these other cases.
The simpler one is just a set of fixed values, for example a Data Provider that Outputs the months: VerySimple { Month Input &i = 1 to 12 {
MonthNumber = &i } }
Note: this is similar to a For &i = 1 to 12 in the procedural language.
A more sophisticated Input can be another SDT collection. For example: CustomersFromAnotherDataProvider { &CustomersSDT = GetCustomers() // a DataProvider that Outputs Customers collection
Customer Input &Customer in &CustomersSDT { Id = &Customer.Code Name = &Customer.Name } }
In sum, any collection variable should be treated through the Input clause to achieve iteration.
For a summary of the different Input sources, read Data Provider: Input
|