HowTo: Data Provider

903 views
Skip to first unread message

Gabriel Medina

unread,
May 12, 2009, 11:17:00 PM5/12/09
to G.GeneXus, ggg (GeneXus Google Groups)





category:Data Provider Object


Applications increasingly need to interact by exchanging data. From an application in a travel agency, ticket requests have to be sent to an airline containing the passengers' information; from another application a Google service has to be used; or merely exchange structured data within the application itself. 

In this scenario, handling structured data becomes essential. The format used to represent them is not and will not be homogeneous.  While XML is the most widely used format today, other formats are emerging, such as JSON to lighten the transfer, and we don't know what the future holds. 

A Data Provider is basically a 'declarative procedure' used to obtain data in a hierarchical structure, with maximum clarity and minimum effort.  How? By making the intention clear, that is to say, the output. Then, by simply indicating the desired format of the output.

image:DataProvider Input Output

Anything that can be done with a Data Provider can also be done with a Procedure. Both can be seen as processes where there is an Input, a Transformation and an Output. The difference between them is that in a regular Procedure the focus is on the Transformation language. Meanwhile, in a Data Provider the focus is on the Output language.
 
For example, if the process consists of reading all customers (Input) and writing an XML file with them (Output), the Procedure is as follows:

&XmlWriter.Open(...)
&XmlWriter.WriteStartElement('Clients')
For Each
   &XmlWriter.WriteStartElement('Client')
      &XmlWriter.WriteElement('Code', CustomerId.ToString())
      &XmlWriter.WriteElement('Name', CustomerName)
   &XmlWrite.EndElement()
Endfor
&XmlWriter.EndElement()
&XmlWriter.Close()

&XmlWriter is a variable of the GeneXus XMLWriter data type.

Here is not so easy to quickly 'see' the procedure output. Indeed, it is confusingly embedded inside the code written to obtain it.

Instead, the Data Provider shows clearly the intention:

Clients
{
     Client
     {
          Code = CustomerId
          Name = CustomerName
     }
}

Then, through the ToXML method, the output could be easily converted into the equivalent XML format:

<Clients>
   <Client>
      <Code>1</Code>
      <Name>John Smith</Name>
   </Client>
   <Client>
      <Code>2</Code>
      <Name>Jennifer Lopez</Name>
   </Client>
   ...
</Clients>

 image:DPExample

The way GeneXus implements hierarchical structures is the Structured Data Type (SDT) Object. You can see this example fully implemented.

As you can see, a Data Provider is easier to write and understand, so some tasks will be easier to do with Data Providers than with regular Procedures. Which are they? Those that return structured data. For example:

Examples

Before going into a more formal definition, it can be helpful to present some samples:

Sample 1: Listing today's Invoices ordered by amount

Invoices
{
Date = today()
Invoice Order InvoiceAmount
Where InvoiceDate = today()
{
Id = InvoiceId
CustomerId = CustomerId
CustomerName = CustomerName
Amount = InvoiceAmount
Product
{
Id = ProductId
DetailQuantity = InvoiceDetailQuantity
DetailAmount = InvoiceDetailAmount
}
}
}

The output will be an SDT with today's date and a collection of  items representing those invoices with InvoiceDate = today().

Sample 2: System Constants

ChartConstants
{
ChartServer = 'http://www.gxchart.com/drawchart.asp'
ChartParameters = '...'
}

Another example in the same area is loading the Tabs SDT needed by the TabbedView Web Component:

LoadTabs parm(&CustomerId)

Tabs
{
Tab
{
Code = 'General'
WebComponent = link(WCustomerGeneral, &CustomerId)
}

Tab
{
Code = 'Invoices'
WebComponent = link(WCustomerInvoices, &CustomerId)
}
}

(see here for more details).

Sample 3: RSS feed with today's Invoices

rss
{
version = "2.0"
channel
{
title = 'Today's Invoices"
link = link(ViewTodayInvoices)
item
where InvoiceDate = today()
{
title = format('Invoice %1', InvoiceId)
link = link(ViewInvoice, InvoiceId)
description = format('Invoice %1 for customer %2, amount %3', InvoiceId, CustomerName, InvoiceAmount)
author = 'system'
pubDate = today()
}
}
}

Now that you understand the spirit of Data Providers, take a look at:

------------------------------------------------------------
Data Provider Language


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.

image:DPLanguejeOmitItem

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

Data Provider: Input








Using Data Providers in Other GX Objects


As you may already know, the output of a Data Provider is an SDT or BC (that could be later -even immediately- converted to another format such as XML). The only output formats supported are SDT and BC. However, not only a simple SDT or BC can be returned, but also a collection of them.

Using a Data Provider in another GeneXus Objetc is similar to using a Procedure that returns an SDT or BC to the caller.

In this article:

Example

An easy way to implement an RSS feed is defining a Procedure that calls a Data Provider to populate the data and returns an XML with the RSS format:

&rss = rss() // RSS() is a DP that load the data
&response.addstring(&rss.toxml(true))

Notes:

  • the proc must be defined as Main with Call Protocol HTTP.
  • &rss is of type RSS SDT.
  • &response is of type HTTPResponse.

When the output is a Business Component

Usually the Data Provider Output is an SDT, but some times it is very convenient to use a Business Component as the Output. For example for the following Data Provider 'CustomersProvider'  the Output is the 'Customer' BC, the 'Collection' property is set to 'True', and the 'Collection Name' property is set as 'customers' (exactly the name given to the root Group in the Source).

image:DPOutputProperties

Notes

  • Given this DP will return more than one Customer, the 'Collection' property is set to 'True', and the 'Collection Name' property is set as 'customers' (exactly the name given to the root Group in the Source).

customers

{

customer

{

CustomerId = 400

CustomerName = 'Mike Hemingway'

}

customer

{

CustomerId = 401

CustomerName = 'Jeniffer Faulkner'

}

}

So, in a procedure we could define a '&myCustomersBC' variable with 'Customer' BC data type and with the 'Collection' property set to True (see Collection variables):

image:BC collection variable properties

Then, inside the object we could write:

&myCustomersBC = CustomersProvider()

For &customer in &myCustomersBC

&customer.Save()

endfor

That is, the two-item collection returned by the 'CustomerProvider' Data Provider is scanned and each time, in the '&customer' variable (of the 'Customer' BC data type) each item is loaded and a Save is done, having a new record on the database customers table.

Important:
The Data Provider only fills the associated structure of the BC, as would be done by hand in a Procedure, so that, the resulting BC mode is 'INSERT'. In other words, a Data Provider cannot make a 'LOAD' operation. That is to say, the Save() method applied to the BC returned by a Data Provider will attempt to create new records in the database.
 

It isn't necessary to define the '&myCustomersBC' variable, you just can write (see For in command):


For &customer in CustomersProvider()

&customer.Save()

endfor

Parameters

The Data Provider also supports receiving parameters (all parameters are "IN" parameters). Example:

&CustomerId = 1
&Tabs = Tabs(&CustomerId)

Suppose that you define a &Tabs variable of TABS type, which is an SDT associated to the Output property of the Data Provider named "Tabs".

How to work with the items of a collection returned by a Data Provider

You just write the following:

For &Var in Dataprovider(parameters list)
    &Var.SomeElement
Endfor
This avoids having to define a collection variable and writing the following:
&Collection = Dataprovider(parameters list)
For &Var in &Collection
    &Var.SomeElement
Endfor
See also For in Command.

Example

Another example could be to show a tree from a transaction structure through an SDT.
TreeNodeCollection
{
TreeNode
  where CategoryParentId = &CategoryId when not &CategoryId.IsEmpty()   
  {
  Id = CategoryId.ToString()
  Name = CategoryName
  Nodes = CategoriesDP(CategoryId)
 }
 TreeNode
where CategoryId = &CategoryId
 {
  Id = CategoryItemId.ToString()
  Name = CategoryItemName  
 }
}
 
..
Recursive Data Providers


Data Providers can fill recursive structures, either directly or through recursive calls.

Consider an 'Employee' transaction with the following structure:

Employee

EmployeeId*

EmployeeName

BossId subtype of EmployeeId

BossName subtype of EmployeeName

Example 1

Suppose it is necessary to load an employee's information into a structure (for example to send it as a parameter to another object). First, the SDT should be defined as follows:

image:Employee SDT

Note that this is a Recursive SDT. Then, a Data Provider receiving the employee will solve the problem:

image:DP Load EmployeeSDT

Where:

Output: EmployeeSDT

Collection: False

Parm( &EmployeeId );

This is a recursive Data Provider. DPLoadEmployee is calling itself to fill the recursive member of the EmployeeSDT. For example, the XML output  (DPLoadEmployee( 123 ).toXML()) would be:

 <EmployeeSDT>
<Id>123</Id>
<Name>John Doe</Name>
<Manager>
<Id>99</Id>
<Name>Mary Shelley</Name>
<Manager>
<Id>69</Id>
<Name>Ernest Jackson</Name>
<Manager></Manager>
</Manager>
</Manager>
<EmployeeSDT>

Example 2

Now suppose that given an employee, we have to Output all his/her subordinates (direct or indirect), as shown below:

<SubEmployees>
<Employee>

<Id>99</Id>

<Name>Mary Shelley</Name>

<Subordinates>

<Employee>

<Id>123</Id>

<Name>John Doe</Name>

<Subordinates>

...

</Subordinates>

</Employee>

<Employee>

<Id>150</Id>

<Name>Camile Green</Name>

<Subordinates>

...

</Subordinates>

</Employee>

...

</Subordinates>

</Employee>

<Employee>

...

</Employee>

....

</SubEmployees>

This can be easily done with a Data Provider.

First, it is necessary to define the Output structure (this is done using an SDT):

image:Recursive SDT 'SubEmployees'

Second, the Data Provider itself is:

Name:   DPLoadSubordinates
Output: SubEmployees
Rules: parm(&BossId)

SubEmployees

{

Employee

where BossId = &BossId

{

Id = EmployeeId

Name = EmployeeName

Subordinates = DPLoadSubordintates( EmployeeId )

}

}

Note that this is a recursive Data Provider (DPLoadSubordinates is calling itself).

...
Example: 'CustomersProvider' Data Provider


A Data Provider is a GeneXus Object aimed at providing data collections (for example, a list of customers) or a data structure (for example, customer data) in an easy and high-level, declarative way.

The output is a hierarchical structure: how GeneXus represents that? As an SDT or BC, or a collection of them.

The strength of Data Providers lies in that the focus is on the output. As a result, the GeneXus analyst only has to declare how the several data items of the hierarchical output structure should be filled.

Suppose that we have defined the 'Customer' transaction shown below:

image:Customer Structrure Image

We need to load the customers database into a collection to send it to another object, such as a Web Panel, for example, or any other object:

image:Data Provider drawing example

First, we have to define the SDT to load the customers. A first consideration is that we cannot name the SDT with the same name as the transaction (it's not allowed), so we will call it 'Clients'. A second consideration is about the fact that we have two ways to work with collections. As we will see, we can choose any of them.  

First Option

Defining the 'Clients' collection SDT:

image:Clients SDT

Next, we have to create the Data Provider. We will call it 'CustomersProvider'. In order to work less, after creating the Data Provider we can drag the 'Clients' SDT and the Source will look as follows:

image:CustomersProvider after Drag and Drop

We only have to fill the values on the right with CustomerId and CustomerName, respectively.
The 'Output' Property is a special property that should be given special attention, even if it goes unnoticed because the drag and drop operation automatically set it. The image below shows how we indicate which SDT will be the Data Provider output:

image:Data Provider Clients Output Property Image 2 Note the 'Collection' property. If we had chosen the other possibility, we would have changed to True, as we will see.

The rest is easy and familiar. From the object that requires the customer list, i.e. the web panel of the image above, we only have to define a  variable of 'Clients' SDT data type to receive the output of the Data Provider and, for example, later convert it to XML format:

&clients = CustomersProvider()
&xml = &clients.toXML()

See more Using Data Providers in Other GX Objects


Second Option 

Defining the 'Client' SDT:

image:Client SDT

As in the previous option, now we have to create the 'CustomersProvider' Data Provider. Doing the same as before, after dragging the 'Client' SDT to the Source area, we will have the following:

image:CustomerProvider 2 after Drag and Drop

with the Output Property:  

image:CustomersProvider DP Output Property

However, this is not what we want... We need to load a collection of customers, so we need to change the 'Collection' property as follows:

image:CustomersProvider DP 2 Output Property
 

Note that changing 'Collection' to True introduces a new 'Collection Name' Property. We have to enter a name and then change the Source, adding a group with that name as a root:

image:CustomersProvider DP

The rest is similar to the first option (defining the &clients variable as a collection of the 'Client' SDT data type).

...

Data Provider Use Case: TabbedView usage


The procedural way of loading an SDT (like the one needed in the TabbedView Web Component) is:

Event Start

...

&Tabs = new()

&TabsItem = new()

&TabsItem.Code = 'General'

&TabsItem.Description = 'General info'

&TabsItem.Link = link(ViewCustomer, CustomerId, 'General'

&TabsItem.WebComponent = create(CustomerGeneral, CustomerId)

&Tabs.add(&TabsItem)

&TabsItem = new()

&TabsItem.Code = 'Invoices'

&TabsItem.Description = 'Invoices'

&TabsItem.Link = link(ViewCustomer, CustomerId, 'Invoices'

&TabsItem.WebComponent = create(CustomerInvoices, CustomerId)

&Tabs.add(&TabsItem)

...

TabWC.Object = Create(TabbedView, &Tabs)

Endevent

Using Data Providers, the declarative way is:

LoadCustomerTabs
Tabs
{
TabOptionsItem
{
Code = 'General'
Description = 'General info'
Link = link(ViewCustomer, CustomerId, 'General'
WebComponent = create(CustomerGeneral, CustomerId)
}
TabOptionsItem
{
Code = 'Invoices'
Description = 'Invoices'
Link = link(ViewCustomer, CustomerId, 'Invoices'
WebComponent = create(CustomerInvoices, CustomerId)
}
}

So the Event Start becomes:

Event Start

   &Tabs = LoadCustomerTabs()

   TabWC.Object = Create(TabbedView, &Tabs)

Endevent









4955

Created by: jnj last modification: 01/27/09 05:41 PM by lcpc2009

...
...
...
...

The Input of most GeneXus Objects is:

Parameters

The Data Provider supports receiving parameters, but all parameters are "IN" parameters. Remember the output is specified independently through the Output Properties.

Embedded

Even though the input is usually taken from the Database, it is usually necessary to have other kinds of input data. Because a Data Provider implements an output-driven programming (that is to say, declarative), the only way to obtain information to fill hierarchical structures is through assignations:

Element = Expression
&var = Expression

So, every 'thing' that could be considered as (part of) an expression, is a valid input. For instance: an udp (calling a Procedure as well as a Data Provider. For the latter, you can see a simple sample here, or another one with Recursive Data Providers). Keep in mind that if the returned value is a collection, the Input clause is needed to iterate inside it.

GeneXus objects have other common Input sources: web sessions and cookies.

How do we obtain a web session value corresponding to a key? By having a variable of the 'WebSession' GeneXus data type, through the ''Get'' method:

value = &webSession.Get( key )

So, it could be a Data Provider's Input, as long as '&webSession.Get( Key )' returns a value. In other words, it could be assigned to an Element or variable of the Data Provider's source.

The same can be said about the 'GetCookie' function, used to obtain a cookie's value:

value = GetCookie( CookieName )


Data Provider: Output


As you may already know, the output of a Data Provider is a hierarchical structure. The way GeneXus represents that kind of data is the Structured Data Type (SDT) object or a collection of them (indeed, there is another way, that you could see below).

The way to retrieve that output is not the usual one (through the 'parm' rule). There is a special Output property instead. It is mandatory to associate that SDT to the Data Provider through that property.

Of course, the structure of the defined output in the source has to match the structure of the SDT you associate in the Output Properties of the Data Provider. There can be more than one solution to the same Source, as we can see in the following examples: 

image:DP Source + Output + SDT image:DP Source + Output + SDT version 2

Besides the possibilities of returning a simple SDT, a collection of it can be returned.

In addition a data provider can be exposed as a web service by setting the Expose as Web Service property to true.

Business Component as Output

The Transaction object is also a GeneXus way to represent hierarchical data, but at database level, through a user interface (the Form). The corresponding hierarchical structure resulting of keeping only the structure (and all the logic) but not the Form is called Business Component (BC). So, a Business Component also involves an SDT, that is, a hierarchical structure that can be used in the same scenarios an SDT can be used.  Then, the output of a Data Provider not only can be an SDT (or collection of them) but also a BC (or collection of them).

The BC output case doesn't have any difference with the SDT output case. The BCs structure is filled inside the Data Provider just like an SDT. The object that calls the Data Provider will have to work with the BC, executing the Save method to insert into the database. That is, to the Data Provider point of view, there is no difference between an SDT output and a BC one.







Data Provider.doc
Reply all
Reply to author
Forward
0 new messages