Monday, December 29, 2008

WCF interface for Dynamics AX

In DAX you must implement a lot of interfaces, some of them using ADO, AIF, WS, MSMQ, etc. I have developed my own interface using WCF (.NET Framework 3.5).

In this interface, I built two applications: the server that interacts with DAX by using the Business Connector and the client that is a standalone application that doesn't need ADSI and can run outside of the organization, for example, to send or receive data from partners automatically.



The example bellow includes the source code for Visual Studio 2008 and the DAX project (.xpo). In this project, I transport data from a local database (MyTable) to an owner table in DAX: WCFMyTable. Also, I can detect changes from any DAX table and send data to the client. For example: when any customer is added, I can replicate this data to legacy systems.



The DAX project includes the following elements:

WCFMyTable: this is a sample table for receive data from the client side.

WCFConnTest: this class is used to test the connection with DAX. The method getCustomer can be used for retrieve the first customer in the table CustTable.

WCFTask: is a class that encapsulates the access to any table we want to send to the client.

    getRecord - returns a common record.
    getRecordAxXml - returns the xml representation for the record this task is belong to.
    MarkAsOK - update the task with a successful result when the client has processed it.
    MarkAsFAILED - update the task as "failed" when the client has processed it and an error was raised. Tasks with errors are not removed from the table for debugging purposes.

WCFSender: this is the class that can be used for create a task for deliver to the client. You must override the update, insert and delete methods in the table you want to send. Here are the insert and update methods from the Dimensions table:

public void insert()
{
    super();

    // Send this task
    WCFSender::SendThisRecord(this);
    // End
}


public void update()
{
    DimensionSetCombination dimensionSetCombination;
    ;

    ttsbegin;

    if (this.Closed != this.orig().Closed)
    {
        update_recordset dimensionSetCombination
            setting active = !this.Closed
            where dimensionSetCombination.Dimension[Dimensions::code2ArrayIdx(this.DimensionCode)] == this.Num;
    }

    super();

    ttscommit;

    // Send this task
    WCFSender::SendThisRecord(this);
    // End
}


WCFTasks: this table store all tasks pending for deliver to the client. The table only store the reference to the record by tableId and recId.

WCFInterface: this is the main class that is used for read tasks from server (method getNextTask) and send data to server (method ProcessTask).

    getNextTask: with this method we can get the next task for processing in the client side and at the same time, we can update the previous one. By using the Company and previousCompany parameters, we can read tasks from several companies.

    ProcessTask: this is the method called when we want to send data back to DAX. We can also send multi-company data by specifying the Company parameter.

Here are the files for download:

Visual Studio 2008 project (AXInterface.zip)
The DAX project: WCFInterface.xpo
MyTable.sql definition

License: The Microsoft Public License (Ms-PL)

Sunday, December 28, 2008

Truncated real fields (ex. LineNum) when table is exported as XML

When you are using the method xml() for exporting records from any table in DAX you could get inconsistency data for real fields.

The problem is located inside the method Global::xmlstring. In this method, the real type is exported only with 2 decimals. You must override this method in the appropiated layer to export reals with 8, 10, 12 or more number of decimals.

// Returns a XML string describing the given value of the given type.
static str xmlString(anytype value, Types theType, int indent=0)
{
    str r;
    Object o;
    Common record;

    ...

    r = strrep(' ', indent);

    switch (theType)
    {

    ...
    
    // Modified: original allows only 2 decimals,
    // now, we allow 12
    case Types::Real:       r += num2str(value,
                                        /* min chars */ 1,
                                        /* decimals */  12,  // Original = 2
                                        /* decimals = point */ 1,
                                        /* no thousand sep */ 0); break;

    ...

    }
    return r;
}


Saturday, December 13, 2008

Some features in .NET Framework 3.0

Implicitly typed variables and arrays

Implicit variables are useful for improve writing and reading code (not in all cases) and they are key for LINQ query expressions:

Before:

Dictionary<string, string> dict = new Dictionary<string, string>();
int[] Numbers = new int[] { 1, 2, 3, 4, 5 };
string[] s = new string[] { "a", "b", "c", "d" };


Now:

var dict = new Dictionary<string, string>();
var Numbers = new[] { 1, 2, 3, 4, 5 };
var s = new[] { "a", "b", "c", "d" };


Extensions Methods

That's an amazing feature for extend interfaces and classes that we don't have the source code or we don't want to modify directly.

Example: Suppose you have an interface that has two properties: Name and Age. Also, it has a method that display its data as 'Name=NameValue Age=AgeValue':

public interface ITestClass
{
    string Name { get; set; }
    int Age { get; set; }

    void Show();
}


But, we need to show this data in a different way in several parts of our code, for example: 'NameValue (AgeValue)'. By extending this interface we can simply invoke the method as 'tc.ShowCustom();' like if ShowCustom is part of this interface:

public static class TestClassExtension
{
    public static void ShowCustom(this ITestClass tc)
    {
        MessageBox.Show(tc.Name + " (" + tc.Age.ToString() + ")");
    }
}

...

ITestClass tc = GetTestClassInteface();
tc.Name = "Oscar";
tc.Age = 36;

tc.Show();
tc.ShowCustom(); // Now it appears like if this method is part of ITestClass (!!! amazing !!!)



NOTE: You can extend standard and base classes/interfaces like String, IEnumerator, etc.

Lambda Expressions

They are key for LINQ expressions and for reduce the use of delegates, here a little example:

var list = new List<string>();

list.Add("one");
list.Add("two");
list.Add("three");

bool bExist = list.Exists(delegate(string s) { return s == "two"; });


And using Lambda Expressions:

bool bExist = list.Exists(s => s == "two");


Anonymous types

Allows the on-the-fly creation of structures:

var person = new { Name = "Oscar", Age = 36 };

MessageBox.Show(person.Name + "(" + person.Age + ")");


Object/Collection Initializers

We can now initialize any property when we are creating some instance for objects or collections:

Timer t = new Timer() { Interval = 30000, Enabled = true };

var list = new List<string> { "one", "two", "three" };


References:

C# 3.0 Tutorial By Jonathan Worthington

C# Version 3.0 Specification MSDN


View My Stats