Quantcast
Channel: Goshoom.NET Dev Blog
Viewing all 203 articles
Browse latest View live

Layer change in MorphX VCS

$
0
0

I already posted an article about the same thing some time ago: Moving a model to another layer. The difference is that it was about Team Foundation Server, but this time I needed to move a model to another layer while using MorphX VCS.

MorphX VCS doesn’t support synchronization, therefore my original approach can’t be used. What you need is to move all objects to the target layer by export/import and update data in tables that form MorphX VCS repository. The following code will help you with the latter step:

str oldLayer = 'usr';
str newLayer = 'cus';
str 10 searchPattern = strFmt(@'\\%1\\*', oldLayer);
 
SysVersionControlMorphXItemTable item;
SysVersionControlMorphXRevisionTable rev;
SysVersionControlMorphXLockTable lock;
 
str replaceLayer(str path)
{
    return strFmt(@'\%1\%2', newLayer, subStr(path, 6, strLen(path)));
}
 
ttsBegin;
 
while select forUpdate item
    where item.ItemPath like searchPattern
{
    item.ItemPath = replaceLayer(item.ItemPath);
    item.update();
}
 
while select forUpdate rev
    where rev.ItemPath like searchPattern
{
    rev.ItemPath = replaceLayer(rev.ItemPath);        
    rev.update();
}
 
while select forUpdate lock
    where lock.ItemPath like searchPattern
{
    lock.ItemPath = replaceLayer(lock.ItemPath);        
    lock.update();
}
 
ttsCommit;

Parsing XML files with .NET Interop

$
0
0

Today I want to show how to download XML files from internet, validate, parse and use them in Dynamics AX 2012. It’s also yet another example of how we can simplify implementation by using appropriate .NET APIs and seamlessly integrate such a code with Dynamics AX.

Our data source will be daily exchange rates provided by European Central Bank as a simple XML file: www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml. In the end, we’ll have the data in an X++ object and we’ll display them in infolog:

You can download the complete solution here – it contains a single .xpo file (for AX2012). I’m going to discuss most of the code below, but you’ll have to look to the source code for details.

The very first step is to create a C# class library and add it to AOT. I’ve called it EcbExchRates and set the namespace to Goshoom.Finance.

Then create a new class – DailyExchangeRateService – and add few constants that will be needed in a minute.

private const string DailyRatesUri = "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml";
private const string GesmesNamespace = "http://www.gesmes.org/xml/2002-08-01";
private const string EcbNamespace = "http://www.ecb.int/vocabulary/2002-08-01/eurofxref";

The content of the XML file must be downloaded somehow – that’s responsibility of the DownloadData() method.

private string DownloadData()
{
    using (System.Net.WebClient webClient = new System.Net.WebClient())
    {
        return webClient.DownloadString(DailyRatesUri);
    }
}

We need to parse the string returned by DownloadData(). Fortunately, .NET offers several APIs helping with that – let’s use LINQ to XML in this case. First load the XML string to XDocument (from System.Xml.Linq namespace) and store it in an instance variable:

XDocument xmlData;
 
private void Load()
{
    xmlData = XDocument.Load(new StringReader(this.DownloadData()));
}

One of many advantage of XML is the support for validation against XSD schemas – we’re doing that in the Validate() method. Schemas are stored as embedded resources (you can see them in the downloadable source code).

private void Validate()
{
    XmlSchemaSet schemas = new XmlSchemaSet();
 
    schemas.Add(GesmesNamespace, XmlReader.Create(new StringReader(Resources.GesmesSchema)));
    schemas.Add(EcbNamespace, XmlReader.Create(new StringReader(Resources.EcbSchema)));
 
    xmlData.Validate(schemas, null);
}

The last step is to parse the XML to an object. But before we can do that, we have to define a class for the object. We’ll create a simple X++ class holding the date and exchange rates:

class DayExchRates
{
    date    effectiveDate;
    Map     rates; // Currency code -> exchange rate
 
    public void addRate(str _currency, real _rate)
    {
        rates.insert(_currency, _rate);
    }
 
    …more code…
}

Drag the class from Application Explorer to your C# project so the .NET proxy gets generated.

Now we can extract data from the XML – first we need to find the right element. Except a minor complication caused by namespaces, the implementation is trivial:

XNamespace gesmesNs = GesmesNamespace;
XNamespace ecbNs = EcbNamespace;
XElement ratesRoot = xmlData.Element(gesmesNs + "Envelope").Element(ecbNs + "Cube").Element(ecbNs + "Cube");

We create an instance of our X++ class and get the date from the time attribute:

DayExchRates rates = new DayExchRates();
rates.parmEffectiveDate((DateTime)ratesRoot.Attribute("time"));

Then we iterate through nodes with rates, get currency code and exchange rate from attributes and add them to the X++ class.

foreach (XElement rateNode in ratesRoot.Descendants())
{
    rates.addRate(
        rateNode.Attribute("currency").Value,
        decimal.Parse(rateNode.Attribute("rate").Value));
}

Encapsulate parsing in Parse() method and put it all together in a public method called GetRates():

public DayExchRates GetRates()
{
    this.Load();
    this.Validate();
    return this.Parse();
}

Display properties of the Visual Studio project, set Deploy to Client to Yes (because our test X++ code will run on client), deploy the project and we’re done in Visual Studio.

Create a job in Dynamics AX and put the following code into it (just change names, if you used different ones):

Goshoom.Finance.DailyExchangeRateService service = new Goshoom.Finance.DailyExchangeRateService();
DayExchRates dailyRates = dailyRates = service.GetRates();

The code is very straightforward, but there is one thing that deserves attention. What the GetRates() method returns is not the managed proxy class – .NET Interop is smart enough to use replace the proxy by the original X++ type, therefore we can directly assign the value from the .NET method to a variable of our X++ type. That may not sound like a big difference, but in fact it greatly simplifies things.

To complete our task, let’s display exchange rates in infolog. This is just a normal X++ code:

MapEnumerator enumerator = dailyRates.getRates().getEnumerator();
 
while (enumerator.moveNext())
{
    info(strFmt("%1: %2", enumerator.currentKey(), num2str(enumerator.currentValue(), -1, 4, -1, -1)));
}

You probably could do the same thing in pure X++ (I would just have to investigate how to validate against two target namespaces in X++), but you would get much more code to write, read and maintain, which would negatively affect your productivity. This difference would be even bigger if the example was not so simple.

Dynamics AX/X++ simply can’t – and shouldn’t – duplicate all libraries and APIs available in .NET, but that doesn’t mean that we have to do without them. .NET Interop allows us to combine the best from both worlds (AX and .NET) and each version of AX makes it a bit easier.

Invoke-WebRequest

$
0
0

Powershell 3.0 introduced Invoke-WebRequest cmdlet which offers a lot of options regarding HTTP requests. Rather then repeating the documentation and other sources, I’m going to show a simple example – the script below finds some specific links in the given web page and download them. It uses Invoke-WebRequest for both getting (and parsing) the web page and downloading files.

$url = 'http://blogs.msdn.com/b/briankel/archive/2011/09/16/visual-studio-11-application-lifecycle-management-virtual-machine-and-hands-on-labs-demo-scripts.aspx'
cd c:\temp\
 
$site = Invoke-WebRequest -Uri $url
$links = $site.Links | where href -like "http://download.microsoft.com*" | select -expand href
$links | % { Invoke-WebRequest -Uri $_ -OutFile (Split-Path $_ -Leaf) }

Hodge-podge – 06/2013

$
0
0

AxErd

Microsoft published several entity-relationship diagrams (ERD) regarding AX2012 R2 (+ some related data such as foreign keys). Direct link here: AxErd.

Dynamics AX 2012 and TFS 2012

Dynamics AX 2012 (CU5/R2 CU1) is officially compatible with Team Foundation Server 2012 (source).

Code maps debugger integration

Code maps inVisual Studiu 2012 (CU1+) serve for visualization of code. Version CU2 added the integration to debugger – all you have to do is to activate Code maps and you immediately get visual idea of where you are in the code. See this short video. Unfortunately this is included only in the Ultimate version.

Microsoft Virtual Academy

If you don’t know the virtual akademy, I recommend taking at least a look. Although it’s quite focused on administrators, developers find also a lot of interesting stuff. I can recommend recent jumpstarts about Windows 8 development and Team Foundation Server, for instance.

Conference: Agile Prague 2013

Agile Prague conference is going to be as late as in September, but you can get a discount if you register soon enough. Details on agileprague.com/registration.htm.

Convergence of Skype and Lync

From now on, Skype users can send IM and call Lync users – and vice verse. More details here:

Twitter

I’ve opened an account on Twitter – I’m going to try it for few months and then I’ll see how much I find it useful. I’m there as @goshoom – feel free to follow me.

Daily or not daily

$
0
0

I’m now a part of a newly formed team and team procedures are still being defined. One of the questions discussed was how often would we hold team meetings. Suggestions varied significantly – from daily to less than weekly. In this post I’m going to think aloud (think visibly?) about few things that may be good to consider.

We should always ask whether a meeting brings a higher value than the time it costs. I’m sure that everybody sometimes finds himself in utterly useless meetings – and it’s absolutely right approach trying to eliminate this waste of time.

But just getting rid of all meetings is not the answer – if a team should work together, then people need to meet each other, understand what to do, how they can help the rest of the team and so on. Instead of refusing all meetings as valueless, we must make some of them valuable. If we waive often (daily) meetings because people consider them “waste of time”, we’re not only losing opportunity to work closely as team and identify problems and opportunities early, but it often means resigning to any improvement at all – we would have meetings less often, but they would remain equally useless.

The previous paragraph assumes one thing that is not automatically true for every “team” – that the individuals really work as a team, therefore they are interested in what others do, have a reason to synchronize with them and plan together what’s needed to do. If everybody has his own work and don’t collaborate with others, there is no reason to meet – such a group may be called a team, but it doesn’t work as team, so it won’t benefit much from collective meetings.

Agile methodologies such as Extreme Programming or Scrum use daily meetings heavily – but it’s a common mistake to hold daily status meetings, call them “Scrums” and automatically expect better results. That’s just a receipt how to waste a bit more time every day.

Agile methodologies concentrate a lot on building teams that work together to achieve a common goal and on removing communication barriers. People are often impelled to work closely together – to plan work together, to estimate together, to pair program and so on. That also helps everybody to know what the team is working on, because the whole team analyzed, decomposed, estimated and accepted every single piece of work. Because individual tasks are small enough, there is measurable progress (or measurable lack of progress) every day, therefore it really makes sense to ask what was achieved yesterday and what will be done today.

Sometimes people have their “own” items to implement and don’t care about what others do. Sometimes people are willing to collaborate with others but they don’t know what others are exactly doing and don’t get this information even in the meeting. Sometimes people repeat for many days that they work still on the same task. Sometimes the whole purpose is to give Project Manager feeling that he’s managing anything.

It may be better to get rid of such meetings. But even better is to change the way how we work.

Talks about AX in the Czech republic

$
0
0

This post is primarily intended for the Czech mutation of this blog, but I’m not going to discriminate against anybody, right?

Although I already work abroad for few years, I come to the Czech republic several times a year and I’m still in touch with many former colleagues. Sometimes I get questions about Dynamics AX and requests to show in better details something what I mentioned on the blog or so.

Because many things may be interesting for more people, it occurred to me that I could discuss some of them publicly. And if somebody else had the same tendency, we could make one more step forward and, say, organize some regular meetings where everybody could present their ideas, discuss problems and so on.

From me, you could expect mainly talks about programming, integration of AX and .NET and application lifecycle management. Concretely, I recently got few questions about using Powershell to administer Dynamics AX and about Team Foundation Server – these would likely be my first topics.

Because most potential participants live in Prague, and because I always pass through Prague, I would do it there. We could consider other even places in future, but it would be a pointless debate in the moment.

If you would like to participate on something like that and/or you have any related idea, please let me know (you can leave a comment here or send me an e-mail to goshoom at goshoom dot net. What will happen next depends on your interest.

Dynamics AX Solutions Excellence Certification Program

$
0
0

You may have already heard about Microsoft Dynamics AX Solutions Excellence Certification Program – a new level of certifications for Dynamics AX. Microsoft already did changes to the Partner Program before to make getting high-level competencies a bit harder. This is one more step in that direction – the new requirements are not trivial.

Individuals will be able to get the Solutions Excellence Certification in three tracks:

  • Development
  • Technical
  • Functional

Details about the functional track are not available yet, but something was released about the other two.

Both development and technical track will contain two exams – one knowledge exam and one lab exam. Furthermore, several other exams are required before going to Solutions Excellence Certification (they’re the same both tracks):

It’s probably not a combination of exams that many people have these days – we’ll have to make some effort. But that’s the point, isn’t it?

It’s important to understand that these certifications are not just for few people to get one more certification logo. Microsoft Partners will actually have to employ people with these certification, if they want to get (or keep) an advanced competency level. MPN: Gold Competency will require one employee with Technical and one with Development Excellence certification and Certified Software Advisors will need even two certified employees for each track.

With some level of simplification, we can say that the new exams will be available next year (2014) and the new competency requirements will be enforced from 2015.

For details, please visit Introducing the Microsoft Dynamics AX Solutions Excellence Certification Program page on Partnersource.

AX2012 R2 Demo V3.0

$
0
0

Microsoft recently released new virtual machines with preconfigured AX2012 R2 (and a lot of other stuff): Microsoft Dynamics AX 2012 R2 Solution Demo Package V3.0.

I’ve already installed the main machine and I would like to share two warnings:

  1. If you run Hyper-V 2.o (Windows Server 2008), you can’t import the virtual machine – you have to create a new one and use the provided virtual disk. See Setup Instructions document for details.
  2. It requires a lot of resources. I had no troubles on my machine with the previous version, but this one immediately took all resources, including complete capacity of 4 processors (2.4 GHz). Fortunately, killing SharePoint helped me to get to some normal numbers.
    It’s also obvious that 6 GB of RAM (that successfully I used for the previous version) aren’t sufficient anymore – Microsoft recommends 16 GB.

Exception handling with X++ and .NET Interop

$
0
0

A recent discussion on the community forum about exception messages prompted me to test the given case thoroughly and I think the results are worth sharing. The post also mentions something what I already found before – that error handling can be tricky in code which uses .NET Interop and which can be executed as both X++ and CIL.

Let’s introduce the problem. We have an X++ class method (AX2012) that throws an exception:

public class XppClass
{
    public static void run()
    {
        throw error("It failed!");
    }
}

It’s used in a .NET library (through a proxy class):

public class CSharpClass
{
    public static void Run()
    {
        try
        {
            XppClass.run();
        }
        catch (Exception ex)
        {
            throw;
        }
    }
}

The .NET class is called from X++ again:

public static void main(Args args)
{
    try
    {
        CSharpClass::Run();
    }
    catch (Exception::CLRError)
    {
        info(AifUtil::getClrErrorMessage());
    }
    catch (Exception::Error)
    {
        info("Exception::Error caught");
    }
}

We’ll use exactly the same code in different contexts – and we’ll see that the behavior won’t be the same.

Client-bound X++

If we run the code on client, we can catch the exception in X++ as usual (i.e. “Exception::Error caught” will be shown in infolog). In the .NET class, the exception object has type Microsoft.Dynamics.AX.ManagedInterop.ErrorException and the infolog message is contained in the Message property:

ErrorException

That’s the behavior described in Proxies and Exception Mapping [AX 2012].

Server-bound X++

Now let’s run the same code on the server tier. The exception can be caught in X++ code as before. But the situation in the .NET class is different – although the type is still ErrorException, the Message property contains text “Exception of type ‘Microsoft.Dynamics.AX.ManagedInterop.ErrorException’ was thrown” instead of the actual infolog message (and the InnerException is empty).

CIL session

When running the code in a CIL session (such as in batch), the situation is even more different. The exception caught in the .NET class is System.Reflection.TargetInvocationException (with a generic message “Exception has been thrown by the target of an invocation”). Its inner exception is Microsoft.Dynamics.Ax.Xpp.ErrorException with an equally useless message “Exception of type ‘Microsoft.Dynamics.Ax.Xpp.ErrorException’ was thrown”.

The exception in X++ is TargetInvocationException too and its inner exception is the exception we saw in .NET. Because it’s a .NET exception, it won’t be caught by catch (Exception::Error) (which is, by the way, translated to catch (ErrorException) in CIL), we have to use catch (Exception::CLRError). That’s a really important observation – when you run such an X++ code in CIL, your error handling code may not work was expected.

Conclusion

As you can see, combining .NET Interop from X++ and .NET Interop to X++ may easily cause you a headache, because the behavior depends on execution tier and whether the code runs as X++ or CIL. In many cases, you also won’t get details about X++ exceptions.

You may want to take that into account when designing your solutions – maybe you don’t need to use .NET Interop from X++ and .NET Interop to X++ in the same time.

Year in number sequence (AX2012)

$
0
0

I’ve noticed a lot of questions in discussion forums about how to add year to a number sequence in AX2012, e.g. to generate IDs such as 2013-xxxxx and 2014-xxxxx, that would automatically use the current year. Some people understand that number sequence scopes should allow that, but they don’t know how.

Fortunately it’s not only possible but it’s also quite simple. Rather then describing it theoretically or repeating the documentation, let’s walk through a simple end-to-end example.

Data type

First of all, we need a new data type that will be associated with the number sequence. Create a new string data type, name it DocuRevisionId, set its Label to Revision ID and StringSize to 20. The type represents a hypothetical document revision ID.

Then we need to register the type with an application module – it’ll be document management in our case. Open loadModule() method of NumberSeqModuleDocument class and add the following code at the end (I intentionally omitted everything that’s not necessary for our example):

datatype.parmDatatypeId(extendedTypeNum(DocuRevisionId));
datatype.addParameterType(NumberSeqParameterType::DataArea, true, false);
datatype.addParameterType(NumberSeqParameterType::FiscalCalendarPeriod, true, false);
this.create(datatype);

Here we’re creating a new number sequence definition for DocuRevisionId data type. We’re also defining two scope parameter types: company and fiscal calendar period. It’s very important to declare supported scope parameters – you can’t use number sequence scopes without them.

The definition has no effect unless the module is loaded. You can manage that by running the following code in a job:

new NumberSeqModuleDocument().load();

When it’s done, you can go to Organization administration > Common > Number sequences > Segment configuration and verify that our new type is there and it has both expected segments.

SegmentConfiguration

Fiscal periods

Before configuring number sequences, let’s create fiscal periods that will be used in number sequence scopes. We’ll create periods covering whole years, but you could use anything what makes sense for you.

Open General ledger > Setup > Fiscal calendars and create a new calendar – I’ll call it MyYear. It should cover the whole year:

NewFiscalCalendar

We’ll need two years for our example, therefore press the New accounting year button and create one more year (for 2014) :

AddFiscalYear

Display generated periods and set a short name for the operating period. The short name will be used automatically in number sequences, if provided. You can also rename the period in this form.

Configure fiscal period

Number sequences

Now we’ll create two number sequences – for years 2013 and 2014. They don’t have to have the same number sequence code, but they can, because the unique identifier is not the code alone, but in conjunction with a scope. We’ll use the same code because it will be needed for one additional scenario.

Go to Organization administration > Common > Number sequences > Number sequences and create a new sequence. Set fields as follows:

Number sequence code Revision
Name Revision 2013
Scope Company and Fiscal calendar period

When you choose a scope, you get additional fields – the sequence will be used only for values that you specify there. Pick your company and the operating period for year 2013 in our new calendar.

By default, the sequence includes segments for all scope parameters, therefore the format is something like DAT2013-######. Because we don’t need the company there, delete the Company segment. The value of Fiscal calendar period segment has been initialized from sequence’s short name and you can change it if needed.

We also need (at least in the typical case) to associate the sequence with a data type. Add a new item on the Reference fast tab, choose Document management area and you’ll get just a single option for Reference: Revision ID. That’s because Revision ID the only type in the selected module that supports Company and Fiscal calendar period.

The result should look like this: NewSequence

Now do the same thing once more, just use 2014 instead of 2013.

Using sequences

Everything is ready by now, we just need to have some code that will find the right number sequence and obtain a generated number.

Look at the following implementation. First of all, we find the fiscal period for a given date. Then we use it (together with the current company) to create a scope object. Finally, we find the number sequence for the given data type and scope and get a new number as usual.

date        effectiveDate = mkDate(25,05,2013);
RefRecId    calendarRecId = FiscalCalendar::findByCalendarId("MyYear").RecId;
RefRecId    periodRecId   = FiscalCalendars::findPeriodByPeriodCodeDate(calendarRecId, effectiveDate).RecId;
 
NumberSeqScope scope = NumberSeqScopeFactory::createDataAreaFiscalCalendarPeriodScope(
    curext(),
    periodRecId);
 
NumberSequenceReference seqRef = NumberSeqReference::findReference(extendedTypeNum(DocuRevisionId), scope);
 
NumberSeq::newGetNum(seqRef).num();

You should get a number like 2013-000001. If you change the year to 2014, you’ll get 2014-000001, exactly as intended. If you change the year to 2015, you’ll get an error, because no such sequence exists.

There is one more approach – it’s not normally recommended, but it’s good to know about it. We could have omitted the reference to a data type and refer to number sequences just by a common sequence code. These sequences have the same code, but they differ in scope:

SequencesReady

This source code is similar to the previous one; we just find a number sequence by its code:

date effectiveDate = mkDate(25,05,2013);
RefRecId calendarRecId = FiscalCalendar::findByCalendarId("MyYear").RecId;
RefRecId periodRecId = FiscalCalendars::findPeriodByPeriodCodeDate(calendarRecId, effectiveDate).RecId;
 
NumberSeqScope scope = NumberSeqScopeFactory::createDataAreaFiscalCalendarPeriodScope(
    curext(),
    periodRecId);
 
info(NumberSeq::newGetNumFromCode("Revision", scope).num());

Conclusion

Number sequence scopes extends abilities of number sequences in quite a straightforward way. Nevertheless if people don’t understand them properly, they often have wrong expectations. For example, they want to use Fiscal calendar period segment for a data type that doesn’t support such a scope parameter. Because the parameter is not supported, there is no code that would provide a specific value for it (for example, AX doesn’t know which date and which calendar to use).

My intention was to show all components in a single place – I hope it will help people to understand how these parts fit together.

AxBuild.exe

$
0
0

Just a quick one to bring your attention to something what you may have missed.

Microsoft released a command-line tool for Dynamics AX R2 CU 7 called AxBuild. It’s capable of compiling X++ code directly on AOS and doing it in several threads in the same some time, which has a huge effect on performance. Microsoft even achieved 92% (!) improvement in compilation time (in a carefully built environment).

One more advantage is that it’ll be easier to get response from a running compilation (that’s a trouble particularly with compilations executed by automated scripts). Unfortunately AxBuild doesn’t cover compilation to CIL, but hopefully we’ll get that too, at some point.

I haven’t had a chance yet to play with it, but Tommy Skaue did, so check out his blog post You got to love AXBuild Compiler Tool.

Microsoft Virtual Academy Jump Starts

$
0
0

Microsoft Virtual Academy offers an amazing number of Jump Starts next two weeks:

Fortunately they will be recorded, because I’ll be lucky if I managed to watch at least something.

Printing reports from code in AX2012

$
0
0

How to print a report from X++ and change print settings in AX2012?

I have a concrete example for you – it should give a good idea how to do that even if your situation is a little bit different.

SrsReportRunController          controller = new SrsReportRunController();
SysUserLicenseCountRDPContract  rdpContract = new SysUserLicenseCountRDPContract();
SRSPrintDestinationSettings     settings;
 
// Define report and report design to use
controller.parmReportName(ssrsReportStr(SysUserLicenseCountReport, Report));
// Use execution mode appropriate to your situation
controller.parmExecutionMode(SysOperationExecutionMode::ScheduledBatch);
// Suppress report dialog
controller.parmShowDialog(false);
 
// Explicitly provide all required parameters
rdpContract.parmReportStateDate(systemDateGet());
controller.parmReportContract().parmRdpContract(rdpContract);
 
// Change print settings as needed
settings = controller.parmReportContract().parmPrintSettings();
settings.printMediumType(SRSPrintMediumType::File);
settings.fileFormat(SRSReportFileFormat::Excel);
settings.fileName(@'\\share\UserLicenseCount.xlsx');
 
// Execute the report
controller.startOperation();

Lines for multiple headers

$
0
0

The goal of today’s excercise is to create a form with two grids (containing headers and corresponding lines). The trick is that if you select multiple headers, the form will display lines for all of them.

The following form uses sales orders. Notice that selected orders are 1, 3 and 4 and displayed lines also belong to orders 1, 3 and 4:

Form

I’ve created two implementations: one for AX2012 and another for AX2009. They’re slightly different – I’m going to describe AX2012 first and then to mention changes done for AX2009. You can find a download link for both at the end.

The form has two data sources (SalesTable and SalesLine), which are not linked together. Filtering and query execution are not handled by AX; they’re explicitly triggered from selectionChanged() method on SalesTable data source.

For each selected header, a new range is added to SalesLine data source:

Ranges

You could use just a single range and create one long value by concatenating order IDs together, separated by space (e.g. “000001, 000002″), nevertheless it has a huge problem: the supported length of range value isn’t unlimited (I even managed to shoot down AX2012 client by using a sufficiently long value). Having several shorter values is also beneficial when displaying ranges in a grid (as in the previous picture).

The implementation itself is very straightforward; you shouldn’t have any problems to understand what’s going on:

public class FormRun extends ObjectRun
{
    MultiSelectionHelper headerSelection;
    QueryBuildDataSource lineQueryDs;
}
 
public void init()
{
    super();
 
    // Initialize MultiSelectionHelper for later use
    headerSelection = MultiSelectionHelper::construct();
    headerSelection.parmDatasource(salesTable_ds);
 
    // For convenient access to lines' QueryBuildDataSource
    // (in AX2012, you could use salesLine_ds.queryBuildDataSource())
    lineQueryDs = salesLine_ds.query().dataSourceNo(1);
}
 
public void selectionChanged() // SalesTable datasource
{
    super();
    SalesLine_ds.executeQuery();
}
 
public void executeQuery() // SalesLine datasource
{
    lineQueryDs.clearRanges();
    this.filterBySelectedHeaders();
 
    super();
}
 
void filterBySelectedHeaders() // SalesLine datasource
{
    SalesTable st = headerSelection.getFirst();
 
    while (st.RecId)
    {
        // Add one range for each selected SalesId
        lineQueryDs.addRange(fieldNum(SalesLine, SalesId)).value(queryValue(st.SalesId));
        st = headerSelection.getNext();
    }
}

In AX2009, we don’t have selectionChanged() method, therefore I combined markChanged() method and a delayed link between data sources, which calls lines’ executeQuery() on header selection. I just had to remove the dynamic link, otherwise AX would show lines for just a single header.

You can download both versions here: LinesForMultipleHeaders.zip.

Size matters

$
0
0

X++ normally doesn’t distinguish letter case – you can call warning(“x”) or WaRnInG(“x”) and both will do the same (although your colleagues may not be as forgiving as the X++ compiler). Nevertheless the same doesn’t apply to .NET Interop from X++ – although you can use System.Math::Cos(0), for example, you’ll get a compilation error if you try to call System.Math::cos(0).

There are even situations when a change in casing results in a call of a different method. Look at the following piece of code – it causes a .NET exception, catches it, converts it to a string and sends it to infolog:

try
{
    System.IO.File::Open('no such file', System.IO.FileMode::Open);
}
catch (Exception::CLRError)
{
    error(CLRInterop::getLastException().ToString());
}

The output should be something like: “System.Reflection.TargetInvocationException: Exception has been thrown by the target of invocation. —> System.IO.FileNotFoundException: Could not find file ‘C:\Windows\system32\no such file.’ (…)”

Now change ToString() to tostring(). The output will be “Class CLRObject”.

What happened? What we get from CLRInterop::getLastException() is an instance of CLRObject (X++) class, which represents a .NET object (FileNotFoundException in this case). When we call ToString() method, AX is smart enough to recognize that FileNotFoundException contains such a method and calls it. But if we call tostring(), AX can’t use the method on FileNotFoundException due to case sensitivity. But there is toString() method on the CLRObject class itself (inherited from Object) that can be called without issues. Therefore we’re not dealing with a single method returning different results, what’s called are methods on completely different objects.

It may be a bit confusing but it makes sense.


A required device isn’t connected

$
0
0

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 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 you 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.

A user session cannot be created

$
0
0

I installed AX2012 R2 CU7 with the demo data downloaded from Microsoft Dynamics AX 2012 Solution Demos on PartnerSource. When I attempted to start the AX client, it threw the following error:

A user session on the server could not be created. Try to start the client again. If the problem continues, contact the Microsoft Dynamics AX administrator.

I also found the following message in the Event Log:

Unable to connect to an Application Object Server. A company name or server name may not be specified correctly in the client configuration, or the server specified may not be available.

I checked the setup and everything was all right. I also looked on internet, just to find that few other people ran into the same error – but I didn’t find any solution.

I thought that “user session on the server could not be created” might have referred to missing permissions for execution of CREATEUSERSESSIONS stored procedure but it wasn’t the case either.

Then I used the SQL Server Profiler to obtain statements sent to database – as expected, CREATEUSERSESSIONS was called. I copied the command (with all parameters) and executed it by myself. And it failed! The error was:

Procedure or function CREATEUSERSESSIONS has too many arguments specified.

It was easy then to find out that the extra argument was a partition ID. Partitions were introduced in AX2012 R2 and the database obviously was from a pre-R2 environment.

There are two data set on Microsoft Dynamics AX 2012 Solution Demos:

  • Demo Data (used to restore VM-A v3 to original demo data state)
  • Demo Data (used to restore VM-A v4 to original demo data state)

Accidentally, I downloaded v3 without noticing there is another version available. When I downloaded and installed v4, everything started to work. I thought that v3 version was built upon AX2012 R2 too, however it doesn’t seem to be the case with the database.

The lesson learned isn’t only that I should be more careful about what I’m installing. The way how I managed to identify the problem is important as well and might help in other cases in future.

Powershell management cmdlets

$
0
0

I love Powershell and I use it for many tasks such as managing AOS services and deploying AX applications. Nevertheless I rarely do much work regarding Windows servers, because there is always somebody who does that as a part of his own job. But now I’m preparing few private machines for my research and development and it’s great how management cmdlets make it easier. Just a few examples:

Rename-Computer Dev1
Install-WindowsFeature Hyper-V -IncludeManagementTools
Restart-Computer

Lovely!

Creating sales orders via AIF in AX2012

$
0
0

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.

Windows Azure Virtual Network with Active Directory

$
0
0

First of all, why would you want to create virtual machines in Windows Azure? There may be many reasons; let’s mention just a few of them:

  1. You don’t need any new hardware or adding more pressure on existing hardware.
  2. Creating virtual machines is very easy – you can do it in just a few clicks.
  3. You can easily build systems with several machines, for example if you need to test a clustered environment.
  4. You can easily change allocated resources, e.g. you may add additional processor cores and RAM for the time of compilation.

By the way, if you’re considering putting a production environment of Dynamics AX to Azure, you may want to wait for AX 2012 R3, where deployment to Windows Azure will be officially supported (as announced on Convergence 2013 EMEA).

In many cases, you’ll also need Active Directory (installation of Dynamics AX falls into this category). You could use your existing domain even in Windows Azure and that’s something what you probably want to do with production systems. But creating a new domain (or even several domains) is useful for many development and testing scenarios. And what’s equally important – you will get your own domain where you can do everything you want, which is typically (and fortunately) not the case with your real company domain. You may want to add new machines to a domain, create new users to test permissions, create users for AX lifecycle services, testing Group Policy and so and so on.

Before you start, you’ll need an Azure account. If you have an MSDN subscription, you’ll get monthly credit for Azure services – that’s the best way to start. Otherwise you can always use the free one-month trial.

Creating a network with Active Directory domain is relatively easy, it just requires several steps.

First of all, log into the Azure management portal and create a new virtual network. You can use Network Services > Virtual Network > Quick Create or Custom Create.NewVirtualNetwork800

Then create a new virtual machine and configure it as a domain controller. Use Install a new Active Directory forest in Windows Azure as a reference.

Create a new virtual machine from gallery (choose a template with Windows Server OS, of course).

NewVM800

Don’t forget to assign it to the newly created virtual network.

VMConfig2

Log into the new machine and configure it as a Domain Controller.

By the way, I used merely the Small VM size (1 core, 1.75 GB RAM).

When the domain controller is ready, go to the configuration of your virtual network and set the Domain Controller machine as a DNS server (use its Internal IP Address that you can find on VM’s Dashboard). You can use some additional DNS servers, if you want:

DNS400

The last step is creating a virtual machine (machines) for your real work (don’t forget to link it to the virtual network) and adding it to your new domain as usual.

If you’re in doubt which size to choose for Dynamics AX 2012, I’m using the Large one (4 cores, 7 GB RAM). You can always change it if you find it unsuitable for your purposes.

Viewing all 203 articles
Browse latest View live