I have made a query in AOT. My goal is to print information using Group by "CustGroup" and Order by "count(RecId)" desc of CustTable table. The group by works properly, but the order by does not. I can not understand why...
This is what my query looks like:
This is code I use:
Static void Query(Args _args)
{
QueryRun qr;
CustTable myCustTable;
;
qr = new QueryRun(queryStr(MyQuery));
while(qr.next())
{
myCustTable = qr.get(tableNum(CustTable));
info(strFmt("Group %1 Num %2", myCustTable.Custgroup, myCustTable.RecId));
}
}
The result is:
AX does not sort by Count(RecId) but by the grouping.
You can solve your problem by dragging your query to a new view, then doing the sort on the count field of the view. You can also define the view without a query.
Related
I'm looking for a way to enable logging changes for certain tables.
I have tried and tested adding tables to database log programatically, but with various success so far - sometimes it works sometimes it doesn't (mostly it does not) - it seems simply inserting rows into DatabaseLog table doesn't quite do the trick.
What I have tried:
Adding row with proper tableId, fieldId, logType and . Domain had been assigned as 'Admin', main company, empty field and subcompanies with the same result.
I have created class that handles inserts, main two functions are:
public static void InsertBase(STR tableName, domainId _domain='Admin')
{
//base logging for insert, delete, uptade on fieldid=0
DatabaseLog DBDict;
TableId _tableId;
DatabaseLogType _logType;
fieldId _fieldId =0;
List logTypes;
int i;
ListEnumerator enumerator;
;
_tableId= tableName2id(tableName);
logTypes = new List(Types::Enum);
logTypes.addEnd(DatabaseLogType::Insert);
logTypes.addEnd(DatabaseLogType::Update);
logTypes.addEnd(DatabaseLogType::Delete);
logTypes.addEnd(DatabaseLogType::EventInsert);
logTypes.addEnd(DatabaseLogType::EventUpdate);
logTypes.addEnd(DatabaseLogType::EventDelete);
enumerator = logTypes.getEnumerator();
while(enumerator.moveNext())
{
_logType = enumerator.current();
select * from dbdict where
dbdict.logTable==_tableId && dbdict.logField==_fieldId
&& dbdict.logType==_logType;
if(!dbDict) //that means it doesnt exist
{
dbdict.logTable=_tableId;
dbdict.logField=_fieldId;
dbdict.logType=_logType;
dbdict.domainId=_domain;
dbdict.insert();
}
}
info("Success");
}
and the method that lists every single field and adds as logType::Update
public static void init(str TableName, DomainId domain='Admin')
{
DatabaseLogType logtype;
int i;
container kk, ll;
DatabaseLog dblog;
tableid _tableId;
fieldid _fieldid;
;
logtype = DatabaseLogType::Update;
//holds a container of not yet added table fields to databaselog
kk = BLX_AddTableToDatabaseLog::buildFieldList(logtype,TableName);
for(i=1; i <= conlen(kk);i++)
{
ll = conpeek(kk,i);
_tableid = tableName2id(tableName);
_fieldid = conpeek(ll,1);
info(strfmt("%1 %2", conpeek(ll,1),conpeek(ll,2)));
dblog.logType=logType;
dblog.logTable = _tableId;
dblog.domainId = domain;
dblog.logField =_fieldid;
dblog.insert();
}
}
result:
What am I missing ?
#EDIT with some additional info
Does not work for SalesTable and SalesLine, WMSBillOfLading.
I couldn't add log for SalesTable and SalesLine by using wizard in administration panel, but my colleague somehow did (she has done exactly the same things as me). We also tried to add log to various other tables and we often found out that she could while I could not and vice versa (and sometimes none managed to do it like in case of WMSBillOfLading table).
The inconsistency of this mechanism is what drove me to write this code, which I hoped would solve all the problems.
After doing your setup changes you probably have to call
SysFlushDatabaseLogSetup::main();
in order to flush any caches.
This method is also called in the standard AX code in the form method SysDatabaseLogTableSetup\Methods\close and in the class method SysDatabaseLogWizard\doRun.
I have to do a Form query by code. The datasource table is CustomVendTable(is a custom table).
I open a form and in my init method I get the table caller :
public void init ()
{
VendTable = myVendTableCaller;
myVendTableCaller = element.args().record();
// There is a dialog and get a date by a _Dialog_ and save in a date variable
super();
}
In my data source I have a build a query. The table in my datasource is related whit VendTable. I filter by code my DataSource by myVendTableCaller.RecId and variable date dateByDialog inserted in the opening dialog
My query is this:
public void executeQuery()
{
query q = new Query();
QueryBuildRange qbr;
QueryBuildDataSource qbds ;
QueryRun queryRun;
qbds = q.addDataSource(tableNum(CustomVendTable) );
qbds.addRange(fieldNum(CustomVendTable, ValidFrom)).value(SysQuery::value( strFmt ("<=%1 ", _dateByDialog)) ) ;
qbds.addRange(fieldNum(CustomVendTable, ValidTo)).value(SysQuery::value( strFmt (">=%1 ", _dateByDialog))) ;
qbds.addRange(fieldNum(CustomVendTable, Vendor )).value(SysQuery::value(myVendTableCaller.recId));
queryRun = new QueryRun (q);
CustomVendTable_ds.query(queryRun.query());
super();
}
*For information there is a Table Relation CustomVendTable.Vendor == VendTable.RecId
So, I have some problems! I think not to be able to make a correct query by date. The fields ValidFrom - ValidTo are UTCdatetime type.
1) I have to convert my _dateByDialog in UTC ? How to do ? It' s correct my query date way ?
Considering that the conversion is not impossible, my BIG problem is that by filtering by date, if I have only one record with recid same range and dates I can somehow see it, BUT if there are more record with these same characteristics (If I have 2 record) I don't see nothing! My Form Grid is void!
I have read that you should set the Session date time (I'm talking about this ) to have control over dates.
I still believe that I do not build my Query very well.
Do you have an idea how can I do ?
If you used DateTimeUtil::newDateTime and made new UTCDateTime parameters for the dates?
https://community.dynamics.com/ax/b/alirazatechblog/archive/2012/09/03/date-to-utcdatetime-convertion-dynamics-ax-2012
Maybe you use a table which use date effective. You assume you have to do the selection yourself, this is not true.
Instead call method validTimeStateAsOfDate on the datasource. See this answer for details.
I want to get the RecId selected from lookup method?
In lookup method in StringEditLookup_ZipCode
public void lookup()
{
Query query = new Query();
QueryBuildDataSource queryBuildDataSource;
QueryBuildRange queryBuildRange;
SysTableLookup sysTableLookup = SysTableLookup::newParameters(tableNum(LogisticsAddressZipCode), this);
sysTableLookup.addLookupField(fieldNum(LogisticsAddressZipCode, ZipCode));
sysTableLookup.addLookupField(fieldNum(LogisticsAddressZipCode, City));
sysTableLookup.addSelectionField(fieldNum(LogisticsAddressZipCode, RecId));
queryBuildDataSource = query.addDataSource(tableNum(LogisticsAddressZipCode));
sysTableLookup.parmQuery(query);
sysTableLookup.performFormLookup();
//super();
}
In modified method I would want to get ad read and use the RecId taken.
I want to populate the StringEditLookup_ZipCode with the ZipCode value.
It's possible to take a RecID ?
The LogisticsAddressZipCode Table is's not indexed by ZipCode
for this I need to take the RecID.
There is a way to save in a global variable or somehow recid selected in the lookup method or another point?
Thanks all,
enjoy!
As far as I know this cannot be done with the SysTableLookup Framework. Basically you want your lookup to return two values, the ZipCode and the RecId. But the framework can only return one value.
So instead of the framework you will need to implement your own lookup by creating a new lookup form. From this form you can then retrieve the selected record in the lookup method. Here is some code to give you an idea how the lookup method could look like:
public void lookup()
{
FormRun lookupFormRun;
Args args;
LogisticsAddressZipCode myLookupZipCode;
args = new Args();
args.name(formStr(MyNewLookupForm));
lookupFormRun = classFactory.formRunClass(args);
lookupFormRun.init();
this.performFormLookup(lookupFormRun);
lookupFormRun.wait();
if (lookupFormRun.closedOk())
{
myLookupZipCode= formRun.docCursor();
}
}
From there you can save the RecId of myLookupZipCode to a class variable and then later use it in the modified method.
Also take a look at Lookup form returning more than one value for additional Information.
What you have there is exactly what I would've tried. What happens when you choose a value from the lookup? If that isn't working, I'd try adding RecId as one of the lookupFields. It's probably something where it isn't selecting the field so it's always blank. That's the first thing I would try.
In the query window that pops up, if a user right clicks and chooses "1:n" and selects a table, how can one detect and use that table? I have a good sample job and screenshots that should demonstrate what I'm trying to accomplish.
I wrote this sample job that dumps out the AOT query objects but not the dynamically joined table/range/value.
static void InventSumQuery(Args _args)
{
Query query = new Query(queryStr(InventDimPhys));
QueryRun qr = new QueryRun(query);
QueryBuildRange queryRange;
DictField dictField;
int i, n;
if(qr.prompt())
{
for (n=1; n<=query.dataSourceCount(); n++)
{
for (i=1; i<=query.dataSourceNo(n).rangeCount(); i++)
{
queryRange = query.dataSourceNo(n).range(i);
dictField = new dictField(query.dataSourceNo(n).table(), fieldName2id(query.dataSourceNo(n).table(), queryRange.AOTname()));
info(strFmt("%1.%2", tableId2name(dictField.tableid()), dictField.name()));
}
}
}
info("Done");
}
Of course I figure my own answer out. Query objects are static, and the query form actually just modifies the query when you make the change.
So you need to modify the code above to:
if(qr.prompt())
{
query = qr.query();
This gets the modified query. The advanced querying actually is just a function of the form itself that ultimately modifies the query.
I am using the following code in X++ to get table names:
client server public static container tableNames()
{
tableId tableId;
int tablecounter;
Dictionary dict = new Dictionary();
container tableNamesList;
for (tablecounter=1; tablecounter<=dict.tableCnt(); tablecounter++)
{
tableId = dict.tableCnt2Id(tablecounter);
tableNamesList = conIns(tableNamesList,1,dict.tableName(tableId));
}
return tableNamesList;
}
Business connector code :
tablesList = (AxaptaContainer)Global.ax.
CallStaticClassMethod("Code_Generator", "tableNames");
for (int i = 1; i <= tablesList.Count; i++)
{
tableName = tablesList.get_Item(i).ToString();
tables.Add(tableName);
}
The application hangs for 2 - 3 minutes while fetching data. What could be the cause? Any optimizations?
Rather than use ConIns, use +=, it will be faster
tableNamesList += dict.tableName(tableId);
ConIns has to work out where in the container to place the insert. += just adds it to the end
As mentioned before avoid conIns() when appending elements to a container because it makes a new copy of the container. Use += instead to append in place.
Also, you may want to check for permissions and leave out temporary tables, table maps, and other special cases. Standard Ax has a method to build a table name lookup form that takes these things into account. Check the method Global::pickTable() for details.
You could avoid some calls through the business connector as well and build the entire list in Ax in a similar way and return that in a single function call.
If you are using Dynamics Ax 2012, you could skip the treeNode stuff and use the SysModelElement table to fetch the data and return it immediately as a .Net Array to easy up things on the other side.
public static System.Collections.ArrayList FetchTableNames_ModelElementTables()
{
SysModelElement element;
SysModelElementType elementType;
System.Collections.ArrayList tableNames = new System.Collections.ArrayList();
;
// The SysModelElementType table contains the element types
// and we need the recId for the next selection
select firstonly RecId
from elementType
where elementType.Name == 'Table';
// With the recId of the table element type,
// select all of the elements with that type (hence, select all of the tables)
while select Name
from element
where element.ElementType == elementType.RecId
{
tableNames.Add(element.Name);
}
return tableNames;
}
}
Alright, I have tried a lot of things and in the end, I decided to create a table consisting of all table names. This table will have a Job populating it. I am fetching records from this table.