Dynamics AX 2012 - Grid Control linkPhysicalTableInstance to TempTable, first record inserted doesn't appear in Grid, fine afterwards - axapta

The problem related to using table buffers in AX 2012 with Grid Controls, where first time additions to the form's tempDB were not displayed in real time (but were persistent and subsequent additions worked fine thereafter).
I resolved the problem with help from DAX legend Martin Dráb and Brandon Weise on the Dynamics Community MSDN but I'm posting on SO in case it helps others (as I couldn't find anything close), and I don’t think it hurts the community to add more Dynamics AX content on SO. There are also some learnings to be had about how physical tables link to tempDBs and their relationship to the form's datasource.
Original thread: https://community.dynamics.com/ax/f/33/t/225120
Problem:
I have a Wizard that generates a new form at runtime, containing a Grid Control.
The Wizard passes a reference to one of its temp Tables to the form, in which I use linkPhysicalTableInstance in the runtime form's datasource init() method.
The GridControl has an add new record button, which inserts records in to the tmp table reference.
The new record is saved to the reference temp table correctly, and displays in the grid when the runtime form is closed and reopened but does not display in the grid immediately after the insert.
To add to the weirdness, after a runtime form has been created, a record inserted and then closed, subsequent run time forms do display new record insertions immediately, without needing to be re-opened. Some code snippets below.
Why does this behavior only happen for the first time that data is inserted in to the temp table, but displays fine for subsequent runs of the runtime form?
Creating the runtime form:
args = new Args(formstr(RunTimeFormName));
formRun = classFactory.formRunClass(args);
formRun .parmRuntimeFormsGridTmpDS(sysWizard.ReferenceToWizardsTableTmp()); // Passing a reference for Wizards tmpTable to form
formRun .init();
formRun .run();
formRun .wait();
formRun .detach();
RunTime Form's parmDataSourceMethod:
public void parmRuntimeFormsGridTmpDS(CommentsGridTmp _ReferenceToWizardsTableTmp)
{
ReferenceToWizardsTableTmp = _ReferenceToWizardsTableTmp;
}
DataSource init() method:
public void init()
{
super();
RuntimeFormsGridTmpDS.linkPhysicalTableInstance(ReferenceToWizardsTableTmp);
}
RunTime Form's New button clicked method:
void clicked()
{
int64 numRows;
;
// Refresh records loaded in grid DS to ensure correct number of records for setting initial index number
// Note: SomeId is passed in Args() record, its passing fine as is the number of rows count - and replacing with a static value has no effect.
select count(RecId) from ReferenceToWizardsTableTmp
where ReferenceToWizardsTableTmp.SomeId == someId;
numRows = ReferenceToWizardsTableTmp.RecId;
ReferenceToWizardsTableTmp.Comment = "Comment " + int642str(numRows + 1);
ReferenceToWizardsTableTmp.Filename = "";
ReferenceToWizardsTableTmp.someId = someId;
ReferenceToWizardsTableTmp.insert();
element .Task(#TaskF5);
// super();
}
So as described above, the first time that the runtime form is created and a record is inserted, it doesn't display. Reopening the form will display the inserted data fine. Also, after reopening the form any new records inserted appear in the grid in real time.
I originally supposed it had to be something to do with the linkToPhysicalTable and where the grid fields look for records to display…
By the way, if you have a better answer or explanation then please feel free to contribute.

Solution
I have a working solution whereby I run a select statement on the buffer reference prior to the linkPhysicalTableInstance operation (a delete_from tmptable statement has the same effect and is cheaper), which acts to initialize the buffer reference despite it being empty.
The linkPhysicalTableInstance operation then succeeds at the first run because the buffer properly exists - and changes written to the form DS are now persistent and reflected in the calling Wizard's buffer reference.
In Addition (from Brandon Weise):
In case you happen to be jumping tiers in your code, here's a small gotcha to watch out for.
https://community.dynamics.com/ax/b/dynamicsaxexperience/archive/2016/01/24/2012-unexpected-degeneration-of-insert-recordset-into-tempdb-buffer
Techniques That I Found Useful for Investigation
(Credit to Brandon Weise and Martin Dráb for these)
It does seem that using .linkPhysicalTableInstance(..) to change the underlying temp table associated with a form datasource after it
has already initialized produces some weird behavior. This seems to
be true even when you can demonstrate with .getPhysicalTableName()
that they are linked properly.
One technique to help is to create your form, and
call .init(), but not yet .run(). Then use
.linkPhysicalTableInstance() to link the freshly created temp table
underlying the data source to your external temp table buffer. In
other words, instead of trying to transplant your already created
temp table into the form's data source, let the form create the temp
table, then let the caller transplant that temp table into its own
buffer using .linkPhysicalTableInstance(). Then insert records,
then call .run(). If necessary, call .executeQuery() on the form
data source after .run().
I took a scattergun approach to printing
the table names throughout initialization and operation, and while
the two tables do eventually link correctly with the same table
name, they take a roundabout way of getting there.
Inspect the contents of a temp table while debugging, from SQL Server Management Studio using:
set transaction isolation level read uncommitted;
select * from tempdb..t107946_BE044A13A9C24283897CA1B59607CBD2;
Which is easy if you have the table name from
.getPhysicalTableName(), but even if you don't know it precisely,
it's often easy to locate with a little trial and error. select *
from tempdb.sys.tables; The table will of course start with "t" and
the table number. Often it's the most recently created one, so
sorting by create_date desc floats it to the top, but of course
there can be a pool of them.
Review Methods on a Form Data Source which methods you can use when working with records through a datasource.

Related

In App Maker, how do you make dynamic table cell text?

In App Maker, I am displaying a table and want to replace table cell data with different text using a data lookup from another table. Assume two tables, Departments and Employees.
Departments is two fields, DeptID and DeptDescription.
Employees is multiple fields including DeptID.
In the table listing for Employees, I would like to replace the DeptID with the DeptDescription. (The page datasource is Employees. I do not want to set up a relationship between the data models.)
I am guessing I want to do some scripting in the onDataLoad event for the table cell label for DeptID. I have this much so far:
app.datasources.Departments.query.filters.DeptID._equals = widget.datasource.item.DeptID;
app.datasources.Departments.newQuery().run();
widget.text = app.datasources.Departments.item.DeptDescription;
I know this is not correct, but am I close?
This answer is untested, but I wanted to present a possible solution that would not require a lot of DB calls, especially ones that make repeated calls to a server script which might consume a lot of processing time when you do line item calls.
Set up a separate datasource under the Department model. Change the default 'Query Builder' to 'Query Script' and add a parameter of type 'list(number)' or 'list(string)', this should match your Primary Key field type. Uncheck the 'auto load' option.
In your 'Query Script' portion enter the following code:
query.filters.Id._in = query.parameters.YourParameter;
return query.run();
Go to your Employees datasource that is supposed to generate your table and find your 'On Load' client script section. In this section enter the following code:
var departmentsDs = app.datasources.YourDepartmentsDs;
departmentsDs.properties.YourParameter = datasource.items.map(function(deptIds) {return deptIds.DeptID;});
departmentDs.load();
Now go the page that contains your table. If you have not already create a label widget do so now. In this label widget for the text binding enter the following:
#datasources.YourDepartmentsDs.loaded && (#datasources.YourDepartmentsDs.items).map(function(Id){return Id.Id}).indexOf(#widget.datasource.item.DeptID) !== -1 ? #datasources.YourDepartmentDs.items[(#datasources.YourDepartmentsDs.items).map(function(Id){return Id.Id}).indexOf(#widget.datasource.item.DeptID)].DeptDescription : 'Unable to retrieve Dept Description'
As stated this is untested and I wrote the code from memory without App Maker in front of me so it may require some additional tweaking. Going with the first option presented by J.G. would also be a very viable solution though. And I apologize but the code formatter does not seem to be working for me.
1 way) Create an aggregate table that joins your tables if you need to bypass using the relations feature. This way you can use sql to join the two tables in the datasource definition
2) if you don't want to make a new table. Change the text from a value binding to "more options"
=getDescription(#datasource.item.DeptId)
and then the code you wrote in a client side script
function getDescription(id){
google.script.run
.withSuccessHandler(function successHandler(result){ return result;})
.withFailureHandler( function failureHandler(e){ console.log(" Failed" +e);})
.queryValue(id);
}
server side script:
function queryValue(id){
var query = app.models.Departments.newQuery();
query.filters.DeptID._equals = id;
var results = query.run();
return results[0]["DeptDescription"];
}
that last line might be results[0].DeptDescription

reload table in WatchKit

We have a hierarchical watch app.
The root controller is a table of menu items. That list of items is controlled by a server. The data is retrieved and stored in core data. The menu is populated the first time going into the app.
But I want this table to stay current. My thought was to add code to willActivate to check if there was changes, and reload the table. In my reload logic I call the same function I called the first time, which sets the menuTable.setNumberOfRows and creates each row. Looking at what I'm putting in the logs, it is going through this logic with a different count of rows and new labels. But the app on the watch shows the table with the old data.
How can I get this table to reload with the new information?
I've had this problem too and as rmp says, it still seems to be a bug in watchOS 1.0.1. The problem appears when you try to reload your tableView after run willActivate() and nothing will happen.
In my case, I reload the tableView after receive a reply from a delegate and then I reload all the content just if it's necessary. To achieve this, I remove all rows from a NSIndexSet and load again.
if isNecessary {
self.table.removeRowsAtIndexes(NSIndexSet(indexesInRange: NSMakeRange(0, maxItems)))
isNecessary = false
}
I've tried a lot of tricks but none has worked for me:
Force to reload rows by table.setNumberOfRows(0, withRowType: "data")
Setting parameters to empty text before assign new values
One thing you could do is to hide tableView before removing rows, and avoid the remove animation.
It is a bug in WatchKit. Seems like Apple doesn't handle the repetitive interface object correctly.
The general principle here is: Only insert or remove necessary rows after a table is created. Do not reload the whole table like what we usually do in iOS. It just doesn't work (or trigger the bug).
So specifically, you have to:
Do this in willActivated method. This is correct.
If this is the first load, before the table is even created, do what you are now doing – load all table rows.
For all following times, don't reload the table, fetch the new data and check the desired number of rows.
Compare with the current number of rows in the table, insert to or remove from the bottom of the existing table.
Now simply re-assign the new data to all existing rows. Again, do not reload.
It should work if you follow the above steps.
I have found what works best given the current state of watchkit is to remove all rows then re-populate the table.
Try something like this:
- (void)loadTableData{
//clear the table
[mainTableView setNumberOfRows:0 withRowType:#"myTableRow"];
//set the row count again
[mainTableView setNumberOfRows:numberOfRows withRowType:#"myTableRow"];
//populate table
}
Its pretty simple. As Apple hasn't provided any method to reload the data.
You can still achieve that by simply populating the rows for the tableview.
Below is the sample code:
for index in 0..<tracksTableView.numberOfRows {
if let controller = tracksTableView.rowController(at: index) as? EditPlaylistRowController {
controller.playingTrackId = self.playingTrackID
controller.sharedTrack = trackItems[index]
}
}
Use it whenever you want to refresh the data.
NOTE:
You can still make some conditional statements inside the row controller class.
Happy to help :)

Data copied between rows on form's datagrid

Within AX 2009, I have, through compare and compile, added two new controls within a datagrid on a form, a Real edit and a combobox. I have compiled with no issues. The Allow Edit property is set to Yes on both controls.
However, on the form, if I edit one row, whether typing a new number with Real edit or combobox, and don't hit Save but hit the Down Arrow key, the data I typed on the previous records is duplicate in the next record and so on until I release the Down Arrow key, rather than just setting the focus on a new record.
The table where these fields were created doesn't exhibit this behavior. The focus simply moves to the next record and what was typed will not carry over to the next record. Only the form does this...
Has anyone seen this behavior before with AX forms?
You may have omitted to specify the data source on the grid itself?
Or if the new controls are based on Edit methods on the data source, have you got the data source parameter in the method signature?
see http://msdn.microsoft.com/en-us/library/aa637541(AX.10).aspx

Collect all parameters from all reports in reportserver folder and populate a sql table

[Thanks to Filburt and Devjosh. I have restructured the post and included my attempt approach. ]
I have a table on my SQL DB call ReportList which is a list of report. I need to go through that list and interrogate the reportserver, eport by report, to populate a table called ReportParameters. The ReportParameters table has a column for ReportOwnerID which needs to contains the ReportID value of the corresponding (owner) report as listed in the ReportList table.
This is in VB.NET 2005 ASP2.0 and I have ended up with a mess. Please help me with the cleanest approach to doing this.
It needs to work so:- I have a listbox of the reports as per REportList and a GridView that list all the parameters (uniquely - most of the parameters are common to many reports) the idea being that the parameters get set once and the report can be kicked off by selecting them in the ReportList CheckListBox and clicking on Execute.
I would like it that as I click on a particular report in the ListView, the relevant parameters in the Gridview get a green background and those that do not apply are red. The leftmost column in the gridview contains tha Parameter NAME (not editable) and the next column must be editable to populate the value.
DONE SO FAR:
I have tried on clicking the EXECUTE button , to build a parameters string in a testbox and call that with the Javascript OpenReportWin() function when I open the report in a new window. This works fine, but my biggest issue it interrogating the reportserver reports to get back a list of parameters and dooping them into a table. I have triend to use a hidden DataGrid bound to a ds onto the reportParamaters table; I have tried to poulate it using a datalist but I cannot get the hang of these thionsg and its looking messy. Ther must be a simple clean way of gettting the .GetParameters resultset back from the report server and populating the table without having to create a reportviewer object and cycling through the list of reports - it then has to render each report before you can get that list out.
Thanks
I will withdraw this for now. I will submit a solution when I am comfortable that I have reahced a clean solution.
Mac
PLEASE CLOSE!!!

Qt: QSqlTableModel + QTableView sync with PostgreSQL

I'm writing a database access app for storing some data and want to ask a few questions about the model/view architecture.
(Using: Qt 4.7.4, own build; PostgreSQL 9.0; Targets: WinXP, Win7 (32/64 bit))
Let me first explain what I am trying to achieve and where I am currently.
I have two pages (subclassed QWidgets inserted in a QStackedWidget) with a QTableView bound to a model. Each view is bound to a table in the PostgreSQL server. You can add/edit/delete/sort/filter items.
Each page can be seen by only one type of users, lets call the roles Role1 and Role2.
The submit strategies of everything connected to the model are OnManualSubmit.
(Transaction isolation level = Serializable.) When two users want to edit(for example) the same row, I want to do a "SELECT ... FOR UPDATE" query - to make sure that when someone edits something, he will merge his changes with newer ones (if any, just like in SVN for example). But I see only a submitAll() method the QSqlTableModel.
Maybe catching the signals beforeUpdate(), beforeDelete(), beforeInsert() and performing manually "SELECT ... FOR UPDATE" is one option.
The other way I think is to subclass QSqlTableModel. What is the clean and nice way to achieve this?
I want to periodically update the QSqlTableView for each of the pages (one page is seen at most, Role1 users have access only to Page1 and the same for Role2 => Page2).
The first thing that came to my mind is to use a QTimer and manually call select() of the QSqlTableModel, but... not sure if this is the cool way.
I also want to periodically check if the connection to the database is ok, but I think that a QTimer + QSqlDatabase::isOpen () will do.
Now, the 2 tables have the same primary keys and some columns are the same. I want when a user with Role1 changes a row in Table1 to automatically change corresponding columns of Table2 and vice versa. Should I create a trigger in Postgres?
BTW, the database is small - each of the two tables is around 3-4000 rows with ~10 columns (varchars mostly, 1 text and 2 date colunms).
Thanks for reading and Happy New Year! :)
I think you should consider doing something of the following:
Instead of using QSqlTableModel as a model I'd implement my own model as a subclass of QAbstractTableModel. This will allow you a lot of control over what you can do in terms of data manipulation.
One thing that this will require is for certain fields in the table you would need to implement subclass of QAbstractItemDelegate that will allow for modification of data in the table as I am fairly sure you don't want to allow users updating any field in the table as for example primary key is likely have to be left alone.
For question 2 I would suggest implementing a field called transaction_counter for every row so you don't have to select every row in the table just the updated ones the transaction_counter will be updated on every row update and the new one will be inserted on the new row insert. One thing that will be required is that the counter is unique across the table. For example if initial state of the table is: row1 has counter = 0 and row2 has counter = 0. If row1 is updated counter set to 1. When row1 is then updated again counter on it is set to 2. When row2 is now updated counter on it is set to 3, etc. You can certainly do the data refreshes now using QTimer and this will be much more advantageous to for example checking the data as one user may be updating the same table as another user with the same Role.
For Question 3. I don't see any reason why not custom models and especially if you decide to separate data from the model you can manipulate data separately from it's display. Sort of Data->Model->View->Controller implementation. Each one can be maintained separately as long as you have a feedback mechanism for your delegates.
For Question 4. The answer is sure or you can implement the trigger in your application.
Hope this helps. Have a great New Year!

Resources