web
You’re offline. This is a read only version of the page.
close
Skip to main content

Notifications

Announcements

No record found.

Community site session details

Community site session details

Session Id :
Finance | Project Operations, Human Resources, ...
Suggested Answer

SysOperationFramework code enhancement with query

(0) ShareShare
ReportReport
Posted on by 1,552
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);            }        }    }}
 
I have the same question (0)
  • Martin Dráb Profile Picture
    237,970 Most Valuable Professional on at
    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.
  • junior AX Profile Picture
    1,552 on at
    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";    
          
      }
        
    }

     
  • Suggested answer
    Martin Dráb Profile Picture
    237,970 Most Valuable Professional on at
    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
    1,552 on at
    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"; } }

     

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

Responsible AI policies

As AI tools become more common, we’re introducing a Responsible AI Use…

Neeraj Kumar – Community Spotlight

We are honored to recognize Neeraj Kumar as our Community Spotlight honoree for…

Leaderboard > Finance | Project Operations, Human Resources, AX, GP, SL

#1
André Arnaud de Calavon Profile Picture

André Arnaud de Cal... 456 Super User 2025 Season 2

#2
Martin Dráb Profile Picture

Martin Dráb 429 Most Valuable Professional

#3
BillurSamdancioglu Profile Picture

BillurSamdancioglu 239 Most Valuable Professional

Last 30 days Overall leaderboard

Product updates

Dynamics 365 release plans