Skip to content
Archive of posts tagged ax2012

Compile backwards

Today I noticed that Type hierarchy browser in AX 2012 R3 allows you to compile types backwards:

HierarchyBrowserCompile

Context menu in AOT supports only forward compilation.

It’s possible that the feature is there for some time and it just took me long time to notice it, because I normally don’t compile code from Type hierarchy browser.

Merging concurrent changes in AX with TFS

Suppose you’re an AX developer working in an AX instance shared by few other developers. You want to do some changes that would likely cause issues to your colleagues until they’re finished, therefore you’ve created a separate instance and work there. To simplify the example, assume that you’ve changed only a single method.

If nobody changed it in the meantime, it’s all good – you can simply import the method. But if somebody changed it, importing your code is a bad thing, because you would overwrite somebody else’s code. It doesn’t mean just that a feature would disappear; you would probably remove only a part of a feature and the behavior of the remaining application would be unpredictable.

What you have to do is to merge your changes with the current version of the application. In the simplest case, you want to keep all changes, therefore you’ll add your changes to what’s already there. Sometimes you want to replace a previous change (because, for example, somebody made a quick fix before your more robust solution gets ready). Sometimes you’ll have to implement completely new code to allow concurrent changes to work together. In the worst case, the changes are not compatible and can’t be merged together at all. (This usually reveals a problem with project management.)

My experience indicate that the main problem is not that developers fail to merge code correctly – they often don’t do it at all and overwrite previous changes. Unfortunately you can’t avoid merging if you want to support parallel development on the same objects, nevertheless you CAN ensure that people don’t skip merging.

The answer is obviously a version control system. If you take an object from a repository, modify it and you’re going to commit it, the version control system checks whether there aren’t already conflicting changes. If there are any, you’re not allowed to continue without resolving conflicts.

Let’s take a look at a concrete example with a few pictures. I’m going to simulate two developers working on AX2012 with Team Foundation Server. Each developer has his own AX instance pointing to the same team project and branch.

To make it easier, I used a single instance and I simulated the other developer by adding some change directly in Visual Studio, using a different TFS workspace. I took an existing, empty job and checked it out to both workspaces. In Visual Studio, I typed in // Code added in Visual Studio and checked the change in. In Dynamics AX, my change was // Code added in AX. When committing changes, TFS detects a conflict and shows a dialog where you can see and merge changes:

MergeDialog

This is a very simple example, nevertheless you can see that is shows the changes I’m checking in, the changes done by others in newer versions and the resulting merged code (on the bottom). If there are several conflicts, I can resolve them one by one or simply accept one of versions of the files: ResolveAll

I can change the code in the bottom pane as needed. For example, I decided to merge comments by creating a completely new one:

ManuallyMerged

The code inserted to TFS is the result of this merge, of course:

ViewChanges

You may not like that the dialog uses the format of .xpo files and not a visual tree as in AX, but that’s how AX code and metadata are saved to source control systems and it’s usually better for merging anyway. By the way, .NET code is stored and compared exactly in the same way – as text files; it just has prettier formats. Don’t be too afraid of the .xpo format – it’s simple and you’ll need it in a few other cases, such as when reviewing changes line by line with annotate:

Annotate

It’s also worth mentioning that you may want to have a separate branch for each developer and merge code on branch-level rather than changeset-level. An advantage for merging is that it happens independently on AX, therefore you can use latest tools, regardless what version of TFS is integrated to AX. On the other hand, you usually want to integrate code together (and find any issues caused by that) as soon as possible, which is an argument against too many branches.

Data contract serialization from X++

You can decorate classes in AX2012 with DataContractAttribute in much the same way as in .NET with System.Runtime.Serialization.DataContractAttribute. The same is true also for DataMemberAttribute.

This is a simple data contract class in X++:

[DataContractAttribute]
class ItemContract
{
    str name;
 
    [DataMemberAttribute("Name")]
    public str parmName(str _name = name)
    {
        name = _name;
        return name;
    }
}

In .NET, you can serialize data contracts to XML (and deserialize from XML) with DataContractSerializer. What if you want to do the same with data contracts in X++?

I’m not aware of any such serializer in X++. You could write one, but it wouldn’t be a five-minute job. But there is a way how to actually use the .NET serializer.

As you surely know, AX2012 is able to compile X++ types to the Common Intermediate Language (CIL). Other “.NET” languages are compiled to CIL as well, which allows all of them to work together seamlessly. You can save a class written in a different language to a variable, call its methods, inherit from it and so on. (Don’t worry too much about differences between .NET, CLR, CIL, CLI etc. You just need to know that it’s all related to the same thing, usually called .NET).

When AX2012 generates CIL, .NET types for X++ classes and tables are created in Dynamics.Ax.Application assembly. For example, PriceDisc class is turned to Dynamics.Ax.Application.PriceDisc. If a piece of X++ code is executed in a CLR session, CIL types from Dynamics.Ax.Application are transparently used instead of original X++ types.

However, classes decorated by DataContractAttribute won’t be decorated by Dynamics.Ax.Application.DataContractAttribute in CIL. The X++ attribute is actually replaced by System.Runtime.Serialization.DataContractAttribute, therefore you’ll get a proper .NET data contract that can be serialized by DataContractSerializer. That’s likely what happens somewhere in AX kernel when you pass a data contract with parameters from AX to a report server, for instance.

By the way, I really like this approach. People from Microsoft managed to provide an easy way to define data contracts in X++ and still benefit from everything related to data contracts in .NET. Now it looks like an obvious solution, but who would have thought it about before?

Nevertheless what if we want to use the serializer from our own X++ code? First of all, let me introduce a C# method that will do all the serialization. All we have to do is to pass a data contract and we’ll get back the object serialized to XML.

using System;
using System.Runtime.Serialization;
using System.IO;
using System.Xml;
 
namespace Demo
{
    public class ContractSerializer
    {
        public static string Serialize(Object o)
        {
            Type type = o.GetType();
 
            if (!Attribute.IsDefined(type, typeof(DataContractAttribute)))
            {
                throw new ArgumentException(String.Format("{0} is not a data contract", type.FullName));
            }
 
            using (var stringWriter = new StringWriter())
            using (var xmlWriter = new XmlTextWriter(stringWriter) { Formatting = Formatting.Indented })
            {
                new DataContractSerializer(type).WriteObject(xmlWriter, o);
                return stringWriter.GetStringBuilder().ToString();
            }
        }
    }
}

You can put the class to a class library, add it to AOT and deploy it to both client and server.

Then we need to ensure ourselves that our X++ code will run in CIL. We’ll utilize SysOperation framework for that purpose:

class SerializationDemo extends SysOperationServiceController
{
    public static void main(Args args)
    {
        SerializationDemo demo = new SerializationDemo();
 
        demo.parmClassName(classStr(SerializationDemo));
        demo.parmMethodName(methodStr(SerializationDemo, serializeToInfolog));
        demo.parmExecutionMode(SysOperationExecutionMode::Synchronous);
 
        demo.run();
    }
 
    [SysEntryPointAttribute]
    public void serializeToInfolog()
    {
        if (!xSession::isCLRSession())
        {
            throw error("Must run CIL!");
        }
        // Here we'll do the job
    }
}

Do not omit the verification that we’re really in a .NET session. If the code ran in X++, it wouldn’t work, because we can’t simply pass an X++ object to a .NET method. If the code don’t execute in CIL, go to Tools > Options > Development and tick Execute business operations in CIL. Also, don’t forget to generate CIL.

If we’re in a .NET session, we can simply create an instance of a data contract class and send it to our method. Let’s create some data:

private DirPersonInfoData getPersonData()
{
    DirPersonInfoData person = new DirPersonInfoData();
 
    person.parmPersonName("John Doe");
    person.parmPersonPrimaryEmail("john(at)doe.com");
    person.parmPersonUserId("jdoe");
 
    return person;
}

Unfortunately, if you try to pass it directly to the serialization method:

DirPersonInfoData person = this.getPersonData();
Demo.ContractSerializer::Serialize(person);

it will fail with the error “Microsoft.Dynamics.AX.ManagedInterop.Object is not a data contract”.

The problem here is that the generated code uses a proxy class, which is not what we need, because proxy classes are not generated with attributes. Let’s tell AX that we’re working with a .NET object:

DirPersonInfoData person = this.getPersonData();
System.Object clrPerson = CLRInterop::getObjectForAnyType(person);
;
Demo.ContractSerializer::Serialize(clrPerson);

Now let’s put it together and finalize serializeToInfolog() method by adding exception handling and output to infolog:

[SysEntryPointAttribute]
public void serializeToInfolog()
{
    DirPersonInfoData person = this.getPersonData();
    System.Object clrPerson;
    System.Exception ex;
    str serialized;
 
    if (!xSession::isCLRSession())
    {
        throw error("Must run CIL!");
    }
 
    try
    {
        clrPerson = CLRInterop::getObjectForAnyType(person);
        serialized = Demo.ContractSerializer::Serialize(clrPerson);
    }
    catch (Exception::CLRError)
    {
        ex = CLRInterop::getLastException();
        throw error(ex.ToString());
    }
 
    info(serialized);
}

And this is how DirPersonInfoData will be serialized:

<DirPersonInfoData xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
                   xmlns="http://schemas.datacontract.org/2004/07/Dynamics.Ax.Application">
    <parmPersonCommunicatorSignIn />
    <parmPersonName>John Doe</parmPersonName>
    <parmPersonPrimaryEmail>john(at)doe.com</parmPersonPrimaryEmail>
    <parmPersonUserId>jdoe</parmPersonUserId>
    <parmWorkerTitle />
</DirPersonInfoData>

The fact that the same X++ code can be executed once in a native AX session and once in a CLR session is very powerful and you usually don’t have to care about how it works. Of course, it may be sometimes confusing. For example, you need to know how your code is executed in a given moment to use the right debugger.

Here I showed how to leverage it even more. The solution works because we can define a type in X++ and use it later as a CLR type. But it also depend on the fact how the CIL generator deals with DataContractAttribute, which is something you can only find by looking into CIL. A CIL decompiler comes handy if you want to play with these things.

Creating sales orders via AIF in AX2012

I was asked for an example how to create a sales order through AIF document services (SalesSalesOrderService). Here is my minimalist variant in C# (for AX2012 and demo data):

var line = new AxdEntity_SalesLine()
{
    ItemId = "D0001",
    SalesQty = 42,
    SalesUnit = "ea"
};
 
var order = new AxdEntity_SalesTable()
{
    CustAccount = "US-003",
    PurchOrderFormNum = "xyz",
    ReceiptDateRequested = DateTime.Now.Date,
    SalesLine = new AxdEntity_SalesLine[] { line }
};
 
var orderList   = new AxdEntity_SalesTable[] { order };
var callContext = new CallContext() { Company = "USMF" };
var client      = new SalesOrderServiceClient();
 
try
{
    client.create(callContext, orderList);
    client.Close();
}
catch
{
    client.Abort();
    throw;
}

It doesn’t have to work for you out of the box (for example, you may have additional fields required by the contract), but it should give you an idea how it looks like, without adding unnecessary complexity.

If you’re not familiar with AIF document services, you may want to look at AX 2012 Documentation Resources for AIF and Services.

A required device isn’t connected

Microsoft recently released Dynamics AX 2012 R2 Solution Demo Package V4, i.e. a new version of the virtual machine with Dynamics AX and related components installed and configured. The version of AX there is 2012 R2 CU7.

I successfully downloaded, unpacked and imported VM A (30 GB), but when I tried to boot it, I got the following error:

0xc000000e A required device isn’t connected or can’t be accessed

I tried few changes in settings and even different versions of Hyper-V to no avail. Finally I tried to fix the master boot record and other boot data – and that helped!

Here are the instructions:

  1. Mount a Windows Server 2012 installation disc or a disc image (in VM settings). Ensure yourself that CD is the first boot device (section BIOS in Settings).
  2. Start the VM and confirm the boot from CD.
  3. Select/confirm a language, keyboard layout and so on.
  4. Don’t click Install now – choose Repair your computer instead.
  5. Click Troubleshoot and than Command Prompt
  6. Call these commands:
    bootrec /fixmbr
    bootrec /fixboot
    bootrec /rebuildbcd
  7. Confirm the Window installation to be added to boot.
  8. Reboot the VM and let it boot from disk.