Monday, May 02, 2011

LC Analyzer for AX

Loncar Technologies has created an amazing product to improve development and analysis en AX (AX4, AX2009 and now AX2012). Take a look at http://www.loncartechnologies.com.

Also, get some free stuff: LC AOT Browser (browse the AOT filtered by a layer) and LC Project search (search for an object in all projects nodes).

Enjoy it !!!

Friday, July 17, 2009

Updating the caller Form/DataSource

If we need to notify events to the caller form we can try this pattern:

In the child form, make a method named updateCaller and invoke it when you want to notify the parent:

void updateCaller()
{
    Common common;
    Object dataSource;
    Object caller;
    ;

    //-----------------------------------
    //We are notifying using the dataSource
    common = element.args().record();
    if (common
        && common.isFormDataSource()
        && formDataSourceHasMethod(common.dataSource(), identifierstr(SomethingWasHappend)))
    {
        dataSource = common.dataSource();
        dataSource.SomethingWasHappend();
    }
    //-----------------------------------

    //-----------------------------------
    //We are notifying using the form
    caller = element.args().caller();
    if (caller
        && classidget(caller) == classnum(SysSetupFormRun)
        && formHasMethod(caller, identifierstr(SomethingWasHappend)))
    {
        caller.SomethingWasHappend();
    }
    //-----------------------------------
}

Implement the handling method in the parent form:

void SomethingWasHappend()
{
    ;
    info("Something was happend (Form)");
}

Or if you prefer, in the DataSource:

void SomethingWasHappend()
{
    ;
    info("Something was happend (DataSource)");
}

Invoking it from a simple button inside the child form:

void clicked()
{
    super();

    element.updateCaller();
}


See a pratical sample in the form MarkupTrans (DataSource -> method write).

Saturday, June 27, 2009

How to delete AOT nodes by code (UtilIdElements solution)

In my old post: How to delete AOT objects (AX/Axapta), I have used the TreeNode class. There is another useful method by using the UtilIdElements system table described in the following links:

- MS KB 913530 (CustomerSource)
- forum topic: microsoft.public.axapta.programming - Currupt records in UtilIdElement and UtilElements

Warning: Serious problems might occur if you modify the UtilIdElements table using this or another method. Modify system tables at your own risk.

The code:

static void Job1(Args _args) 
{ 
    UtilIdElements utilElement; 
    ; 

    ttsbegin; 

    select utilElement 
        where utilElement.name == 'myElementName'
        && utilElement.utilLevel == utilEntryLevel::cus // any layer 
        && utilElement.recordType == utilElementType::Table; // object type 

    if (utilelement) 
    { 
        utilElement.delete(); 

        ttscommit; 
        info('Record should be deleted now.'); 
    } 
    else 
    { 
        ttsAbort; 
        info('Could not delete record, or it was not found.'); 
    } 
}

AOS crashed due corrupted node in the AOT

Warning: this post is only informative, do not try to replicate the problem described here in a production environment. !!!!

The problem:

The problem occurred when we created a 'View' with one circular reference. After that, every time we tried to click over "Data Dictionary\Tables" or "Data Dictionary\Views", the AOS crashes.

How to reproduce the problem:

Warning: this post is only informative, do not try to replicate the problem described here in a production environment. !!!!

0. Make a backup. Also, make a copy of the AxCus.AOD file.
1. Create a simple view (ex. View1).
2. Add a new DataSource manually. Then, open DataSource's properties dialog and specify in the table field the name of the view you have created (ex. View1).
3. STOP at this point if you dont want to crash the AOS !!!.
4. Expand the DataSource node. Drag the field CreatedBy to the view's fields. It will create the CreatedBy1 field.
5. Save it if you are sure you want to continue. After this action, your AOS will crash. (oops !)

You can try to recover the error by deleting the corrupted node (ex. View1 or CreatedBy1 field) using this method: How to delete AOT objects (AX/Axapta) or this other: How to delete AOT nodes by code (UtilIdElements solution). None of them will work!!!.

The solution:

After several tries, the only solution was open the AxCus.AOD file in a binary editor (Visual Studio is enough) and perform the next steps.

Warning: Serious problems might occur if you modify the AxCus.AOD file using this or another method. Modify system files at your own risk.

0. Make a backup. Also, make a copy of the AxCus.AOD file.
1. Stop the AOS and open the AxCus.AOD in a binary editor.
2. Locate the "CreatedBy1" field data (or your corrupted node's name). Normally, it should be near to the end of the file. (Look at pictures for details).
3. Change the reference data. It is above the field's name. Put the value FF FF (65,535) in the parent and field ids.
4. Save the file.
5. Delete the index file axapd.aoi.
6. Start the AOS again.
7. Delete the corrupted view.

Image with the corrupted node:



Image with the fixed node:



It is probable the corrupted node stays alive in the AOT, so you can try the following code to remove it:

static void Job1(Args _args) 
{ 
    UtilIdElements utilElement; 
    ; 

    ttsbegin; 

    select utilElement 
        where utilElement.id == 65535; // Invalid ID we have writed in the AxCus.AOD file 

    if (utilelement) 
    { 
        utilElement.delete(); 

        ttscommit; 
        info('Record should be deleted now.'); 
    } 
    else 
    { 
        ttsAbort; 
        info('Could not delete record, or it was not found.'); 
    } 
}


Tuesday, March 17, 2009

Posting a ledger journal

Here is the adapted example for posting a ledger journal line. Simply replace values between #s (#value#):

static void ExampleLedgerJournal(Args _args)
{
    LedgerJournalTable      ledgerJournalTable;
    LedgerJournalTrans      ledgerJournalTrans;
    LedgerJournalCheckPost  ledgerJournalCheckPost;
    NumberSeq               numberSeq;
    ;

    ttsbegin;

    // Journal name
    ledgerJournalTable.JournalName = "#JOURNALNAME#"; // ex. Daily, daytrans, etc.
    ledgerJournalTable.initFromLedgerJournalName();
    ledgerJournalTable.Name = "#DESCRIPTION#";  // description for this journal
    ledgerJournalTable.insert();

    // Voucher
    numberSeq = NumberSeq::newGetVoucherFromCode(LedgerJournalName::find(ledgerJournalTable.JournalName).VoucherSeries);
    ledgerJournalTrans.Voucher = numberSeq.voucher();

    // Lines
    ledgerJournalTrans.JournalNum = ledgerJournalTable.JournalNum;
    ledgerJournalTrans.CurrencyCode = CompanyInfo::standardCurrency();
    ledgerJournalTrans.ExchRate = Currency::exchRate(ledgerJournalTrans.CurrencyCode);
    ledgerJournalTrans.AccountNum = "#ACCOUNT#";
    ledgerJournalTrans.AccountType = LedgerJournalACType::Ledger;
    ledgerJournalTrans.AmountCurDebit = #VALUE#;
    ledgerJournalTrans.TransDate = systemDateGet(); //Avoid the Today function
    ledgerJournalTrans.OffsetAccount = "#OFFSET ACCOUNT#";
    ledgerJournalTrans.Txt = "#TXT#";
    ledgerJournalTrans.insert();

    //Posting the Journal
    ledgerJournalCheckPost = LedgerJournalCheckPost::newLedgerJournalTable(ledgerJournalTable, NoYes::Yes);
    ledgerJournalCheckPost.run();
    
    ttscommit;
}


Note: you can expand to include more than one line. Enjoy!

Friday, February 06, 2009

Delivering large strings through WCF

If you have trouble sending large strings through WCF (more than 8192 bytes), it is probable you need to configure the parameter ReaderQuotas.MaxStringContentLength (default value is 8192). This parameter should be configured in both sides (client and server) as needed.

Set this by code:

NetTcpBinding tcp = new NetTcpBinding();
tcp.ReaderQuotas.MaxStringContentLength = 65535;


or modifying the config file:

...
<binding name="myTcpBinding">
    <readerQuotas maxStringContentLength="65535" />
    ...
</binding>
...


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