Skip to main content

Notifications

Finance | Project Operations, Human Resources, ...
Suggested answer

SysOperationFramework code enhancement with query

(0) ShareShare
ReportReport
Posted on by 1,550
Hi,

I want to create a sysOperationFramework class -- it could be run from a menu item, or from a button clicked on form.
If it runs from a menu item, it will take data from a query and insert into certain tables based on an enum.
if it runs from a button, i will run it based on selected records from grid, then i will need to add an extra range to the AOT query.

Here's what I did... can someone help me improve the code in a better way??
1. how can i enhance, contact, controller and service class code? in terms of args i passed and trying to add a query range
2. how can i enhance code called from form
  
[DataContractAttribute] class Contract1{   str callerName;    str packedQuery;    str ContainerStr;        public str parmCallerName(str _callerName = callerName)     {   callerName = _callerName;        return callerName;    }         public str parmContainerStr(str _containerStr = containerStr)    {         containerStr = _containerStr;        return containerStr;    }         [ DataMemberAttribute,     AifQueryTypeAttribute('_packedQuery', queryStr(Query1))]         public str parmQuery(str _packedQuery = packedQuery)    {           packedQuery = _packedQuery;        return packedQuery;    }     public void setQuery(Query _query)    {         packedQuery = SysOperationHelper :: base64Encode(_query.pack());    }         public Query getQuery()     {         return new Query(SysOperationHelper :: base64Decode(packedQuery));    }    }    
class Controller1 extends SysOperationServiceController{    Public ClassDescription defaultCaption()    {        return /caption/;    }    protected void new()    {        super(classStr(Service1), methodStr(Service1, run), SysOperationExecutionMode::Synchronous);    }    public static Controller1 construct()    {        Controller1 controller;        controller = new Controller1();              return controller;    }    public static void main(Args _args)    {        Controller1 controller;        Contract1   contract;                        controller = Controller1::construct();        controller.parmArgs(_args);        contract = controller.getDataContractObject();        contract.parmCallerName(_args.callerName());        contract.parmContainerstr(_args.parm());        controller.startOperation();    }}
class Service1 extends SysOperationServiceBase{    Table1                                             table1,parmTable1;    Table2                                             table2;    System.ArgumentException                           ex;    Query                                              query;    QueryRun                                           queryRun;    str                                                callerName;    QueryBuildDataSource                               qbds;    QuerybuildRange                                    qbr;    Str                                                parmContainerStr;            public void run(Contract1 _contract)    {               this.getParamFromContract(_contract);        this.setQueryRanges(); // Depend on caller               queryRun = new QueryRun(query);        while(queryRun.next())        {                        table2 = queryRun.get(tableNum(table2));            table1 = queryRun.get(tableNum(table1));            if(table1)             {                                try                {                                        ttsbegin;                    //logic                    ttscommit;                }                catch (Exception::Error)                {                    ttsabort;                }            }        }                }    public void getParamFromContract(Contract1 _contract)    {        query = _contract.getQuery();        callerName =_contract.parmCallerName();        parmContainerStr = _contract.parmContainerStr();    }    public void setQueryRanges()    {        if(callerName == menuItemDisplayStr(Table1) && parmContainerStr)        {            qbds = query.dataSourceName('Table1');            qbr  = qbds.addRange(fieldNum(Table1, RecId));            qbr.value(parmContainerStr);        }    }    private static ClassDescription description()    {        return /description/;    }}


and here's the code when calling menu item from Form based on button clicked
[Form]public class Table1Form extends FormRun{    [Control(/MenuFunctionButton/)]    class Controller1    {        public void clicked()        {            Table1                                  table1;            container                               container1;            Args                                    args = new Args();            MultiSelectionHelper                    selectionHelper = MultiSelectionHelper::construct();                        selectionHelper.parmDataSource(Table1_ds);             table1 = selectionHelper.getFirst();            while (table1)            {                    container1 += table1.RecId;                    table1 = selectionHelper.getNext();            }                        if(conLen(container1))            {                args.caller(element);                args.parm(con2str(container1));                new MenuFunction(menuitemActionstr('Controller1'), MenuItemType::Action).run(args);            }        }    }}
 
  • junior AX Profile Picture
    junior AX 1,550 on at
    SysOperationFramework code enhancement with query
    Hi Martin,
    • What do you mean by "Note that I wouldn't do it directly in the static main() method. There I would merely pass _args to parmArgs() and do the processing in an instance method" ??
      Do you mean i should call controller in clicked method? and why would i need parmArgs? please look at x method i made in controller and let me know if this is what you meant
       
    • As u can see, i set query range in Service class -- what I noticed is that if tick batch processing to true, the parmCallerName and parmList1 are empty, therefore logic doesn't work. However, if i run it directly without batch, values are set and it works as expected.
      Shall i move setting query range from service class to controller? and maybe do it in X method and in this case I won't need to add the RecId to the list or need the list itself in the first place?
     
    class Controller1 extends SysOperationServiceController 
    {
      Public ClassDescription defaultCaption() 
      {
        return "caption";
      }
      
      protected void new() 
      {
        super(classStr(Service1), methodStr(Service1, run), SysOperationExecutionMode::Synchronous);
      }
      
      public static Controller1 construct() 
      {
        Controller1 controller;
        controller = new Controller1();
        return controller;
      }
      
      public static void main(Args _args) {
        controller1 controller;
        Contract1 contract;
    
    
        controller = Controller1::construct();
        controller.parmArgs(_args);
        controller.parmLoadFromSysLastValue(false); controller.x(); contract = controller.getDataContractObject(); contract.parmCallerName(_args.callerName()); contract.parmList1(_args.parm()); controller.startOperation(); }

        public void x() // not sure what to call this method??
        {
            if(args && args.callerName() == formStr(Table1Form))
            {
                if(args.dataset() == tableNum(Table1))
                {
                    FormDataSource table1DS = FormDataUtil::getFormDataSource(args.record());

                    Table1 table1;
                    List                                    selectedList      = new List(Types::Int64);
                    MultiSelectionHelper                    selectionHelper = MultiSelectionHelper::construct();
                
                    selectionHelper.parmDataSource(table1DS );
                    table1= selectionHelper.getFirst();

                    while(table1)
                    {
                        selectedList.addEnd(table1.RecId);
                        table1 = selectionHelper.getNext();
                    }

                    if(selectedList.elements())
                    {
                        args.parmObject(selectedList);
                    }
                }
            }
        } }
     
    [DataContractAttribute] 
    class Contract1
    {   str  callerName;
        str  packedQuery;
        List list1; 
        
        public str parmCallerName(str _callerName = callerName) 
        {   callerName = _callerName;
            return callerName;
        } 
        
        public List parmList1(List _list1= list1)
        {
            list1= _list1;
            return list1;
        }
        
        [ DataMemberAttribute, 
        AifQueryTypeAttribute('_packedQuery', queryStr(Query1))] 
        
        public str parmQuery(str _packedQuery = packedQuery)
        {   
            packedQuery = _packedQuery;
            return packedQuery;
        } 
    
        public void setQuery(Query _query)
        { 
            packedQuery = SysOperationHelper :: base64Encode(_query.pack());
        } 
        
        public Query getQuery() 
        { 
            return new Query(SysOperationHelper :: base64Decode(packedQuery));
        }
        
    }
    class Service1 extends SysOperationServiceBase 
    {
      Table1     table1, parmTable1;
      Table2     table2;
      Query      query;
      QueryRun   queryRun;
      str        callerName;

      ListEnumerator listEnumerator;
    List       parmList1 = new List(Types::Int64); QueryBuildDataSource qbds; QuerybuildRange qbr; List parmList1 = new List(Types::Int64); public void run(Contract1 _contract) { this.getParamFromContract(_contract); this.setQueryRanges(); queryRun = new QueryRun(query); while(queryRun.next()) { table2 = queryRun.get(tableNum(table2)); table1 = queryRun.get(tableNum(table1)); if(table1) { try { ttsbegin; //logic ttscommit; } catch (Exception::Error) { ttsabort; } } } } public void getParamFromContract(Contract1 _contract) { query = _contract.getQuery(); callerName =_contract.parmCallerName(); parmList1 = _contract.parmList1(); } public void setQueryRanges() { if(callerName == menuItemDisplayStr(Table1) && parmList1.elements()) {
      listEnumerator = parmProcessingQueueList.getEnumerator();
            if(listEnumerator)
            {
      while (listEnumerator.moveNext())
               {               qbds = query.dataSourceTable(tableNum(Table1));
                  qbr  = qbds.addRange(fieldNum(Table1, RecId));
                  qbr.value(queryvalue(listEnumerator.current()));
    }
    } } } private static ClassDescription description() {

    ​​​​​​​ return "description"; } }

     
  • Suggested answer
    Martin Dráb Profile Picture
    Martin Dráb 230,853 Most Valuable Professional on at
    SysOperationFramework code enhancement with query
    Select the right value of Data Source property of the menu item button. Then you'll receive the table buffer in controller's main() method in _args parameter (namely _args.record()). Use FormDataUtil::getFormDataSource() to get a data source object from the buffer and use the logic you currently have in clicked().
     
    Note that I wouldn't do it directly in the static main() method. There I would merely pass _args to parmArgs() and do the processing in an instance method.
  • junior AX Profile Picture
    junior AX 1,550 on at
    SysOperationFramework code enhancement with query
    Hi Martin,

    I have a menu item that runs directly from the controller -- so for this one, the business logic is in the controller.
    However, for the other one. I have a button that when it's clicked i need to pass selected records then call the action menu item, if i'm not going to do it in the clicked method then how i'm going to pass selected records from controller? (cause u said to remove business logic)
    and can you please explain what do you mean by this "Then I would throw away the hard-coded menu item name and simply check if a record was provided in Args."

    I'll try to post the code again. As editing the code from the post is still appearing in a wrong way
     
    [Form] public class Table1Form extends FormRun {
      [Control("MenuFunctionButton")] 
      class Controller1
      {
        public void clicked() 
        {
          Table1 table1;
          container container1;
          Args args = new Args();
          MultiSelectionHelper selectionHelper = MultiSelectionHelper::construct();
          selectionHelper.parmDataSource(Table1_ds);
          table1 = selectionHelper.getFirst();
          while (table1) 
          {
            container1 += table1.RecId;
            table1 = selectionHelper.getNext();
          }
          if (conLen(container1))
          {
            args.caller(element);
            args.parm(con2str(container1));
            new MenuFunction(menuitemActionstr(Controller1), MenuItemType::Action).run(args);
          }
        }
      }
    }


    Here's the controller -- how can i enhance the main method to support both running it from the menu item directly, or by passing selected records?
    class Controller1 extends SysOperationServiceController 
    {
      Public ClassDescription defaultCaption() 
      {
        return "caption";
      }
      
      protected void new() 
      {
        super(classStr(Service1), methodStr(Service1, run), SysOperationExecutionMode::Synchronous);
      }
      
      public static Controller1 construct() 
      {
        Controller1 controller;
        controller = new Controller1();
        return controller;
      }
      
      public static void main(Args _args) {
        Controller1 controller;
        Contract1 contract;
        controller = Controller1::construct();
        controller.parmArgs(_args);
        contract = controller.getDataContractObject();
        contract.parmCallerName(_args.callerName());
        contract.parmContainerStr(_args.parm());
        controller.startOperation();
      }
    }

    Here's the contract class
    ​
    [DataContractAttribute] 
    class Contract1
    {   str callerName;
        str packedQuery;
        str ContainerStr;
        
        public str parmCallerName(str _callerName = callerName) 
        {   callerName = _callerName;
            return callerName;
        } 
        
        public str parmContainerStr(str _containerStr = containerStr)
        { 
            containerStr = _containerStr;
            return containerStr;
        } 
        
        [ DataMemberAttribute, 
        AifQueryTypeAttribute('_packedQuery', queryStr(Query1))] 
        
        public str parmQuery(str _packedQuery = packedQuery)
        {   
            packedQuery = _packedQuery;
            return packedQuery;
        } 
    
        public void setQuery(Query _query)
        { 
            packedQuery = SysOperationHelper :: base64Encode(_query.pack());
        } 
        
        public Query getQuery() 
        { 
            return new Query(SysOperationHelper :: base64Decode(packedQuery));
        }
        
    }
        
    
    ​

    Here's the service class:
     
    class Service1 extends SysOperationServiceBase 
    {
      Table1 table1, parmTable1;
      Table2 table2;
      System.ArgumentException ex;
      Query query;
      QueryRun queryRun;
      str callerName;
      QueryBuildDataSource qbds;
      QuerybuildRange qbr;
      Str parmContainerStr;
      public void run(Contract1 _contract)
      {
          this.getParamFromContract(_contract);
          this.setQueryRanges(); 
          queryRun = new QueryRun(query); 
          while(queryRun.next())  
          {                       
              table2 = queryRun.get(tableNum(table2));
              table1 = queryRun.get(tableNum(table1));  
              if(table1)            
              {                               
                  try                
                  {                                   
                    ttsbegin; 
                    
                      //logic
                      
                    ttscommit;         
                  }          
                  catch (Exception::Error)      
                  {                   
                      ttsabort;         
                   }            
                  
              }        
              
          }                
          
      }    
      public void getParamFromContract(Contract1 _contract)
      {       
          query = _contract.getQuery(); 
          callerName =_contract.parmCallerName();     
          parmContainerStr = _contract.parmContainerStr(); 
          
          
      }    
      public void setQueryRanges()   
      {       
        if(callerName == menuItemDisplayStr(Table1) && parmContainerStr)        
        {          
            qbds = query.dataSourceName('Table1');    
            qbr  = qbds.addRange(fieldNum(Table1, RecId));     
            qbr.value(parmContainerStr);        
                
        }    
          
      }    
      private static ClassDescription description() 
      {        return "description";    
          
      }
        
    }

     
  • Martin Dráb Profile Picture
    Martin Dráb 230,853 Most Valuable Professional on at
    SysOperationFramework code enhancement with query
    Your code is almost unreadable - please post it once more in the correct way.
     
    Personally, I would throw away business logic from clicked() method and move it to the controller. It's better for encapsulation and reusability. Then I would throw away the hard-coded menu item name and simply check if a record was provided in Args.
     
    I prefer adding a range for each value, rather than concatenating values with a separator. One of the benefits is that you don't exceed the allowed value length if there are many selected values.
     
    Throw away the call of ttsAbort - the transaction will be aborted automatically when an exception is thrown.
     
    Throw away the declaration of System.ArgumentException - you never use it for anything. You should be able to find such problems by yourself; it's also detected by the BP check.

Under review

Thank you for your reply! To ensure a great experience for everyone, your content is awaiting approval by our Community Managers. Please check back later.

Helpful resources

Quick Links

Announcing Our 2025 Season 1 Super Users!

A new season of Super Users has arrived, and we are so grateful for the daily…

Vahid Ghafarpour – Community Spotlight

We are excited to recognize Vahid Ghafarpour as our February 2025 Community…

Congratulations to the January Top 10 leaders!

Check out the January community rock stars...

Leaderboard

#1
André Arnaud de Calavon Profile Picture

André Arnaud de Cal... 291,996 Super User 2025 Season 1

#2
Martin Dráb Profile Picture

Martin Dráb 230,853 Most Valuable Professional

#3
nmaenpaa Profile Picture

nmaenpaa 101,156

Leaderboard

Product updates

Dynamics 365 release plans