Thursday, May 5, 2011

AX 2012 & .NET - Coding Business Logic in C#

AX 2012 has pushed the boundary of .NET interop, and - as Microsoft touts - makes managed code a "first-class citizen" in the application.
This is true - with some limitations on the eventing side in my personal opinion - but there are some caveats, which can easily be overcome if you understand the underlying workings of the proxies.

Consider the following example, in C#:


Query query = new Query();
query.addDatasource(Global.tableName2Id("CustTable"));

QueryRun queryRun = new QueryRun(query);
while(queryRun.next())
{
/* .... */
}



At first glance, this would work. And it compiles as well. However, a runtime error occurs complaining that object of type "Query" does not have a method called "Next".

So what's happening? Well, remember the classes in managed code are proxies. So, they can be instantiated by giving them a reference to an existing X++ object. When you pass in your query object to the constructor of QueryRun, it will try to wrap the managed QueryRun proxy around the X++ object reference, which is the Query.

As you can see in the IntelliSense, the other overload of the constructor is to pass in an object of type "object". Not much documentation there, but that is in fact the X++ New() parameter profile we would like to invoke. So, we change our C# code to:


query = new Query();
query.addDataSource(Global.tableName2Id("CustTable"));

queryRun = new QueryRun(query as object);
while (queryRun.next())
{
/* .... */
}



Now, this will work as expected.

10 comments:

  1. Great article! I have been attempting to select records from a database table for hours without luck. I think this code will work. However, when I attempt to use it, C# cannot find the method addDatasource on the my query object and it cannot find the QueryRun object type at all. Is there a specific dll I need to add to get access to the type and method in question?

    ReplyDelete
    Replies
    1. You have to make sure to add the proxies of course. So your question about finding the queryrun object type, make sure you have the proxy.
      For the addDatasource method, I have not seen this issue... in relation to this article, how are you creating your query object?

      Delete
    2. I am creating it just like you are. In my case, I need to retrieve some rows from the SalesQuotationLine Table, see if they have a specific value and then delete them from the SalesQuotationLine Table if they do. I do not think I have the proxy. How would I add it? Is there an article on how to do it somewhere? I have the Msft Intro to Dev I, II, & III Pdfs but I have not seen anything covering this topic (I read I and II cover to cover)?

      Delete
    3. I think I do have the proxy actually. I have this line of code in my c# using Microsoft.Dynamics.AX.ManagedInterop; and I have included a bunch of tables, classes, etc from the AOT into my project. I do not know what I would need to add to my project to have access to Query, QueryRun, etc.

      Delete
    4. There's a few things you can do to get access to kernel proxy classes (which is what Query and QueryRun are), and there's an article on my blog here on that: http://daxmusings.blogspot.com/2011/06/ax2012-net-proxies-for-kernel-objects.html

      For Query and QueryRun you could also use SysQuery and SysQueryRun instead, those inherit from query and queryRun and they are available in the AOT, which means you can easily add them to your VS project.

      Delete
    5. Thank you, that worked. Is there any way to include ttsbegin/ttscommit and forupdate into the query? I have looked around the code for a while and found nothing.

      Delete
    6. I just spent a few hours on the same issue as WBH and here is the solution to both your questions:

      using (Session session = new Session())
      {
      session.Logon("dat", null, null, null);
      session.TTSBegin();
      CustTable table= new CustTable();
      table.InitializeForSelect();
      while (table.Next())
      {
      //Somehow find which row do you need
      //...
      //This is equivalent to the selectforupdate kernel command in X++
      table.Call("selectForUpdate", "1");
      table.Update();
      session.TTSCommit();
      }
      }

      Delete
    7. I'm not sure what the session.logon is for, that is a business connector object so you don't need that during execution from within AX.
      You can actually call ttsBegin and commit from the Application class.

      As far as session, I think there's a way to get the current session, but I forget how, I'll have to look that up.

      Delete
  2. http://msdn.microsoft.com/en-in/library/gg865119.aspx

    I believe this may be the used to find current session

    ReplyDelete
    Replies
    1. I guess I owed an answer on this. If your code is running within an AX session, you can use the following code to get the session.

      Microsoft.Dynamics.AX.ManagedInterop.Session.Current

      And if you have code that is "hybrid" (that either runs in AX or has to use a BC.net session), you could do something like this:

      if (Microsoft.Dynamics.AX.ManagedInterop.Session.Current == null
      || !Microsoft.Dynamics.AX.ManagedInterop.Session.Current.isLoggedOn())
      {
      Axapta ax = new Axapta();
      ax.Logon(null, null, null, null);
      }

      from which point on the session.current will actually be the session (either the AX process within which your code runs, or the bc.net session).

      Delete