Sunday, December 30, 2007

IAsyncResult interface

There are a lot of asynchronous operations (APM: Asynchronous Programming Model) that we can perform using the .NET Framework. Normally, they start by BeginXXX and EndXXX. Here a little sample using an asynchronous operation to read a file stream.

In this code, we launch the "asynchronous" operation and wait until it finished polling the .IsCompleted property of IAsyncResult.

byte[] b = new byte[1000];
 
// Open the stream
FileStream fs = new FileStream("C:\\ike.zip", FileMode.Open, FileAccess.Read);
 
// Start the asynchronous operation
IAsyncResult ar = fs.BeginRead(b, 0, b.Length, null, null);
 
// Wait until task is completed
while (!ar.IsCompleted)
{
    Thread.Sleep(1000);
    MessageBox.Show("not yet");
}
 
MessageBox.Show("Completed" + fs.EndRead(ar).ToString());
 
fs.Close();
But, we really want to perform some asynchronous operation, so, we need to declare the asynchronous callback method like this:

private void MyAsyncCallback(IAsyncResult ar)
{
    FileStream fs = ar.AsyncState as FileStream;
 
    try
    {
        MessageBox.Show("Completed" + fs.EndRead(ar).ToString());
    }
    catch (Exception ex)
    {
        MessageBox.Show("Error: "  + ex.Message);
    }
    finally
    {
        fs.Close();
    }
}
And call the BeginRead as:

byte[] b = new byte[1000];
 
FileStream fs = new FileStream("C:\\ike.zip", FileMode.Open, FileAccess.Read);
 
IAsyncResult ar = fs.BeginRead(b, 0, b.Length, new AsyncCallback(MyAsyncCallback), fs);
Points of interest:

- We use the AsyncState parameter to pass some important object to the callback method.

- Also, if some exception is thrown in the asynchronous operation, we'll be notified only when we call the EndRead method, so, we catch the exception in the callback.

Synchronizing UI Elements:

If we try to access UI elements in another thread than they were created, an exception will be raised, to avoid that, we can ask for the property InvokeRequired in some UI elements such as forms, text boxes, list views, etc. When this property is true, we need to call the appropriated Invoke method as follows:

// Delegate declaration
public delegate void ShowResult(string message);
 
// Callback method
private void MyAsyncCallback(IAsyncResult ar)
{
    FileStream fs = ar.AsyncState as FileStream;
 
    try
    {
        ShowResultInt("Completed" + fs.EndRead(ar).ToString());
    }
    catch (Exception ex)
    {
        MessageBox.Show("Error: " + ex.Message);
    }
    finally
    {
        fs.Close();
    }
}
 
private void ShowResultInt(string message)
{
    if (txtResult.InvokeRequired)
        // Invoke is required, we are in another thread than UI elements
        txtResult.Invoke(new ShowResult(ShowResultInt), message);
    else
        // Not invoke required, then we can access UI elements directly
        txtResult.Text = message;
}

Using the ThreadPool.RegisterWaitForSingleObject

For creating threads in the ThreadPool, we use the QueueUserWorkItem:
ThreadPool.QueueUserWorkItem(new WaitCallback(FunctionCall), PersonalizeDataForTherad);
Or, for creating a timed thread in the ThreadPool, we use RegisterWaitForSingleObject:
ThreadPool.RegisterWaitForSingleObject(AutoResetEventObject, new WaitOrTimerCallback(FunctionCall), PersonalizeData, TimeOut, false);
This method is useful for performing timed or event signed tasks. The RegisteredWaitHandle can be used to unregister the callback.
// Declaring our handle.  It could be declared alone or as part of 
// the state class we can pass to the RegisterWaitForSingleObject method.
RegisteredWaitHandle rwh;
AutoResetEvent arevent = new AutoResetEvent(false);
 
...
 
// Our callback procedure
private void wit(object state, bool btimeOut)
{
    if (btimeOut)
    {
        // Perform some timed task
        MessageBox.Show("1: " + DateTime.Now.ToShortTimeString());
    }
    else
    {
        // Perform some signed task or unregister the WaitHandle
        rwh.Unregister(null);
    }
}
 
...
 
// Now, we can create the WaitHandle
// We can use the state object to pass more information to the callback
rwh = ThreadPool.RegisterWaitForSingleObject(arevent, new WaitOrTimerCallback(wit), null, 2000, false);
 
...
 
// After we were used the callback, we can sign the finished event
arevent.Set();
An util use of this pattern, can be achieved by cancelling asynchronous delegates when time out.
// Declaring our handle.
RegisteredWaitHandle rwh;
 
...
 
// Creating some asynchronous operation
IAsyncResult result = someobject.BeginGetResponse(new AsyncCallback(someAsyncMethod), state);
 
...
 
// Creating the waiting callback
rwh = ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, new WaitOrTimerCallback(ScanTimeoutCallback), result, 30000, true);
 
...
 
// Timeout callback
private void ScanTimeoutCallback (object state, bool timedOut) 
{ 
    if (timedOut) 
    {
        // Cancel the asynchronous operation by timeout
        RequestState reqState = (RequestState)result.AsyncState;
        if (reqState != null) 
            reqState.request.Abort();
    }
    else
    {
        // Unregister the WaitHandle
        rwh.Unregister(null);
    }
}
References:

How do I use ThreadPool in C# and .NET? by Ashish Singhal Asynchronous HttpWebRequest by stevencohn
RegisterWaitForSingleObject in ThreadPool By Ashish Singhal
Multithreading Part 4: The ThreadPool, Timer Classes and Asynchronous Programming By Manisha Mehta

EventHandler delegate

MSDN Says: "The advantage of using EventHandler<(Of <(TEventArgs>)>) is that you do not need to code your own custom delegate if your event generates event data. Additionally, the .NET Framework needs only one implementation to support EventHandler<(Of <(TEventArgs>)>) regardless of the event data type you substitute for the generic type parameter."
// Here, MyEventArgs class declaration
public class MyEventArgs : EventArgs
{
    private string msg;
 
    public MyEventArgs(string messageData)
    {
        msg = messageData;
    }
 
    public string Message
    { 
        get { return msg; } 
        set { msg = value; }
    }
}
 
...
 
// Declaring the event property
public event EventHandler<MyEventArgs> SampleEvent;
 
...
 
// Invoking the event
if (SampleEvent != null)
    SampleEvent(this, new MyEventArgs(val));
 
...
 
// Subscribing to the event 
MyClass.SampleEvent += new EventHandler<MyEventArgs>(SampleEventHandler);
 
...
 
// Where SampleEventHandler was implemented as
private void SampleEventHandler(object src, MyEventArgs mea)
{
    Console.WriteLine(mea.Message);
}

Monday, December 24, 2007

Thread synchronization

For thread synchronization, we can use the lock statement that permits access a shared object by one thread at once. Also, we can use the AutoResetEvent and the ManualResetEvent to send some signal to our threads.

// Declaring our event for signal threads we are closing the form
private AutoResetEvent MyCloseEvent = new AutoResetEvent(false);
 
private void MyThreadProc()
{
    try
    {
        // Enqueue some values
        for(int i = 0; ; i++)
        {
            // Signal for finish this thread
            if (MyCloseEvent.WaitOne(0, false))
                break;
 
            // Thread synchronization
            lock (MyQueue.SyncRoot)
                MyQueue.Enqueue(i);
 
            Thread.Sleep(500);
        }
 
        Console.WriteLine("Finished");
    }
    catch (ThreadAbortException)
    {
        // Perform the cleanup process
        Console.WriteLine("Aborted");
    }
    catch
    {
        Console.WriteLine("Error");
    }
}
 
private void btnDequeue_Click(object sender, EventArgs e)
{
    // Thread synchronization
    // Before access the shared object
    lock (MyQueue.SyncRoot)
    {
        foreach(object obj in MyQueue)
            MessageBox.Show(obj.ToString());
 
        MyQueue.Clear();
    }
}
 
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    if ((MyThread != null) && (MyThread.IsAlive))
    {
        // Signal and wait until MyThread finish
        this.Cursor = Cursors.WaitCursor;
     
        MyCloseEvent.Set();
        MyThread.Join();
 
        this.Cursor = Cursors.Default;
    }
}
The diference between AutoResetEvent and ManualResetEvent is that the ManualResetEvent only revert to an unsignaled state when its Reset method is called. Both derives from the WaitEventHandle class.

NOTE from MSDN: "Using the lock keyword is generally preferred over using the Monitor class directly, both because lock is more concise, and because lock insures that the underlying monitor is released, even if the protected code throws an exception. This is accomplished with the finally keyword, which executes its associated code block regardless of whether an exception is thrown."

If you have some code that must be completed before aborting one thread, you can use the BeginCriticalRegion and EndCriticalRegion:

Thread.BeginCriticalRegion();
...
Thread.EndCriticalRegion();
The Mutex object can be used to synchronize threads across processes. In the following axample, we run a single instance application by signaling one Mutex named "MyMutex-EE7DA40F-3916-4DC6-8554-ED8BF82AC647".

[STAThread]
static void Main()
{
    bool createdNew;
 
    // Try to create the mutex
    Mutex mt = new Mutex(false, "MyMutex-EE7DA40F-3916-4DC6-8554-ED8BF82AC647", out createdNew);
    if (createdNew)
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
    else
        MessageBox.Show("The application is already running...");
}
The ReaderWriterLock is used to synchronize access to a resource. At any given time, it allows either concurrent read access for multiple threads, or write access for a single thread. In a situation where a resource is changed infrequently, a ReaderWriterLock provides better throughput than a simple one-at-a-time lock, such as Monitor.

Threading

This sample shows how to start, abort and wait for a thread.

NOTE: I have created a Windows Form application with two buttons btnStartThread and btnStopThread.

Step 1: declare the thread object

// Declaring the thread object
private Thread MyThread = null;
Step 2: declare the thread procedure. We catch the ThreadAbortException to manage in a graceful manner the abort event.

/// <summary>
/// This is the procedure that will be executed by the thread object
/// </summary>
private void MyThreadProc()
{
    int i = 0;
    int limit = 10;
 
    try
    {
        // Dummy process
        for (; i < limit; i++)
        {
            Console.Write("{0}", i);
            Thread.Sleep(500);
        }
    }
    catch (ThreadAbortException)
    {
        // Perform the cleanup process
        MessageBox.Show(string.Format("Thread was aborted at {0}", i));
    }
    catch
    {
    }
}
Step 3: Handle buttons events

private void btnStartThread_Click(object sender, EventArgs e)
{
    // Start the thread
    MyThread = new Thread(MyThreadProc);
 
    MyThread.Start();
}
 
private void btnStopThread_Click(object sender, EventArgs e)
{
    // Abort the thread, automatically throw the ThreadAbortException
    if ((MyThread != null) && (MyThread.IsAlive))
        MyThread.Abort();
}
Step 4: Handle the FormClosing event to wait until the thread has been finished.

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    if ((MyThread != null) && (MyThread.IsAlive))
    {
        // Wait until MyThread has been finished
        this.Cursor = Cursors.WaitCursor;
        MyThread.Join();
        this.Cursor = Cursors.Default;
    }
}
For starting the thread procedure with parameters, it must be declared for receive one object type parameter, it could be an integer, an string, a class object, etc.

/// <summary>
/// This is the procedure that will be executed by the thread object
/// </summary>
private void MyThreadProcWithParameters(object objParam)
{
    int limit = (int)objParam;
    int i = 0;
 
    try
    {
        // Dummy process
        for (; i < limit; i++)
        {
            Console.Write("{0}", i);
            Thread.Sleep(500);
        }
    }
    catch (ThreadAbortException)
    {
        // Perform the cleanup process
        MessageBox.Show(string.Format("Thread was aborted at {0}", i));
    }
    catch
    {
    }
}
For launching the thread, we simply use the new procedure or the ParameterizedThreadStart delegate, both are legal. Note how the method Start includes the value we want to pass.

private void btnStartThread_Click(object sender, EventArgs e)
{
    // Start the thread
    //MyThread = new Thread(new ParameterizedThreadStart(MyThreadProcWithParameters));
    MyThread = new Thread(MyThreadProcWithParameters);
 
    MyThread.Start(20);
}
Using the ThreadPool

For use the ThreadPool, we need to invoke the QueueWorkItem with the reference to the thread procedure or the WaitCallback delegate:

// Here, myParam is the parameter we pass to MyThreadPoolCallback
ThreadPool.QueueUserWorkItem(MyThreadPoolCallback, myParam);
...
ThreadPool.QueueUserWorkItem(new WaitCallback(MyThreadPoolCallback), myParam);
And the thread procedure/callback must be declared as:

// Wrapper method for use with thread pool.
public void MyThreadPoolCallback(Object threadContext)
{
    int threadIndex = (int)threadContext;
    ...
}

Sunday, December 23, 2007

The NameValueCollection

This class is useful for storing multiple values with the same key:
// Setup our collection
NameValueCollection nvc = new NameValueCollection();
 
// We can add different values with the same key
nvc.Add("1", "one");
nvc.Add("1", "uno");
 
nvc.Add("2", "two");
nvc.Add("2", "dos");
 
nvc.Add("3", "three");
nvc.Add("3", "tres");
 
// Iterating through Keys
foreach (string s in nvc.Keys)
    MessageBox.Show(s); // shows "1", "2", "3"
 
// Iterating through Values for an specific Key
foreach (string s in nvc.GetValues("1"))
    MessageBox.Show(s); // shows "one" and "uno"
 
// Iterating through Values
for (int i = 0; i < nvc.Count; i++)
    MessageBox.Show(nvc[i]); // shows "one, uno", "two, dos", "three, tres"
 
// Iterating using the Enumerator (Returns all keys)
IEnumerator nenum = nvc.GetEnumerator();
while (nenum.MoveNext())
    MessageBox.Show((string)nenum.Current); // shows all keys "1", "2", "3"
NOTE: using the indexer, you can not add multiple values in the same key, to avoid this, use the method Add
nvc["4"] = "four";
nvc["4"] = "cuatro";
 
MessageBox.Show(nvc["4"]); // Only shows "cuatro"

Iterating through Dictionary

// Setup our dictionary
Hashtable ht = new Hashtable();
 
ht.Add("1", "one");
ht.Add("2", "two");
ht.Add("3", "three");
 
// Iterating through values
foreach (string v in ht.Values)
    MessageBox.Show(v);
 
// Iterating through keys
foreach (string k in ht.Keys)
    MessageBox.Show(k);
 
// Iterating using the DictionaryEntry struct
foreach (DictionaryEntry de in ht)
    MessageBox.Show((string)de.Value);
 
// Iterating using the Enumerator
IDictionaryEnumerator idenum = ht.GetEnumerator();
while (idenum.MoveNext())
{
    // Getting the current DictionaryEntry
    DictionaryEntry de = (DictionaryEntry) idenum.Current;
 
    // Showing data
    MessageBox.Show((string) de.Key);
    MessageBox.Show((string) de.Value);
 
    // Also, we can access directly keys and values
    MessageBox.Show((string) idenum.Key);
    MessageBox.Show((string) idenum.Value);
}

SyncRoot and IsSynchronized properties

For making your collections thread-safe, you must implement your own code looking steps, because the Synchronized method doesn't work well.

MSDN says: "Enumerating through a collection is intrinsically not a thread-safe procedure. Even when a collection is synchronized, other threads can still modify the collection, which causes the enumerator to throw an exception. To guarantee thread safety during enumeration, you can either lock the collection during the entire enumeration or catch the exceptions resulting from changes made by other threads"

Hashtable ht = new Hashtable();
 
lock (ht.SyncRoot)
{
    ht.Add("1", "one");
    ht.Add("2", "two");
    ht.Add("3", "three");
}
 
lock (ht.SyncRoot)
{
    foreach (string s in ht.Values)
        MessageBox.Show(s);
}

Wednesday, December 19, 2007

Generic Collections

List<T>

List<string> l = new List<string>();
Methods:

Add
Insert
Remove
RemoveAt
IndexOf
Contains
Sort
Clear


Special methods that use predicates:

Find
FindAll
FindIndex
FindLast
FindLastIndex
ForEach
RemoveAll
TrueForAll


Sample using predicate:

// Create our list of customers
List<customer> l = new List<customer>();
 
// Add some elements
l.Add(new customer("mary", 100));
l.Add(new customer("john", 20));
l.Add(new customer("charles", 80));
 
// Sorting customers ascending by balance
l.Sort(delegate(customer c1, customer c2) { return c1.balance - c2.balance; });
 
// Perform some operation with each member
l.ForEach(delegate(customer c0) { c0.balance += 5; });
 
// Look for one customer
customer c = l.Find(delegate(customer c0) { return c0.name == "john"; });
if (c != null)
    MessageBox.Show("Found: " + c.name + " balance=$" + c.balance.ToString());
The customer class looks like:

public class customer
{
    public string name;
    public int balance;
 
    public customer(string _name, int _balance)
    {
        name = _name;
        balance = _balance;
    }
}
In the similar way:

public void Sum(customer c)
{
    c.balance += 10;
}
...
 
// We can also use a function to perform some action
l.ForEach(Sum);
 
public int mySorting(customer c1, customer c2)
{
    return c1.balance - c2.balance;
}
...
// Sorting ascending by balance
l.Sort(mySorting);
SortedList<K, V>

SortedList<string, string> sl = new SortedList<string, string>();
 
// Iterating using the Enumerator
foreach (KeyValuePair<string, string> kvp in sl)
    MessageBox.Show(kvp.Value);
Methods:

Add
Remove
Contains
ContainsKey
ContainsValue


Values
Keys
Count


Queue<T>

Queue<string> q = new Queue<string>();
Stack<T>

Stack<string> sk = new Stack<string>();
Dictionary<K, V>

Dictionary<string, string> d = new Dictionary<string, string>();
 
// Add some elements
d.Add("one", "mary");
d.Add("two", "john");
d["three"] = "charles";  // the typical way
 
MessageBox.Show(d["one"]);  // reading values
 
if (d.ContainsValue("mary"))
    MessageBox.Show("Mary was found!");
 
// Iterating using the enumerator
foreach (KeyValuePair<string, string> kvp in d)
    MessageBox.Show(kvp.Value);
SortedDictionary<K, V>

SortedDictionary<string, string> sd = new SortedDictionary<string, string>();
 
// Iterating using the Enumerator
foreach (KeyValuePair<string, string> kvp in sd)
    MessageBox.Show(kvp.Value);
Differences between SortedList and SortedDictionary:

In the SortedList, keys and values are indexed, but insertion and removal could be slower in the SortedList. Also, SortedList is less intensive in memory than SortedDictionary.

LinkedList<T> Represents a doubly linked list.

LinkedList<string> ll = new LinkedList<string>();
Methods:

AddFirst
AddLast
AddAfter
AddBefore


Find
FindLast
Contains


Clear

Remove
RemoveFirst
RemoveLast


First
Last


Reading elements:

LinkedListNode<string> llnode = ll.First;
Properties of LinkedListNode:

Next
Previous


Value

List // Get the linked list this node belongs to.

Monday, December 17, 2007

Using the .NET Business Connector for Dynamics AX / Axapta

Axapta ax;
 
AxaptaRecord custtable;
 
AxaptaObject query;
AxaptaObject queryrun;
AxaptaObject querybuilddatasource;
AxaptaObject querybuildrange;
 
//---------------------------------
// Making logon
ax = new Axapta();
 
ax.Logon("", "", "", "");
//---------------------------------
 
//---------------------------------
// Create query objects and query data
query = ax.CreateAxaptaObject("Query");
querybuilddatasource = query.Call("AddDataSource", 77) as AxaptaObject;  // CustTable's tableId = 77
querybuildrange = querybuilddatasource.Call("AddRange", 1) as AxaptaObject;  // AccountNum's fieldId = 1
 
querybuildrange.Call("value", "4000");  // We're looking for customer with AccountNum=4000
//---------------------------------
 
//---------------------------------
// Run query
queryrun = ax.CreateAxaptaObject("QueryRun", query) as AxaptaObject;
 
queryrun.Call("prompt");
 
queryrun.Call("next");
//---------------------------------
 
//---------------------------------
// Get result
custtable = queryrun.Call("getNo", 1) as AxaptaRecord;
 
MessageBox.Show((string)custtable.get_Field("Name"));
//---------------------------------
 
//---------------------------------
// Making logoff
ax.Logoff();
//---------------------------------
NOTE: Add reference to the Microsoft.Dynamics.BusinessConnectorNet library. Or, using the EASY WAY:
Axapta ax;
AxaptaRecord custtable;
AxaptaObject connTest;
 
//---------------------------------
// Making logon
ax = new Axapta();
 
ax.Logon("", "", "", "");
//---------------------------------
 
//---------------------------------
// Make a call to our class and get the Customer
connTest = ax.CreateAxaptaObject("ConnTest");
 
custtable = connTest.Call("getCust", "4000") as AxaptaRecord;
 
MessageBox.Show((string)custtable.get_Field("Name"));
//---------------------------------
 
//---------------------------------
// Making logoff
ax.Logoff();
//---------------------------------
And the Axapta Class 'ConnTest' looks like:
class ConnTest
{
 
}
 
public CustTable getCust(str _custAccount)
{
    Query                   q;
    QueryRun                qr;
    QueryBuildDataSource    qbds;
    QueryBuildRange         qrange;
 
    TableId                 _tableId;
    CustTable               _custtable;
    ;
 
    _tableId = tablename2id("CustTable");
 
    q = new Query();
 
    qbds = q.addDataSource(_tableId);
 
    qrange = qbds.addRange(fieldname2id(_tableId, "AccountNum"));
    qrange.value(_custAccount);
 
    qr = new QueryRun(q);
 
    qr.interactive(false);
 
    qr.prompt();
 
    if (qr.next())
        _custtable = qr.getNo(1);
 
    return _custtable;
}

Saturday, December 15, 2007

Using the COM Business Connector in C#, the easy way (AX/Axapta)

The easy way to perform tasks with the COM Business Connector in Axapta/AX is creating a class in the AOT and calling it from C#. Here I will show the hard way and the easy way, the axample queries the CustTable for read his name.

1. THE HARD WAY

//----------------------------------
// Logon
Axapta2Class ax = new Axapta2Class();
 
ax.Logon("username", "", "", "myConfig");
//----------------------------------
 
//----------------------------------
// Make the query
IAxaptaObject query;
IAxaptaObject queryrun;
IAxaptaObject querybuilddatasource;
IAxaptaObject querybuildrange;
 
query = ax.CreateObject("Query", null, null, null, null, null, null);
querybuilddatasource = (IAxaptaObject) query.Call("AddDataSource", 77, null, null, null, null, null); // CustTable Id = 77
querybuildrange = (IAxaptaObject)querybuilddatasource.Call("AddRange", 1, null, null, null, null, null); // AccountNum Id = 1
querybuildrange.Call("value", "3507", null, null, null, null, null); // 3507 is my custom query value
 
queryrun = ax.CreateObject("QueryRun", query, null, null, null, null, null);
 
queryrun.Call("prompt", null, null, null, null, null, null);
//----------------------------------
 
//----------------------------------
// Process result
if ((bool)queryrun.Call("next", null, null, null, null, null, null))
{
    IAxaptaRecord custtable = (IAxaptaRecord)queryrun.Call("getNo", 1, null, null, null, null, null);
 
    //----------------------------------
    // Show customer's name
    MessageBox.Show((string)custtable.get_field("Name"));
    //----------------------------------
 
    if (Marshal.IsComObject(custtable))
        Marshal.ReleaseComObject(custtable);
 
    custtable = null;
}
 
//----------------------------------
// CleanUp
if (Marshal.IsComObject(query))
    Marshal.ReleaseComObject(query);
 
query = null;
 
if (Marshal.IsComObject(queryrun))
    Marshal.ReleaseComObject(queryrun);
 
queryrun = null;
 
if (Marshal.IsComObject(querybuilddatasource))
    Marshal.ReleaseComObject(querybuilddatasource);
 
querybuilddatasource = null;
 
if (Marshal.IsComObject(querybuildrange))
    Marshal.ReleaseComObject(querybuildrange);
 
querybuildrange = null;
//----------------------------------
 
//----------------------------------
// Logoff
ax.Logoff();
 
if (Marshal.IsComObject(ax))
    Marshal.ReleaseComObject(ax);
 
ax = null;
//----------------------------------
 
//----------------------------------
// Release AxCOM.dll for close this session definitely
ApiWin32.CoFreeUnusedLibraries();
//----------------------------------
2. THE EASY WAY

//----------------------------------
// Logon
Axapta2Class ax = new Axapta2Class();
 
ax.Logon("olondono", "", "", "Axapta9");
//----------------------------------
 
//----------------------------------
// Make a call to our class and get the Customer
IAxaptaObject classTest = ax.CreateObject("ConnTest", null, null, null, null, null, null);
 
IAxaptaRecord custtable = (IAxaptaRecord)classTest.Call("getCust", "3507", null, null, null, null, null);
//----------------------------------
 
//----------------------------------
// Show customer's name
MessageBox.Show((string)custtable.get_field("Name"));
//----------------------------------
 
//----------------------------------
// CleanUp
if (Marshal.IsComObject(custtable))
    Marshal.ReleaseComObject(custtable);
 
custtable = null;
 
if (Marshal.IsComObject(classTest))
    Marshal.ReleaseComObject(classTest);
 
classTest = null;
//----------------------------------
 
//----------------------------------
// Logoff
ax.Logoff();
 
if (Marshal.IsComObject(ax))
    Marshal.ReleaseComObject(ax);
 
ax = null;
//----------------------------------
 
//----------------------------------
// Release AxCOM.dll for close this session definitely
ApiWin32.CoFreeUnusedLibraries();
//----------------------------------
And the Axapta class looks like:

class ConnTest
{
 
}
 
public CustTable getCust(str _custAccount)
{
    Query                   q;
    QueryRun                qr;
    QueryBuildDataSource    qbds;
    QueryBuildRange         qrange;
 
    TableId                 _tableId;
    CustTable               _custtable;
    ;
 
    _tableId = tablename2id("CustTable");
 
    q = new Query();
 
    qbds = q.addDataSource(_tableId);
 
    qrange = qbds.addRange(fieldname2id(_tableId, "AccountNum"));
    qrange.value(_custAccount);
 
    qr = new QueryRun(q);
 
    qr.interactive(false);
 
    qr.prompt();
 
    if (qr.next())
        _custtable = qr.getNo(1);
 
    return _custtable;
}
Why I prefer the easy way? because I can change the Axapta class without affect the C# application anyway. And coding AX Applications with AX busines rules is more naturally in the AOT than in C#, normally, we use the C# application as an interface, so, code must be kiss (Keep It Simple and Stupid).

"Buffer overrun" using the COM Business Conector in C# (AX/Axapta)

When you make consecutively logon and logoff, you will get an unexpected error: "Buffer overrun". That is because the COM Business Connector doesn't close the session with Axapta when you call the logoff method. So, to avoid this bug you must ensure releasing every COM object you use. Also, you must release the AXCom.dll library by yourself.

1. Call Marshal.ReleaseComObject for every object you have created including the AxaptaClass object.

if (Marshal.IsComObject(custtable))
    Marshal.ReleaseComObject(custtable);

custtable = null;
2. Finally, after making logoff, release the AXCom.dll library manually:

ApiWin32.CoFreeUnusedLibraries();
And here the ApiWin32 class:

class ApiWin32
{
    [DllImport("ole32.dll")]
    public static extern void CoFreeUnusedLibraries();
}
NOTE: For Visual Basic, declare the API as follows:

Public Declare Sub CoFreeUnusedLibraries Lib "ole32.dll" ()

Thursday, December 13, 2007

How to delete AOT objects (AX/Axapta)

Sometimes, bad objects can not be removed from the AOT because when you click them, Axapta crush !! Look up this job:
static void DeleteDamnTables(Args _args)
{
    TreeNode edt, edt2;
    ;

    edt = TreeNode::findNode("Data Dictionary\\Tables");
    if (edt != null)
    {
        edt2 = edt.AOTfindChild("tablename");
        if (edt2 != null)
            edt2.AOTdelete();
    }
}

Monday, December 10, 2007

Other specialized collections

Collections for working with strings:

StringCollection
StringDictionary
StringEnumerator


Dictionary collections:

Hashtable: optimized for a large collection
ListDictionary: optimized for an small collection
HybridDictionary: "smart" dictionary for managing both, small and large collections

IOrderedDictionary / OrderedDictionary
ListDictionary


Multiple values per key:

NameObjectCollectionBase
NameObjectCollectionBase.KeysCollection
NameValueCollection


Manipulating bits in a 32-bit integer

BitVector32 / BitVector32.Section

BitArray

Queue and Stack collections

Queue is a FIFO type collection and you can Enqueue and Dequeue elements from it. Also, Peek can be used for reading the first object without removing it.
Queue q = new Queue();
 
q.Enqueue("one");
q.Enqueue("two");
q.Enqueue("three");
q.Enqueue("four");
 
MessageBox.Show((string)q.Dequeue());  // Shows 'one'
Stack is a LIFO type collection, and you can Push and Pop elements from it. Also, Peek can be used for reading the first object without removing it.
Stack sk = new Stack();
 
sk.Push("one");
sk.Push("two");
sk.Push("three");
sk.Push("four");
 
MessageBox.Show((string)sk.Pop()); // Shows 'four'

Hashtable and SortedList collections

Represents a key/value collection. Key can't be null, Value can. The hash code is calculated from the value of the Key.

See the following sample taken from CSharp-Online.NET

// Add some elements to the hash table. 
// There are no duplicate keys, but some of the values are duplicates.
exampleTable.Add( "txt", "notepad.exe" );
exampleTable.Add( "bmp", "paint.exe" );
exampleTable.Add( "dib", "paint.exe" );
exampleTable.Add( "rtf", "wordpad.exe" );
 
// The default Item property can be used to change the value associated with a key.
exampleTable[ "rtf" ] = "winword.exe";
 
// If a key does not exist, setting the default Item property
// for that key adds a new key/value pair.
exampleTable[ "doc" ] = "winword.exe";
 
//ContainsKey can be used to test keys before inserting them.
if( !exampleTable.ContainsKey( "ht" ) )
{
    exampleTable.Add( "ht", "hypertrm.exe" );
}
 
// Use the Remove method to remove a key/value pair.
exampleTable.Remove( "doc" );
You can also iterate through the hashtable:

foreach (string v in exampleTable.Values)
{
    ...
}
 
foreach (string k in exampleTable.Keys)
{
    ...
}
Methods and properties:

Add
Remove
Contains
ContainsKey
ContainsValue


Values
Keys
Count


SortedList collection also has:

SetByIndex
GetByIndex

IComparer

You must implement the IComparer interface if you want to provide a custom comparison method in your code:

class MyCustomComparer : IComparer
{
    CaseInsensitiveComparer _comparer = new CaseInsensitiveComparer();
 
    public int Compare(object x, object y)
    {
        // custom comparison here
        return _comparer.Compare(x, y);
    }
}
The Compare method should return -1, 0 or 1 (less than zero, zero, more than zero) to indicate that object x is less than y, equals to y or more than y, respectively.

Then you can use it as parameter invoking the Sort method for any collection you have:

myList.Sort(new MyCustomComparer()); 

IEnumerable

If you implement the IEnumerable, you can return values using the keyword yield:
private int[] items;
  
public IEnumerable Items()
{
    for (int i = 0; i < items.Length; i++)
    {
        yield return items[i];
    }
}
 
public IEnumerable Primes(int max)
{
    for (int i = 0; i < max; i++)
    {
        if (IsPrime(i))
        {
            yield return i;
        }
    }
}

Collections

Namespace System.Collections

Most collection classes derive from the interfaces ICollection, IComparer, IEnumerable, IEnumerator, IList, IEqualityComparer, IDictionary, and IDictionaryEnumerator.

Because these collections are untyped, you should use generic collections unless backwards compatibility is needed.

More information CSharp-Online.Net

Common methods for collections:

Add
Insert
Remove
RemoveAt
IndexOf
Contains
Sort
Clear


Count

Wednesday, December 05, 2007

Reading CSV files in AX (Axapta)

AsciiIo fileHandle = new AsciiIo("C:\\MyFilename.csv","R");
Container cntbuf;
CustTable ct;
CustAccount accountNum;
;
 
fileHandle.inRecordDelimiter('\r\n');
fileHandle.inFieldDelimiter(','); // Or other values like ; or \t
 
while (fileHandle.status() == IO_Status::Ok)
{
    cntbuf = fileHandle.read();
 
    if (conlen(cntbuf) == 3) // You can validate content here
    {
        accountNum = ConPeek(cntbuf, 1); // Index is one based (1-based) no zero based (0-based)
 
        ttsbegin;
 
        select forupdate ct
            index hint AccountIdx
            where ct.AccountNum == accountNum;
 
        if (ct)
        {
            // Read and update fields
            ...
            ct.update();
        }
        else
        {
            // Fill fields and insert record
            ...
            ct.insert();
        }
 
        ttscommit;
    }
}

Converting Enumerations in AX (Axapta)

Axapta stores enumeration values as integer values in database. So, for convert number values to enumeration elements:

// Declare a variable of my enumeration
MyEnumeration myEnum;
 
// Instance a new DictEnum object
DictEnum dictOfMyEnum = new dictEnum(EnumName2Id("MyEnumeration"));
 
// Get the enumeration value from dictionary
r.field = str2enum(myEnum, dictOfMyEnum.value2Name(3));
Other useful methods in DictEnum:

value2Label
value2Symbol

symbol2Value
name2Value

index2Name // index is related to enum position (zero based)
index2Label
index2Symbol

Tuesday, December 04, 2007

Link Types in AX

Passive Linked child data sources are not updated automatically. Updates of the child data source must be programmed on the Active method of the master data source.

Delayed A pause is inserted before linked child data sources are updated. This enables for faster navigation in the parent data source because the records from child data sources are not updated immediately. For example, the user could be scrolling past several orders without immediately seeing the order lines for each one.

Active The child data source is updated immediately when a new record in the parent data source is selected. Notice that continuous updates are resource-consuming.

InnerJoin Selects records from the main table with matching records in the joined table – and vice versa. For example, retrieve one record for each match. Records without related records are eliminated from the result.

OuterJoin Selects records from the main table regardless of whether they have matching records in the joined table.

ExistJoin Selects a record from the main table for each matching record in the joined table. The difference between InnerJoin and ExistJoin: When the join type is ExistJoin, stop searching for matches after locating the first matching record. When the join type is InnerJoin, search for all matching records.

NotExistJoin Select records from the main table that do not have a match in the joined table.

Sunday, December 02, 2007

TypeForwardedTo

using System.Runtime.CompilerServices;
 
[assembly:TypeForwardedTo(typeof(DestLib.TypeA))]
History:

My application has a reference to the ClassLibrary1.dll, but, I had moved the code to another class library (ClassLibrary2.dll). So, I only need to add the TypeForwardedTo attribute and the reference to the ClassLibrary2.dll in the new ClassLibrary1.dll assembly. After that, the application runs well without recompile it.

NOTE: we must deploy the ClassLibrary1.dll and ClassLibrary2.dll together.

Events

public delegate void MyEventHandler(object sender, EventArgs e);
...
 
public event MyEventHandler MyEvent;
...
 
if (MyEvent != null)
{
    // Invokes the delegates.
    MyEvent(this, new EventArgs());
}

Exceptions

Unhandle exception management:
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
Throwing new exceptions:
try
{
    ...
}
catch (Exception ex)
{
    throw new Exception("Something is wrong!!!", ex);
}

Generics

Generics is the implementation of parametric polymorphism. CSharp-Online.net

Advantages: Performance (avoiding Boxing and Unboxing) and Type Safety.

Type parameters: refers to the parameter that is used in the definition of the generic type.

Type arguments: refers to the type you specify to use in place of the type parameter, that is, when you use the generic in one declaration statement.

Example:

Stack<string> s = new Stack<string>();
Constraints on type parameters

where T : struct  (any value type, except Nullables)
where T : class  (class, interface, delegate or array type)
where T : new()  (T must have a public no-argument constructor)
where T : <base class name>  (T must be or derive from the specified base class) 
where T : <interface name>  (T, must be or implement the specified interface)
where T : U  (T, must be or derive from the argument supplied for U)
Generics Delegates:

public delegate void UpdateAnimal<T>(T value);
...
public void UpdateAnimals(UpdateAnimal updater)
{
    ...
}
...
 
public static void UpdateCatAge(Cat cat)
{
    cat.Age += 1;
}
 
public static void UpdateDogAge(Dog dog)
{
    dog.Age += 0.1;
}
 
catList.UpdateAnimals(new UpdateAnimal<Cat>(UpdateCatAge));
dogList.UpdateAnimals(new UpdateAnimal<Dog>(UpdateDogAge));

Saturday, December 01, 2007

QueryBuildRange object

You can not share QueryBuildRange objects between two or more Data Sources.

That is because the Init method is called for every datasource you have in the form/report, so you must create one QueryBuildRange object for every Data Source you have.

How to filter Dimensions in Axapta 3

If you want to filter dimensions in Axapta 3, you can do it by adding a new field for filter in the Dimensions table (in my case, new field named DimEnabled [NoYes enum based]) and then you must change the DimensionsLookup form as this:

1. Add two QueryBuildRange objects in the classDeclaration method:

QueryBuildRange         queryDimensionTypeAllFilter;
QueryBuildRange         queryDimensionTypeSelectableFilter;
2. Expand Data Sources node and locate DimensionsAll:

2.1 Modify the Init method:

void init()
{
    super();
 
    DimensionsAll_ds.query().datasourceNo(1).clearDynaLinks();
    DimensionsAll_ds.query().datasourceNo(1).clearRanges();
 
    queryDimensionTypeAllRange = DimensionsAll_ds.query().datasourceNo(1).addRange(fieldNum(Dimensions, dimensionCode));
 
    queryDimensionTypeAllFilter = DimensionsAll_ds.query().datasourceNo(1).addRange(fieldNum(Dimensions, DimEnabled));
}
2.2 Modify the ExecuteQuery method:

public void executeQuery()
{
    ;
    queryDimensionTypeAllRange.value(queryValue(sysDimension));
 
    // We can apply other special filters here, such as let administrators 
    // select all dimensions
    // Ex. if (User not in Admin group) apply filter
 
    queryDimensionTypeAllFilter.value(queryValue(NoYes::Yes));
 
    super();
}
3. Expand Data Sources node and locate DimensionsSelectable:

3.1 For the Init method:

void init()
{
    QueryBuildDataSource    queryBuildDataSource;
    ;
 
    super();
 
    queryBuildDataSource = DimensionsSelectable_ds.query().datasourceNo(1);
    queryBuildDataSource.clearDynaLinks();
    queryBuildDataSource.clearRanges();
 
    queryDimensionTypeSelectableRange = queryBuildDataSource.addRange(fieldNum(Dimensions, dimensionCode));
 
    queryDimensionTypeSelectableFilter = queryBuildDataSource.addRange(fieldNum(Dimensions, DimEnabled));
 
    DimensionsSelectable.setTmp();
}
3.2 For the executeQuery method:

public void executeQuery()
{
    ;
    queryDimensionTypeSelectableRange.value(queryValue(sysDimension));
 
    // We can apply other special filters here, such as let administrators
    // select all dimensions
    // Ex. if (User not in Admin group) apply filter
 
    queryDimensionTypeSelectableRange.value(queryValue(NoYes::Yes));
 
    super();
}

View My Stats