Find Method Generator AX2012

Hello DAX DEV,

Here’s a tool to automatically create the Find method on tables based on the Primary Unique index, it appears in the context menu of the code editor in Ax 2012.

This is how it works, first, you have to make sure your table has a unique index:
carTable

newMth

Then you create a new method on the table, select all the text and execute the function FindByPrimaryIndex in the Context menu.

Script

This new tool will pop-up dialogs, as the images below, to each unique index in a loop in case there’s more than one until the developer chooses one of the indexes [By pressing “Yes” on the dialog].

If there are no unique indexes, or the user rejects all the possible indexes, the solution will propose to create the find by the RecId field.

1.Finddialog

2.Finddialog2

3.Finddialog3

I’ve generated the three possibilities of Find methods for this scenario and the results are:

find by index CarIdx_UniqueId

/// <summary>
/// Finds the specified record in the <c>CarTable</c> table.
/// </summary>
/// <param name="_carId">
/// The CarId of the <c>CarTable</c> record to find.
/// </param>
/// <param name="_forupdate">
/// A Boolean value that indicates whether to read the record for update; optional.
/// </param>
/// <param name="_concurrencyModel">
/// The table selection ConcurrencyModel; optional.
/// </param>
/// <returns>
/// A record in the <c>CarTable</c> table; otherwise, an empty record.
/// </returns>
// Begin : Axaptahut, DevTools
public static CarTable find(AccountNum _carId, boolean _forupdate = false,
ConcurrencyModel _concurrencyModel = ConcurrencyModel::Auto)
{
CarTable carTable;
carTable.selectForUpdate(_forupdate);
if (_forupdate && _concurrencyModel != ConcurrencyModel::Auto)
{
carTable.concurrencyModel(_concurrencyModel);
}
select firstonly carTable
where carTable.CarId == _carId;
return carTable;
}
// End: Axaptahut, DevTools
view raw Find.CS hosted with ❤ by GitHub

find by index CarIdx_UniqueCombination

/// <summary>
/// Finds the specified record in the <c>CarTable</c> table.
/// </summary>
/// <param name="_carId">
/// The CarId of the <c>CarTable</c> record to find.
/// </param>
/// <param name="_brand">
/// The Brand of the <c>CarTable</c> record to find.
/// </param>
/// <param name="_forupdate">
/// A Boolean value that indicates whether to read the record for update; optional.
/// </param>
/// <param name="_concurrencyModel">
/// The table selection ConcurrencyModel; optional.
/// </param>
/// <returns>
/// A record in the <c>CarTable</c> table; otherwise, an empty record.
/// </returns>
// Begin : Axaptahut, DevTools
public static CarTable find_Combination(AccountNum _carId,
EcoResAttributeTypeName _brand, boolean _forupdate = false,
ConcurrencyModel _concurrencyModel = ConcurrencyModel::Auto)
{
CarTable carTable;
carTable.selectForUpdate(_forupdate);
if (_forupdate && _concurrencyModel != ConcurrencyModel::Auto)
{
carTable.concurrencyModel(_concurrencyModel);
}
select firstonly carTable
where carTable.CarId == _carId
&& carTable.Brand == _brand;
return carTable;
}
// End: Axaptahut, DevTools

find by RecId

/// <summary>
/// Finds the specified record in the <c>CarTable</c> table.
/// </summary>
/// <param name="_recId">
/// The RecId of the <c>CarTable</c> record to find.
/// </param>
/// <param name="_forupdate">
/// A Boolean value that indicates whether to read the record for update; optional.
/// </param>
/// <param name="_concurrencyModel">
/// The table selection ConcurrencyModel; optional.
/// </param>
/// <returns>
/// A record in the <c>CarTable</c> table; otherwise, an empty record.
/// </returns>
// Begin : Axaptahut, DevTools
public static CarTable findRecId(RecId _recId, boolean _forupdate = false,
ConcurrencyModel _concurrencyModel = ConcurrencyModel::Auto)
{
CarTable carTable;
carTable.selectForUpdate(_forupdate);
if (_forupdate && _concurrencyModel != ConcurrencyModel::Auto)
{
carTable.concurrencyModel(_concurrencyModel);
}
select firstonly carTable
where carTable.RecId == _recId;
return carTable;
}
// End: Axaptahut, DevTools
view raw findRecId.cs hosted with ❤ by GitHub

To add this functionality the following method must be placed in the Editor Scripts class.

Editor Scripts Class: Updated Code for AX4 Compatibility

The following code has been recently updated to ensure compatibility with AX4, avoiding reliance on functions introduced in later versions (AX09 or AX12), such as TableId2Name. Instead, it leverages an encapsulated dictTable instance to achieve the same result, retrieving the name property as needed.

This approach guarantees seamless functionality across different AX versions, making it a reliable choice for your Editor Scripts Class.

 

// Begin : Felipe Nogueira, DevTools
public void template_FindByPrimaryIndex(Editor editor)
{
//Macro
#Properties
#define.dialogCaption("DevTools - Find Method generator")
#define.dialogText("Do you wish to generate find based on %1")
#define.findMthName("find")
#define.findRecIdMthName("findRecId")
//Variables
TreeNode aotNode = EditorScripts::getVersionControllableNode(editor);
xppSource xppSource = new xppSource();
TableName tableNameUpper, buffer;
Source whereFunction, xmlParms, methodName;
DialogButton noYesCancelDialogButton;
//Inner Methods
str getFieldEdt(fieldName _fieldName)
{
TreeNode treeNode = aotNode.AOTfirstChild();
dictField curField;
int edtId;
str edtName;
DictType dictType;
;
treeNode = treeNode.AOTfindChild(_fieldName);
curField = new dictField(TableName2Id(TableNameUpper), fieldName2Id(TableName2Id(TableNameUpper),treeNode.AOTname()));
edtId = curField.typeId();
if (edtId)
{
dictType = new DictType(edtId);
edtName = dictType.name();
}
else if (curField.type() == Types::String)
{
edtName = 'str';
}
else if ( curField.type() == Types::Int64)
{
edtName = 'Int64';
}
return edtName;
}
TreeNode runDialog()
{
TreeNode treeNodeIndexes = aotNode.AOTfindChild(literalStr(Indexes));
TreeNode treeNodeCurIdx = treeNodeIndexes.AOTfirstChild();
container conUniqueIdx;
int i;
;
while (treeNodeCurIdx)
{
if (treeNodeCurIdx.AOTgetProperty(literalStr(allowDuplicates)) == literalStr(No))
{
conUniqueIdx += treeNodeCurIdx.AOTname();
}
treeNodeCurIdx = treeNodeCurIdx.AOTnextSibling();
}
conUniqueIdx += #findRecIdMthName;
for (i = 1; i <= conLen(conUniqueIdx); i++)
{
noYesCancelDialogButton = Box::yesNoCancel(strFmt("%1 %2",#dialogText, conPeek(conUniqueIdx,i)), DialogButton::Yes, #dialogCaption);
if (noYesCancelDialogButton == DialogButton::Yes)
{
if (conlen(conUniqueIdx) == i)
{
treeNodeCurIdx = null;
methodName = #findRecIdMthName;
}
else
{
treeNodeCurIdx = treeNodeIndexes.AOTfindChild(conPeek(conUniqueIdx,i));
methodName = #findMthName;
if (treeNodeCurIdx)
{
treeNodeCurIdx = treeNodeCurIdx.AOTfirstChild();
}
}
break;
}
else if (noYesCancelDialogButton == DialogButton::Cancel || i == conLen(conUniqueIdx) )
{
throw error("@SYS107513");
}
}
return treeNodeCurIdx;
}
void addMethodParm(fieldName _parameter, fieldName _fieldName)
{
str aux = @'%1/// <param name="%2">
/// The %3 of the <c>%4</c> record to find.
/// </param>';
;
xmlParms += strFmt(aux, num2char(10), _parameter, _fieldName, tableNameUpper);
}
Source initializeParametersByPrimaryIndex()
{
Source ret,auxStr;
fieldName fieldName, parameter;
TreeNode treeNode = runDialog();
;
if (!treeNode)
{
fieldName = literalStr(RecId);
parameter = "_" + strLwr(subStr(fieldName,1,1)) + subStr(fieldName,2,strLen(fieldName)-1);
addMethodParm(parameter,fieldName);
ret = ret ? ret + strfmt("%1 %2", strFmt(",%1 %2",num2char(10) ,fieldName), parameter)
: strfmt("%1 %2", fieldName, parameter);
whereFunction = whereFunction ? whereFunction + strFmt("%1 &&",num2char(10))
: literalStr(where);
whereFunction += strFmt(" %1.%2 == %3",buffer,fieldName, parameter);
}
while (treeNode)
{
fieldName = treeNode.AOTgetProperty(literalStr(DataField));
parameter = "_" + strLwr(subStr(fieldName,1,1)) + subStr(fieldName,2,strLen(fieldName)-1);
addMethodParm(parameter,fieldName);
ret = ret
? ret + strfmt("%1 %2", strFmt(",%1 %2",num2char(10) ,getFieldEdt(fieldName)), parameter)
: strfmt("%1 %2", getFieldEdt(fieldName), parameter);
whereFunction = whereFunction ? whereFunction + strFmt("%1 &&",num2char(10))
: literalStr(where);
whereFunction += strFmt(" %1.%2 == %3",buffer,fieldName, parameter);
treeNode = treeNode.AOTnextSibling();
}
return ret;
}
Source template()
{
;
return @'/// <summary>
/// Finds the specified record in the <c>%1</c> table.
/// </summary>%6
/// <param name="_forupdate">
/// A Boolean value that indicates whether to read the record for update; optional.
/// </param>
/// <param name="_concurrencyModel">
/// The table selection ConcurrencyModel; optional.
/// </param>
/// <returns>
/// A record in the <c>%1</c> table; otherwise, an empty record.
/// </returns>
public static %1 %5(%3, boolean _forupdate = false,
ConcurrencyModel _concurrencyModel = ConcurrencyModel::Auto)
{
%1 %2;
;
%2.selectForUpdate(_forupdate);
if (_forupdate && _concurrencyModel != ConcurrencyModel::Auto)
{
%2.concurrencyModel(_concurrencyModel);
}
select firstonly %2
%4;
return %2;
}';
}
;
//SourceCode Start
if (strfmt("%1",aotNode.applObjectType()) == #PropertyTable)
{
tableNameUpper = aotNode.AOTname();
buffer = strLwr(subStr(tableNameUpper,1,1)) + subStr(tableNameUpper,2,strLen(tableNameUpper)-1);
editor.insertLines(strFmt(template(),tableNameUpper,buffer,initializeParametersByPrimaryIndex(),whereFunction, methodName, xmlParms));
editor.gotoLine(1);
}
}
// Place holders template Map
/*
%1 - TableName UpperCase
%2 - buffer
%3 - innerMth: parametersList (str separeted by ',')
%4 - whereFunction (Where statement with selected fields)
%5 - find Method Name
%6 - Parms on XML Summary
*/
// End: Felipe Nogueira, DevTools
On Tue, 2 Jul 2024 at 18:03, Felipe Nogueira <felipenog1@gmail.com> wrote:
// Begin : Felipe Nogueira, DevTools
public void FN_FindByPrimaryIndex(Editor editor)
{
//Macro
#Properties
#define.dialogCaption("DevTools - Find Method generator")
#define.dialogText("Do you wish to generate find based on %1")
#define.findMthName("find")
#define.findRecIdMthName("findRecId")
//Variables
TreeNode aotNode = EditorScripts::getVersionControllableNode(editor);
xppSource xppSource = new xppSource();
TableName tableNameUpper, buffer;
Source whereFunction, xmlParms, methodName;
DialogButton noYesCancelDialogButton;
//Inner Methods
str getFieldEdt(fieldName _fieldName)
{
TreeNode treeNode = aotNode.AOTfirstChild();
dictField curField;
int edtId;
str edtName;
DictType dictType;
;
treeNode = treeNode.AOTfindChild(_fieldName);
curField = new dictField(TableName2Id(TableNameUpper), fieldName2Id(TableName2Id(TableNameUpper),treeNode.AOTname()));
edtId = curField.typeId();
if (edtId)
{
dictType = new DictType(edtId);
edtName = dictType.name();
}
return edtName;
}
TreeNode runDialog()
{
TreeNode treeNodeIndexes = aotNode.AOTfindChild(literalStr(Indexes));
TreeNode treeNodeCurIdx = treeNodeIndexes.AOTfirstChild();
container conUniqueIdx;
int i;
;
while (treeNodeCurIdx)
{
if (treeNodeCurIdx.AOTgetProperty(literalStr(allowDuplicates)) == literalStr(No))
{
conUniqueIdx += treeNodeCurIdx.AOTname();
}
treeNodeCurIdx = treeNodeCurIdx.AOTnextSibling();
}
conUniqueIdx += #findRecIdMthName;
for (i = 1; i <= conLen(conUniqueIdx); i++)
{
noYesCancelDialogButton = Box::yesNoCancel(strFmt("%1 %2",#dialogText, conPeek(conUniqueIdx,i)), DialogButton::Yes, #dialogCaption);
if (noYesCancelDialogButton == DialogButton::Yes)
{
if (conlen(conUniqueIdx) == i)
{
treeNodeCurIdx = null;
methodName = #findRecIdMthName;
}
else
{
treeNodeCurIdx = treeNodeIndexes.AOTfindChild(conPeek(conUniqueIdx,i));
methodName = #findMthName;
if (treeNodeCurIdx)
{
treeNodeCurIdx = treeNodeCurIdx.AOTfirstChild();
}
}
break;
}
else if (noYesCancelDialogButton == DialogButton::Cancel || i == conLen(conUniqueIdx) )
{
throw error("@SYS107513");
}
}
return treeNodeCurIdx;
}
void addMethodParm(fieldName _parameter, fieldName _fieldName)
{
str aux = @'%1/// <param name="%2">
/// The %3 of the <c>%4</c> record to find.
/// </param>';
;
xmlParms += strFmt(aux, num2char(10), _parameter, _fieldName, tableNameUpper);
}
Source initializeParametersByPrimaryIndex()
{
Source ret,auxStr;
fieldName fieldName, parameter;
TreeNode treeNode = runDialog();
;
if (!treeNode)
{
fieldName = literalStr(RecId);
parameter = "_" + strLwr(subStr(fieldName,1,1)) + subStr(fieldName,2,strLen(fieldName)-1);
addMethodParm(parameter,fieldName);
ret = ret ? ret + strfmt("%1 %2", strFmt(",%1 %2",num2char(10) ,fieldName), parameter)
: strfmt("%1 %2", fieldName, parameter);
whereFunction = whereFunction ? whereFunction + strFmt("%1 &&",num2char(10))
: literalStr(where);
whereFunction += strFmt(" %1.%2 == %3",buffer,fieldName, parameter);
}
while (treeNode)
{
fieldName = treeNode.AOTgetProperty(literalStr(DataField));
parameter = "_" + strLwr(subStr(fieldName,1,1)) + subStr(fieldName,2,strLen(fieldName)-1);
addMethodParm(parameter,fieldName);
ret = ret
? ret + strfmt("%1 %2", strFmt(",%1 %2",num2char(10) ,getFieldEdt(fieldName)), parameter)
: strfmt("%1 %2", getFieldEdt(fieldName), parameter);
whereFunction = whereFunction ? whereFunction + strFmt("%1 &&",num2char(10))
: literalStr(where);
whereFunction += strFmt(" %1.%2 == %3",buffer,fieldName, parameter);
treeNode = treeNode.AOTnextSibling();
}
return ret;
}
Source template()
{
;
return @'/// <summary>
/// Finds the specified record in the <c>%1</c> table.
/// </summary>%6
/// <param name="_forupdate">
/// A Boolean value that indicates whether to read the record for update; optional.
/// </param>
/// <param name="_concurrencyModel">
/// The table selection ConcurrencyModel; optional.
/// </param>
/// <returns>
/// A record in the <c>%1</c> table; otherwise, an empty record.
/// </returns>
public static %1 %5(%3, boolean _forupdate = false,
ConcurrencyModel _concurrencyModel = ConcurrencyModel::Auto)
{
%1 %2;
;
%2.selectForUpdate(_forupdate);
if (_forupdate && _concurrencyModel != ConcurrencyModel::Auto)
{
%2.concurrencyModel(_concurrencyModel);
}
select firstonly %2
%4;
return %2;
}';
}
;
//SourceCode Start
if (strfmt("%1",aotNode.applObjectType()) == #PropertyTable)
{
tableNameUpper = aotNode.AOTname();
buffer = strLwr(subStr(tableNameUpper,1,1)) + subStr(tableNameUpper,2,strLen(tableNameUpper)-1);
editor.insertLines(strFmt(template(),tableNameUpper,buffer,initializeParametersByPrimaryIndex(),whereFunction, methodName, xmlParms));
editor.gotoLine(1);
}
}
// Place holders template Map
/*
%1 - TableName UpperCase
%2 - buffer
%3 - innerMth: parametersList (str separeted by ',')
%4 - whereFunction (Where statement with selected fields)
%5 - find Method Name
%6 - Parms on XML Summary
*/
// End: Felipe Nogueira, DevTools

 

Hope it might be helpful,
Thanks,
Felipe Nogueira