»

search

Art Of Creation – Dynamics AX Blog

 The everyday life of a Dynamics AX developer

  • Pages

    • AX links
    • Contact
  • Tags

    2009 2012 AIF AOS Application Batch Best practices bookmarks CLR Compiler Configuration Database Datasource Data Types Debug Decisions Dynamics AX Editorscripts Error Event Log Events Forms Fun Google Inheritance Job link Nice-to-haves Odd code Patterns Performance RPC security SQL String synchronize SysOperation System.Diagnostics System.IO TechDays Technical conference Tools Try Catch WinAPI X++
  • Recent Comments

    • Nick Peterson on AX Technical Conference 2011: Session Recordings (Now available)
    • Call a BOF service with a caller record | UnitedTechnos on AX2012: SysOperation part 2: SysOperationServiceController
    • Klaas Deforche on AX Technical Conference 2011: Session Recordings (Now available)
    • Robert on AX Technical Conference 2011: Session Recordings (Now available)
    • How to handle errors in a transaction scope | Sharp Dynamics Magazine on Try Catch and transactions
    • Klaas Deforche on AX2012: SysOperation part 1: Data Contracts and Service Operations
    • Prashanth on AX2012: SysOperation part 1: Data Contracts and Service Operations
    • SysOperationFramework : Field display order - Kenny Saelen’s Microsoft Dynamics AX Blog - AX Technical Blogs - Microsoft Dynamics Community on AX2012: SysOperation introduction
    • Axilicious » SysOperationFramework : Field display order on AX2012: SysOperation introduction
    • Art Of Creation – Dynamics AX Blog » Try Catch example code on Try Catch example code
  • Who's Online

    • 0 Members.
    • 1 Guest.
  • Archives

    • January 2012 (2)
    • December 2011 (1)
    • November 2011 (3)
    • August 2011 (7)
    • April 2011 (1)
    • March 2011 (1)
    • February 2011 (1)
    • January 2011 (2)
    • November 2010 (1)
    • October 2010 (5)
    • August 2010 (2)
    • July 2010 (2)
    • June 2010 (3)
    • May 2010 (4)
    • April 2010 (13)
    • March 2010 (1)
    • February 2010 (8)
    • January 2010 (5)
    • December 2009 (3)
    • November 2009 (2)
    • October 2009 (7)
    • September 2009 (1)
    • August 2009 (3)
    • June 2009 (2)
    • May 2009 (2)
    • April 2009 (7)
  • Ax Dev Twitter

    • Microsoft Dynamics Salary Survey 2012 http://t.co/whcaqA97
    • Session Recordings Technical Conference Nice available here: http://t.co/yyOsKrt0
    • RT @Jvan_veldhuizen: @toincrease #DataMigration in more detail. #MSDynAx #2012 can perform --> 22000+ records per minute, 360+ record ...
    • @Jvan_veldhuizen bedankt om te volgen, ik kijk al uit naar al je blogposts over data migratie :)
    • @fawad343 that's for answering my question a few days back, Thx :)
    • So tell me, AX professionals, how often are you allowed to work from home?
    • RT @msdynamicsax: AX Technical Conference 2011 (Nice, France) wrap-up: http://t.co/vGg7Cn1h #msdynax #erp #AX2012
    • New blog post: AX Technical Conference 2011 Day 1: Information Source http://t.co/RLa5Q2rO
    • New blog post: Data migration: Importing the InventItemGroupForm Table http://t.co/giFAJ4u1
    • New blog post: See you at Technical Conference in Nice (Nov 14-16) http://t.co/moyBB2vZ
  • Microsoft Dynamics Salary Survey 2012

    January 17, 2012 at 21:20  (no comments)

    Hi all!

    Wondering if you’re getting the right salary?
    Nigel Frank is organizing its yearly salary survey for Dynamics professionals.

    You’ll get a copy of the results for free if you take part in the survey, and you can also win some prizes.

    Nigal Frank Salary Survey

    Just click the banner to take part in the survey.

  • AX Technical Conference 2011: Session Recordings (Now available)

    at 11:31  (3 comments)

    The session recordings and slides from the Technical Conference in Nice 2011 are available here:
    https://mbs.microsoft.com/partnersource/newsevents/news/MSDAXTechnicalConf2011Recordings

  • AX Technical Conference 2011: Session Recordings

    December 20, 2011 at 15:05  (no comments)

    Some of you asked if the session recordings from the Technical Conference in Nice were available online, and I’m glad to inform you that they will be available on PartnerSource starting January 4th, 2012.

    Just click this link in early January to view the recordings: https://mbs.microsoft.com/partnersource/training/news/MSDYAX_TechConferenceFall.htm https://mbs.microsoft.com/partnersource/newsevents/news/MSDAXTechnicalConf2011Recordings.

    Some of the sessions were really interesting, so be sure to check it out!

    Update 2012/01/09: The recordings are not online yet. Will keep you updated.
    Update 2012/01/17: Available now!

  • AX Technical Conference 2011 Day 1: Information Source

    November 14, 2011 at 23:16  (2 comments)

    Hi everyone, and greetings from the Microsoft Dynamics AX Technical Conference 2011 in Nice.

    Among other sessions, I saw Arijit Basu’s session about Implementing AX Technical Solution Architecture, and found it pretty inspiring. He demonstrated a lot of tools and methods that will come in very handy and I’m sure I’ll talk about more on this blog in the future. Tools like the Application Analysis Tool in particular look very promising.

    Some of these tools are available through the website that Sri Srinivasan announced: InformationSource (beta).
    You can download these in the “Services” section. There is also an extensive library of Q&A’s, and a lot of documentation (Word and Powerpoint documents, video’s,…) that dive deep into what they call Core Concepts of AX.

    You will need to log in with your PartnerSource or CustomerSource account.
    Be sure to check it out, it’s awesome (really!).

    Also, for those who haven’t seen this yet, a nice screenshot of the POS (Point of Sales) of AX For Retail:
    AX For Retail POS
    It almost makes me feel sad that my company isn’t focusing on the retail sector :).

    I’m looking forward to tomorrow, especially to mfp‘s talk about MorphX and TFS.
    See you then (or there)!

  • Data migration: Importing the InventItemGroupForm Table

    November 8, 2011 at 12:23  (no comments)

    Hi everyone :-).

    I am tasked with doing data migration for a project that is about to go live. One of the things that have to be converted are item groups (InventItemGroup) and their linked dimensions (InventItemGroupForm).

    Importing the InventItemGroup table is easy, but InventItemGroupForm is a little bit trickier.
    There is a job below that shows how to fill in the LedgerDimension field on the InventItemGroupForm table. I’m not saying I completely understand the new dimension framework, but this method worked for me, and I hope it helps you when you have to do the same thing.

    You will notice that you won’t be able to import the InventItemGroupForm table with the excel add-on. You’ll have to use a custom job (or make you own import framework like I did).

    This white paper can help too: Implementing the Account and Financial Dimensions Framework (White paper)

    static void klforDimensionTest(Args _args)
    {
        /* input */

        // the InventItemGroup record we want to set accounts for
        InventItemGroup                     inventItemGroup = InventItemGroup::find("Car Audio");
        // the main account we want to link to our InventItemGroup
        MainAccountNum                      mainAccountNum = "0107";
        // The account type we are setting
        InventAccountType                   inventAccountType = InventAccountType::InventIssue;
        // the dimension group of the account type
        ItemGroupLedgerDimensionGroup       itemGroupLedgerDimensionGroup = ItemGroupLedgerDimensionGroup::Inventory;

        /* declarations */
        Ledger                              ledger;
        MainAccount                         mainAccount;
        DimensionAttributeValueCombination  dimensionAttributeValueCombination;
        DimensionAttributeValue             dimAttributeValue;
        DimensionAttribute                  dimAttribute;
        DimensionStorage                    dimStorage;
        RecId                               hierarchyRecId;

        DimensionStorageSegment             dimensionStorageSegment;
        InventItemGroupForm                 inventItemGroupForm;
        ;

        if(inventItemGroup.RecId == 0)
        {
            throw error("InventItemGroup not found");
        }

        // check if inventItemGroupForm record exists
        select firstOnly forUpdate inventItemGroupForm
        where  inventItemGroupForm.ItemGroupId          == inventItemGroup.ItemGroupId
            && inventItemGroupForm.InventAccountType    == inventAccountType
            && inventItemGroupForm.LedgerDimensionGroup == itemGroupLedgerDimensionGroup;

        if(!inventItemGroupForm)
        {
            // record does not exists, initialize it
            inventItemGroupForm.clear();
            inventItemGroupForm.initValue();
            inventItemGroupForm.ItemGroupId             = inventItemGroup.ItemGroupId;
            inventItemGroupForm.InventAccountType       = inventAccountType;
            inventItemGroupForm.LedgerDimensionGroup    = itemGroupLedgerDimensionGroup;
        }

        // get the ledger record for the current company
        ledger = Ledger::findByLegalEntity(CompanyInfo::current());

        // lookup main account record
        mainAccount = MainAccount::findByMainAccountId(mainAccountNum, false, ledger.ChartOfAccounts);

        if (mainAccount.RecId == 0)
        {
            throw error("MainAccount not found");
        }

        // check if DAVC exists
        select firstOnly dimensionAttributeValueCombination
        where dimensionAttributeValueCombination.MainAccount == mainAccount.RecId
          && dimensionAttributeValueCombination.AccountStructure == 0
          && dimensionAttributeValueCombination.LedgerDimensionType == LedgerDimensionType::DefaultAccount;

        // when the DAVC does not exist, create it
        if(dimensionAttributeValueCombination.RecId == 0)
        {
           // Cache the dimension attribute that represents the Main account
            select firstonly * from dimAttribute where
                dimAttribute.RecId == DimensionAttribute::getMainAccountDimensionAttribute();

            // Lookup the DAV backing the main account instance
            dimAttributeValue = DimensionAttributeValue::findByDimensionAttributeAndEntityInst(dimAttribute.RecId, mainAccount.RecId, false, true);

            // lookup the hierarchy for the default acount
            hierarchyRecId = DimensionHierarchy::getHierarchyIdByHierarchyType(DimensionHierarchyType::DefaultAccount);

            // create new storage
            dimStorage = DimensionStorage::construct();
            // add the default account structure hierarchy
            dimStorage.addAccountStructure(hierarchyRecId);

            // create storage segment for the value
            dimensionStorageSegment = DimensionStorageSegment::construct(mainAccountNum, dimAttributeValue.RecId, dimAttributeValue.HashKey);
            // add the segment to the storage
            dimStorage.setSegment(1, dimensionStorageSegment);

            // save storage as default account and get the record
            dimensionAttributeValueCombination = DimensionAttributeValueCombination::find(dimStorage.saveAsDefaultAccount());
        }

        // set the ledgerdimension reference field
        inventItemGroupForm.LedgerDimension = dimensionAttributeValueCombination.RecId;
        // call modifiedfield voor ledgerdimension field to sync inventPosting
        inventItemGroupForm.modifiedField(fieldNum(InventItemGroupForm, LedgerDimension));

        ttsBegin;

        if(inventItemGroupForm.recid == 0)
        {
            // insert if this is a new record
            inventItemGroupForm.insert();
        }
        else
        {
            // update existing record
            inventItemGroupForm.update();
        }

        ttsCommit;

        info("Done");
    }

    Comments with ideas / insights / improvements are welcome as always.

  • See you at Technical Conference in Nice (Nov 14-16)

    November 7, 2011 at 22:17  (no comments)

    AXTechConference banner

    Hi everyone,

    Together with some colleagues, I will be attending the Technical Conference in Nice next week.
    I’m very exited, as this is a first for me (but hopefully not last).

    I will bring you the news when I get back.
    See you there!

  • AX2012: SysOperation part 3: SysOperationAutomaticUIBuilder

    August 24, 2011 at 12:00  (6 comments)

    Hi everyone.

    This is the third and final part in my series of posts about SysOperation (at least for now). You can find part 1 here and part 2 here.

    I will demonstrate how to override methods on the dialog (in this case modified()), and how to read and set properties of controls.

    For the sake of this demo, we will add a checkbox to the dialog that will allow us to set the enabled property of the date control.
    It should look like this:

    1. Modify the data contract

    We will need to modify the data contract so a checkbox is displayed on the dialog that we can use to enable or disable the date control.

    Add this variable to the class declaration of the KlForCustTesterDataContract class:

        NoYesId     allowModifyDate;

    Then add this method to that same data contract

    [DataMemberAttribute,
    SysOperationLabelAttribute("Enable date control")]
    public NoYesId parmAllowModifyDate(NoYesId _allowModifyDate = allowModifyDate)
    {
        allowModifyDate = _allowModifyDate;
        return allowModifyDate;
    }

    There’s nothing new here, we already did all of this in part 1

    2. Extending SysOperationAutomaticUIBuilder

    We will extend the SysOperationAutomaticUIBuilder class so we can override methods and properties of controls on our dialog.

    First, create a new class, KlForCustTesterUIBuilder:

    class KlForCustTesterUIBuilder extends SysOperationAutomaticUIBuilder
    {
    }

    Things we will need in this class are:

    1. a variable for the date control
    2. a variable for the new checkbox control
    3. our data contract

    So we add these variables to the class declaration:

    class KlForCustTesterUIBuilder extends SysOperationAutomaticUIBuilder
    {
        DialogField     dialogFieldAllowModifyDate;
        DialogField     dialogFieldTransDate;

        KlForCustTesterDataContract klForCustTesterDataContract;
    }

    2.1 Adding the override method

    We will first write the method that will override the modified of the checkbox control. Simply add a new method to the KlForCustTesterUIBuilder class:

    public boolean allowModifyDateModified(FormCheckBoxControl _checkBoxControl)
    {
        ;
        // set enabled or disabled based on checkbox
        dialogFieldTransDate.enabled(any2enum(dialogFieldAllowModifyDate.value()));
        // or alternatively
        // dialogFieldTransDate.enabled(_checkBoxControl.checked());
       
        return true;
    }

    The code above sets the enabled method on the dialogFieldTransDate object based on the value property of dialogFieldAllowModifyDate object, or alternatively, the _checkBoxControl variable.
    As you may remember, we declared the DialogField variables in the class declaration. Of course we will still have to initialize these variables , and that’s what we’ll do when overriding the postBuild method.

    2.2 Overwriting the postBuild method

    Next, we need to override the postBuild method on our KlForCustTesterUIBuilder class. This method will be called after the dialog is created, so it is a good place to put our logic.

    Override the method:

    public void postBuild()
    {
        ;
        super();
    }

    Next, let’s add some code to this method, starting with the code that retrieves the data contract object, which is pretty easy:

    public void postBuild()
    {
        ;
        super();

        // get datacontract
        klForCustTesterDataContract = this.dataContractObject();
    }

    Next, we retrieve the DialogField objects:

    public void postBuild()
    {
        ;
        super();

        // get datacontract
        klForCustTesterDataContract = this.dataContractObject();

        // get dialog fields
        dialogFieldTransDate        = this.bindInfo().getDialogField(KlForCustTesterDataContract, methodstr(KlForCustTesterDataContract, parmTransDate));
        dialogFieldAllowModifyDate  = this.bindInfo().getDialogField(KlForCustTesterDataContract, methodstr(KlForCustTesterDataContract, parmAllowModifyDate));
    }

    In the code above, the bindInfo() method returns an object of type SysOperationUIBindInfo. This contains information about which dialog controls the data members are bound to. By providing a reference to the parmTransDate and parmAllowModifyDate member when calling the getDialogField() method, we get the dialog control that is associated with each member.

    Next, we will register the method we want to override:

    public void postBuild()
    {
        ;
        super();

        // get datacontract
        klForCustTesterDataContract = this.dataContractObject();

        // get dialog fields
        dialogFieldTransDate        = this.bindInfo().getDialogField(KlForCustTesterDataContract, methodstr(KlForCustTesterDataContract, parmTransDate));
        dialogFieldAllowModifyDate  = this.bindInfo().getDialogField(KlForCustTesterDataContract, methodstr(KlForCustTesterDataContract, parmAllowModifyDate));

        // register override methods
        dialogFieldAllowModifyDate.registerOverrideMethod(methodstr(FormCheckBoxControl, modified), methodstr(KlForCustTesterUIBuilder, allowModifyDateModified), this);
    }

    As you can see, we can use the registerOverrideMethod() method to override methods on dialog. This is a huge improvement over overriding methods on dialogs in 2009. We simply point to the method we want to override (FormCheckBoxControl.modified) and the method the needs to be executed (KlForCustTesterUIBuilder.allowModifyDateModified).

    Finally, we initialize the value of the enabled property, and the complete method will look like this:

    public void postBuild()
    {
        ;
        super();

        // get datacontract
        klForCustTesterDataContract = this.dataContractObject();

        // get dialog fields
        dialogFieldTransDate        = this.bindInfo().getDialogField(KlForCustTesterDataContract, methodstr(KlForCustTesterDataContract, parmTransDate));
        dialogFieldAllowModifyDate  = this.bindInfo().getDialogField(KlForCustTesterDataContract, methodstr(KlForCustTesterDataContract, parmAllowModifyDate));

        // register override methods
        dialogFieldAllowModifyDate.registerOverrideMethod(methodstr(FormCheckBoxControl, modified), methodstr(KlForCustTesterUIBuilder, allowModifyDateModified), this);

        // enable/disable transdate based on checkbox
        dialogFieldTransDate.enabled(any2enum(dialogFieldAllowModifyDate.value()));
    }

    3. One more attribute

    Now, we’ve created the UIBuilder class, but we still have to link it to our data contract. That’s what the SysOperationContractProcessingAttribute attribute is for.

    To link the UIBuilder class to the data contract, open the classDeclaration method of the KlForCustTesterDataContract class, and add the SysOperationContractProcessingAttribute:

    [DataContractAttribute,
        SysOperationContractProcessingAttribute(classstr(KlForCustTesterUIBuilder))]
    class KlForCustTesterDataContract
    {
        Name        name;
        TransDate   transDate;
        str         packedQuery;

        NoYesId     allowModifyDate;
    }

    4. Testing!

    But wait (you already know what I’m going to say right), first click the Generate Incremental CIL button to generate CIL.

    Right click the KlForCustTesterServiceController menu item we created on day 2, and choose open. You should see this dialog:

    When you check the checkbox, you should see that the date field is enabled.

    Download the XPO for part 3 here.

    That concludes my blog posts about SysOperation, I hope you like it, and I’m looking forward to your feedback.
    Have fun making batches!

  • AX2012: SysOperation part 2: SysOperationServiceController

    August 23, 2011 at 12:20  (3 comments)

    Hi again.

    In part 1, we created a nice batch using data contracts, service operation and the SysOperationServiceController class.

    Today, we will place our menu item on the Customers form, so we can process the customer that is selected in the grid. It will look like this:

    A thing that is still missing though, is a way to work with the Args that are passed when a menu item is executed. We want to be able to set the query on our data contract before showing the batch dialog. We might also want to have a default value in some fields, for example, the date should be today’s date every time we launch the dialog.
    We can do all of that by extending the SysOperationServiceController class.

    So let’s do that!

    Create a new class that extends SysOperationServiceController

    class KlForCustTesterServiceController extends SysOperationServiceController
    {
    }

    Now let’s create a method that constructs an instance of this class based on the Args object

    public static KlForCustTesterServiceController newFromArgs(Args _args)
    {
        KlForCustTesterServiceController    klForCustTesterServiceController;
        ;

        // create a new instance of the controller
        klForCustTesterServiceController = new KlForCustTesterServiceController();
        // initialize from args
        // one of the things this will do is read the "parameters" property from the menu item
        klForCustTesterServiceController.initializeFromArgs(_args);

        // return a new instance of this controller
        return klForCustTesterServiceController;
    }

    Above is the basic code that should be in your construct method. However, this doesn’t do anything more than the SysOperationServiceController already does, so let’s add the logic that sets the date to today’s date.

    public static KlForCustTesterServiceController newFromArgs(Args _args)
    {
        KlForCustTesterServiceController    klForCustTesterServiceController;
        klForCustTesterDataContract     klForCustTesterDataContract
        ;

        // create a new instance of the controller
        klForCustTesterServiceController = new KlForCustTesterServiceController();
        // initialize from args
        // one of the things this will do is read the "parameters" property from the menu item
        klForCustTesterServiceController.initializeFromArgs(_args);
       
        // get datacontract
        // the string should be the same as the parameter name!
        klForCustTesterDataContract = klForCustTesterServiceController.getDataContractObject('_klForCustTesterDataContract');

        // default current date
        klForCustTesterDataContract.parmTransDate(systemDateGet());

        // return a new instance of this controller
        return klForCustTesterServiceController;
    }

    As you can see, the Data Contract is fetched using the getDataContractObject() method. This will also unpack any values that were packed before, just like a normal RunBase would.
    Setting the date is a simple as setting the parm method on the data contract.

    Now let’s add the logic that creates the correct query by adding a range based on the record in the Args variable.

    public static KlForCustTesterServiceController newFromArgs(Args _args)
    {
        KlForCustTesterServiceController    klForCustTesterServiceController;
        klForCustTesterDataContract         klForCustTesterDataContract;
       
        CustTable   custTable;
        Query       query;
        ;

        // create a new instance of the controller
        klForCustTesterServiceController = new KlForCustTesterServiceController();
        // initialize from args
        // one of the things this will do is read the "parameters" property from the menu item
        klForCustTesterServiceController.initializeFromArgs(_args);
       
        // get datacontract
        // the string should be the same as the parameter name!
        klForCustTesterDataContract = klForCustTesterServiceController.getDataContractObject('_klForCustTesterDataContract');

        // default current date
        klForCustTesterDataContract.parmTransDate(systemDateGet());
       
        // check if the record is of type CustTable
        if(_args && _args.dataset() == tableNum(CustTable))
        {
            // cast record
            custTable = _args.record();

            // create new query
            query = new query(queryStr(KlForCustomers));
            // add range
            query.dataSourceTable(tableNum(CustTable)).addRange(fieldNum(CustTable, AccountNum)).value(queryValue(custTable.AccountNum));

            // set query on datacontract
            klForCustTesterDataContract.setQuery(query);

        }

        // return a new instance of this controller
        return klForCustTesterServiceController;
    }

    There you go. Now we need to make a main method that uses this construct method, and starts the operation:

    public static void main(Args _args)
    {
        KlForCustTesterServiceController klForCustTesterServiceController;
        ;

        klForCustTesterServiceController = KlForCustTesterServiceController::newFromArgs(_args);
        klForCustTesterServiceController.startOperation();
    }

    We also need to create a new menu item. Right click you project – New – Menu Item.
    Enter the following properties:

    ObjectType: Class
    Object: KlForCustTesterServiceController
    Parameters: KlForCustTesterDataService.testCustomer

    Next, add the menu item to the CustTable form:

    Finally before testing, click the Generate Incremental CIL button to generate CIL.

    Right click the CustTable form and click Open. This will open the Customers form. Switch to the grid view (Ctrl + Shift + G), and select a record. Then click the button we just added.

    Note: a bug (or something I do wrong?) I found is that the query values that are displayed on the dialog seem to be “lagging”. The wrong values are shown, but when you click ok, the correct values are used, unless you click the select button.
    This can be fixed by adding this line before returning the controller object:

    KlForCustTesterServiceController.queryChanged("_klForCustTesterDataContract.parmQuery", query);

    This is probably not a nice way to do this, so I’ll look into it, and let you know.

    When you click Ok on the dialog, you should see that only the customer you’ve selected will be processed. You will also see that the date has been set to today’s date, just like we wanted.

    Download the XPO for part 2 here.

  • AX2012: SysOperation part 1: Data Contracts and Service Operations

    August 22, 2011 at 12:00  (3 comments)

    Hi All :-)

    This is part 1 of a series of 3 blog posts where I demonstrate how to create batch classes using the SysOperation classes (Read the introduction here)

    We are going to create a batch that looks like this:

    And we are going to do that without creating a RunBaseBatch class!

    Note: Just to be clear: the batch class won’t actually do anything useful, everything is just for demo purposes.

    Okay, let’s start!

    1. Data Contract

    First, we will create a data contract. The data contract contains data members that represent the fields that will be available on the dialog.

    Let’s create a new class, KlForCustTesterDataContract:

    [DataContractAttribute]
    class KlForCustTesterDataContract
    {
    }

    The string DataContractAttribute indicates that this class is a Data Contract. The square brackets [] indicate that this string is an attribute (You can read more about attributes on MSDN).

    Next, let’s add Data Members to this contract by first declaring them in the class declaration

    [DataContractAttribute]
    class KlForCustTesterDataContract
    {
        Name        name;
        TransDate   transDate;
        str         packedQuery;
    }

    We add a string field, a date field, and a query. We still need to create parm methods for these variables.

    This is just a simple string field, nothing special here except for the DataMemberAttribute attribute that indicates that this method is da data member.

    [DataMemberAttribute]
    public Name parmName(Name _name = name)
    {
        name = _name;
        return name;
    }

    The date field is of type TransDate, so we’ll give it a nicer label by using the SysOperationLabelAttribute attribute to specify the label used.
    Also note that, when two attributes are specified, the are separated by a comma (,).

    [DataMemberAttribute
    ,SysOperationLabelAttribute(literalStr("@SYS11284"))] // today's date
    public TransDate parmTransDate(TransDate _transDate = transDate)
    {
        transDate = _transDate;

        return transDate;
    }

    We will also add a query, so we can loop all customers. As you can see, we can specify the query by using the AifQueryTypeAttribute. The query KlForCustomers is just a query in the AOT with CustTable as a datasource (see xpo file at the end).

    [DataMemberAttribute,
        AifQueryTypeAttribute('_packedQuery', querystr(KlForCustomers))
    ]
    public str parmQuery(str _packedQuery = packedQuery)
    {
        packedQuery = _packedQuery;
        return packedQuery;
    }

    Next, we add two helper methods that are not Data Members, so we can easily set and get the query variable.

    To get the query:

    public Query getQuery()
    {
        return new Query(SysOperationHelper::base64Decode(packedQuery));
    }

    To set the query:

    public void setQuery(Query _query)
    {
        packedQuery = SysOperationHelper::base64Encode(_query.pack());
    }

    2. Service class and operation

    Next, let’s create a service class called KlForCustTesterDataService.

    class KlForCustTesterDataService
    {
    }

    Right click the class, click properties, and set the run on property to server.

    Adding a service operation to this service class is as simple as adding a method. As you can see, the SysEntryPointAttribute attribute indicates that this is a service operation, and our data contract is used as an argument.

    [SysEntryPointAttribute]
    public void testCustomer(KlForCustomerTesterDataContract _klForCustomerTesterDataContract)
    {
    }

    Of course, this method needs some demo functionality, so let’s add that:

    [SysEntryPointAttribute]
    public void testCustomer(KlForCustTesterDataContract _klForCustTesterDataContract)
    {
        QueryRun    queryRun;
        CustTable   custTable;
        ;
       
        // info the name parameter
        info(_klForCustTesterDataContract.parmName());

        // create a new queryrun object
        queryRun = new queryRun(_klForCustTesterDataContract.getQuery());

        // loop all results from the query
        while(queryRun.next())
        {
            custTable = queryRun.get(tableNum(custTable));

            // display the accountnum
            info(custTable.AccountNum);
        }
    }

    3. Service

    Right click your project, and choose New - Service. Give this service the same name as the service class, KlForCustTesterDataService.
    In the properties of this node, set the class property to KlForCustTesterDataService
    Expand the node of your service, then right click on the Operations node.
    Click Add operation and check the add checkbox next to the method we’ve created, and click OK.

    4. Menu item

    All we need to do now is create a menu item so we can start our dialog.
    Do this by right clicking your project – New - Menu item. Name it KlForCustTesterDataService.

    Enter the following properties:
    ObjectType: Class
    Object: SysOperationServiceController
    Parameters: KlForCustTesterDataService.testCustomer

    As you can see, we don’t link directly to our service class, in stead, the class SysOperationServiceController will be used, and will know what service operation to start because it is specified in the parameters property. More about these controller class in part 2 of this series.

    5. Run it!

    But wait, we still have to do one small thing: compile our X++ into the common intermediate language (CIL). Don’t worry, it’s easy. Just click the Generate Incremental CIL button in the toolbar (this might take a while).

    After CIL was generated, right click the menu item and click Open. You should see your batch class. As you can see, all fields from the datacontract are displayed, and a select button was automatically generated so you can modify the query.

    Output when run in batch:

    In part 2, we will take a closer look at the SysOperationServiceController class. See you tomorrow.

    Download the XPO for part 1 here.

  • AX2012: SysOperation introduction

    August 21, 2011 at 17:28  (4 comments)

    Hi everyone!

    Dynamics AX 2012 introduces a new way for creating batches, without using the RunBaseBatch class. The SysOperation* classes are part of the Business Operation Framework, and can be used to create these batches.

    Some documentation is already available on MSDN about the BOF and the SysOpertation classes, but I was still confused when creating such a batch class. That is why in the next 3 days, I will cover the basics on how to create these batches, so you don’t have to figure it all out on your own.

    I hope to get some feedback too, because not everything is crystal clear to me either :-).

    But anyway, this is what you can expect in the next 3 days:

    Day 1 (link)
    A demonstration on how to create Data Contract classes and Service Operations. You will also learn how to use attributes like DataContractAttribute and DataMemberAttribute, and at the end, we will already have a working batch class.

    Day 2 (link)
    We will take a closer look at the SysOperationServiceController class, and will be creating our own controller. This will allow us to set properties on our Data Contract based on Args. This is useful when you batch is started by a button on a form that is linked to a datasource.

    Day 3 (link)
    To make a fully functional batch, we need to be able to overwrite methods on our dialog field like lookup and modified. We also want to be able to change the properties of these fields, like enabling or disabling them. That’s just what the SysOperationAutomaticUIBuilder class is for, and we’ll look into that.

  • Top reasons to follow best practices

    August 16, 2011 at 18:14  (no comments)

    Hi all.

    Here’s a list of af few reasons why I think it is important to fix those best practice errors :-).

    Improved performance
    Many best practices have an influence on performance. For example, it is best practice to set the property CreateRecIdIndex to True on tables with Created/Modified DateTime fields. If you don’t, this will have an adverse effect on the performance of those tables.
    Another example is that if you adhere to the best practices concerning the construction of classes and the use of batch classes, it will be much easier to improve performance by enabling batch multi-threading.

    Improved readability of code
    When all developers follow best practices about formatting of code, the code will be more readable and easier to understand. Avoiding dead code and removing unused variables will also make the code less confusing.

    Developers learn
    There are a lot of handy things you can learn by solving best practice deviations. Adding fields to field groups for example is a great way to avoid customizations to forms. And by figuring out what configuration is needed on a menu item, you learn about what effect configuration keys have on you solution.

    Improved security
    Adding security and configuration keys is vital to good security. When ignoring best practices, an unauthorized user might cause a big mess that you’ll have to clean up.

    Certified for Dynamics AX
    Making sure your solution is best practice deviation free goes a long way towards passing the Software Solution Test.

    Better user experience
    Some best practices have to do with labels, help texts, descriptions for batch task, etc. If you ignore those, it will be more difficult for the user to understand the solution you created.

  • Try Catch and transactions

    August 10, 2011 at 11:57  (1 comment)

    Hi all,

    Exception handling can be quite confusing in Dynamics AX, so I wanted to share some quick facts about try/catch and transactions.

    The general rule is that exceptions are caught in the outer most catch, where the ttslevel is 0. This means that if you put a transaction around a try/catch, your exceptions will not be caught.
    The following two jobs demonstrate that:

    Transaction inside try/catch:

        try
        {
            ttsBegin;
            throw error("an error");
            ttsCommit;
        }
        catch
        {  
            info("error caught");
        }

    Output:

    Error an error
    Info error caught

    Try/catch inside transaction:

        ttsBegin;
        try
        {
            throw error("an error");  
        }
        catch
        {  
            info("error caught");
        }
        ttsCommit;

    Output:

    Error an error

    As you can see, the error is not caught when the transaction is around the try catch.

    However, there are two exceptions to this rule.
    The following code demonstrates that UpdateConflict and DupplicateKeyException can be caught inside a transaction.

        Set             set = new Set(Types::Enum); // a set containing all possible exceptions
        SetEnumerator   se;                         // enumerator used to loop though the set with exception
        Exception       exception;                  // used to cast the value from the set
        boolean         caughtInside;
        ;
       
        // add all exception to a set
        set.add(Exception::Break);
        set.add(Exception::CLRError);
        set.add(Exception::CodeAccessSecurity);
        set.add(Exception::DDEerror);
        set.add(Exception::Deadlock);
        set.add(Exception::DuplicateKeyException);
        set.add(Exception::DuplicateKeyExceptionNotRecovered);
        set.add(Exception::Error);
        set.add(Exception::Info);
        set.add(Exception::Internal);
        set.add(Exception::Numeric);
        set.add(Exception::PassClrObjectAcrossTiers);
        set.add(Exception::Sequence);
        set.add(Exception::Timeout);
        set.add(Exception::UpdateConflict);
        set.add(Exception::UpdateConflictNotRecovered);
        set.add(Exception::Warning);
       
        // create enumerator
        se = set.getEnumerator();
       
        // loop all exceptions
        while(se.moveNext())
        {
            // set flag false
            caughtInside = false;
            // begin outer try catch
            try
            {
                ttsBegin;
                // begin inner try catch
                try
                {
                    // cast exception
                    exception = se.current();
                    // trhow exception
                    throw exception;
                }
                catch
                {
                    // set flag to indicate the exception was caught inside the transaction
                    caughtInside = true;
                   
                    warning(strFmt("%1 can be caught inside transaction", exception));
                    // throw exception again to catch it outside of transaction
                    throw exception;
                }
                ttsCommit;
            }
            catch
            {
                // for once, it's ok to catch everyting :)  
               
                if(caughtInside)
                {
                    warning(strFmt("%1 can alse be caught outside of the transaction", exception));
                }
                else
                {
                    info(strFmt("%1 can only be caught outside of the transaction", exception));
                }
               
            }
        }

    Output:

    Info Info can only be caught outside of the transaction
    Info Warning can only be caught outside of the transaction
    Info Deadlock can only be caught outside of the transaction
    Info Error can only be caught outside of the transaction
    Info Internal can only be caught outside of the transaction
    Info Break can only be caught outside of the transaction
    Info DDEerror can only be caught outside of the transaction
    Info Sequence can only be caught outside of the transaction
    Info Numeric can only be caught outside of the transaction
    Info CLRError can only be caught outside of the transaction
    Info CodeAccessSecurity can only be caught outside of the transaction
    Warning UpdateConflict can be caught inside transaction
    Warning UpdateConflict can alse be caught outside of the transaction

    Info UpdateConflictNotRecovered can only be caught outside of the transaction
    Warning DuplicateKeyException can be caught inside transaction
    Warning DuplicateKeyException can alse be caught outside of the transaction

    Info DuplicateKeyExceptionNotRecovered can only be caught outside of the transaction
    Info Timeout can only be caught outside of the transaction
    Info PassClrObjectAcrossTiers can only be caught outside of the transaction

    It is also noteworthy that, when you enter a catch block, there has been a implicit ttsabort, so the transaction has been rolled back. However this is not true for UpdateConflict and DuplicateKeyException when they were caught inside a transaction.

  • Try Catch example code

    August 9, 2011 at 19:04  (6 comments)

    Hi all!

    The code below contains a try/catch that I use a lot when developing batch jobs, especially multithreaded ones.
    It deals with frequently occurring exceptions that, in some cases, can be easily solved by retrying:

    • Deadlocks
    • Update conflicts
    • Duplicate key conflicts

    Duplicate key conflicts are more rare than update conflicts, but I’ve seen them pop up under heavy load on InventDim records.

        try
        {
            ttsbegin;

            // do stuff here
           
            ttsCommit;
        }
        catch (Exception::Deadlock)
        {
            // retry on deadlock
            retry;
        }
        catch (Exception::UpdateConflict)
        {
            // try to resolve update conflict
            if (appl.ttsLevel() == 0)
            {
                if (xSession::currentRetryCount() >= #RetryNum)
                {
                    throw Exception::UpdateConflictNotRecovered;
                }
                else
                {
                    retry;
                }
            }
            else
            {
                throw Exception::UpdateConflict;
            }
        }
        catch(Exception::DuplicateKeyException)
        {
            // retry in case of an duplicate key conflict
            if (appl.ttsLevel() == 0)
            {
                if (xSession::currentRetryCount() >= #RetryNum)
                {
                    throw Exception::DuplicateKeyExceptionNotRecovered;
                }
                else
                {
                    retry;
                }
            }
            else
            {
                throw Exception::DuplicateKeyException;
            }
        }
  • AIF: There is no service with namespace = ‘http://yournamespace’ and external name = ‘aService’.

    April 27, 2011 at 18:57  (no comments)

    Hi all :-)

    Today, when testing newly deployed AIF web services, this error was thrown:

    There is no service with namespace = ‘http://yournamespace’ and external name = ‘aService’.

    Where ‘http://yournamespace’ is the namespace you specified on the service node in the AOT, and ‘aService’ is the external name of your service.

    The problem is that on the service node of this service in the AOT, there was a ‘/’ (slash) at the end, like this:
    service with slash
    Removing the slash at the end fixes the problem:
    service without slash

    I did a refresh in the Service form and redeployed my services on the IIS, so you might have to do that too.

  • SQLDictionary synchronization problem

    March 22, 2011 at 10:47  (no comments)

    Today, the following message popped up when synchronizing the Data Dictonary (Dutch):

    Kan meerdere records in Table list (SqlDictionary) niet toevoegen. Tabel: 118, 4.
    De SQL-database heeft een fout gegenereerd:

    It roughly translates to:

    Cannot insert multiple records in Table list (SQLDictionary). Tabel 118, 4. The SQL-Database has issued an error:

    Thanks to my colleague Kenny, we quickly figured out that the problem was that the disk was full on the SQL server where the data and log files were stored. SQL server also logged an error in the event viewer on the SQL server saying that the disk was full.

    Lessons learned:

    • Errors in AX don’t always point in the right direction
    • Check the event viewer on your servers
    • Be carefull with low disk space
  • Next Page »

     

Wordpress // Photon theme // Copyright © Klaas Deforche