Fetch NAV ERROR('') in .NET COM

jwikmanjwikman Member Posts: 25
I'm writing a COM automation in C# to handle database calls through ADO.NET.

All functions in the component returns a boolean value telling the caller if everything went well.

I've get a problem when an ERROR() is called in NAV. If I got an open db-connection in the component it keeps open even after the NAV ERROR. And if inside a transaction, the transaction keeps alive, locking every other user out of modified tables...

Example:
In NAV I've got a codeunit with an automation variable:
aw 'ADOwrapper'.ADOwrapper
OnRun()
CLEAR(aw);
CREATE(aw);
IF NOT aw.Connect('SomeConnectstring') THEN
  ShowErrors;
aw.BeginTrans;
IF NOT aw.ExecuteSQL('INSERT INTO aTable (Id) VALUES (''1'')') THEN
  ShowErrors;
IF CONFIRM('Throw error?!',FALSE) THEN 
  ERROR('Error!');
IF NOT aw.ExecuteSQL('INSERT INTO aTable (Id) VALUES (''2'')') THEN
  ShowErrors;
aw.CommitTrans;
aw.Disconnect;
CLEAR(aw);

ShowErrors(); just shows the error and calls ERROR();

If I press Yes in the CONFIRM-box the execution in NAV stops , but the transaction started by the .NET component is still active...

I want the .NET component to be informed, in some way, that there were a problem and that there is no more any references to the object. I guess that it has something to do with reference counts to the wrapper. But I might be completely wrong. :-k

Any tips or thoughts would be very appreciated!

Comments

  • jwikmanjwikman Member Posts: 25
    It seems that this problem isn't caused by NAV.

    I've installed VB6 and recreated the the same problem as in NAV.

    So I guess that this is some general COM Interop problem. And I might have done some fundamentel mistakes in the .NET code... :wink:

    Are there any COM Interop experts in here!? [-o<
  • dick11dick11 Member Posts: 60
    Is your "aw" var global? Try to make it private. (Then the CLEAR command is also not needed anymore).
    It is also always a good idea to use local vars, if possible.
    Dick van der Sar

    www.dasautomatisering.nl
  • todrotodro Member Posts: 117
    jwikman wrote:
    I'm writing a COM automation in C# to handle database calls through ADO.NET.

    All functions in the component returns a boolean value telling the caller if everything went well.

    I've get a problem when an ERROR() is called in NAV. If I got an open db-connection in the component it keeps open even after the NAV ERROR. And if inside a transaction, the transaction keeps alive, locking every other user out of modified tables...

    Example:
    In NAV I've got a codeunit with an automation variable:
    aw 'ADOwrapper'.ADOwrapper
    OnRun()
    CLEAR(aw);
    CREATE(aw);
    IF NOT aw.Connect('SomeConnectstring') THEN
      ShowErrors;
    aw.BeginTrans;
    IF NOT aw.ExecuteSQL('INSERT INTO aTable (Id) VALUES (''1'')') THEN
      ShowErrors;
    IF CONFIRM('Throw error?!',FALSE) THEN 
      ERROR('Error!');
    IF NOT aw.ExecuteSQL('INSERT INTO aTable (Id) VALUES (''2'')') THEN
      ShowErrors;
    aw.CommitTrans;
    aw.Disconnect;
    CLEAR(aw);
    

    ShowErrors(); just shows the error and calls ERROR();

    If I press Yes in the CONFIRM-box the execution in NAV stops , but the transaction started by the .NET component is still active...

    I want the .NET component to be informed, in some way, that there were a problem and that there is no more any references to the object. I guess that it has something to do with reference counts to the wrapper. But I might be completely wrong. :-k

    Any tips or thoughts would be very appreciated!
    As far as I remember, there is an issue in .NET Interop regarding "garbage collection" when dealing with legacy COM objects, the disposing of the COM interface is not done automatically under certain circumstances and you have to take care of that manually within your .NET code.

    In this case, although Navision should dispose the automation COM object (your wrapper instance) when aw is a local variable, the disposing of the ADO object inside your wrapper doesn't seem to take place.
    Torsten
    MCP+I, MCSE NT, Navision MCT (2004,2005)
  • jwikmanjwikman Member Posts: 25
    Now I've found out that this behaviour is by .NET design. When the COM client, NAV in our case, clears the reference to it's object, the interop component (called COM Callable Wrapper, CCW) decreases its reference count to the .NET component. When the reference count is 0 the component is marked for garbage collect. Sometime later, in a timeframe we can't really affect, the Garbage Collector goes through all objects that's marked for garbage collect and call the destructor of the component. This behaviour could result in a lot of open transactions, for an unspecified time...

    So right now I've got a couple of options:
    - Write a wrapper in unmanaged code
    - Redesign completely so that a NAV Error can't be throwed inside a transaction

    I think that I'll go for number two here. :)
  • wakestarwakestar Member Posts: 207
    Hi

    I would expose a com - event for CLR exceptions and pass the Exception - Message to Navision.

    In Navision it should look like this:

    MyControl::ExceptionThrown(Exception: Text[1024])
    MESSAGE(Exception);

    This way you can avoid calling "IF NOT GetBeer(1,cold)" a zillion times and you can implement the logic for the errors in Navision instead in your .net assemby.

    Navision Errors are COM errors and the CLR throws them as COMExceptions.
    So you can try to catch exceptions in .Net and fire an event in Navision, for example ClearObject which should look like:

    MyControl::ClearObject()
    CLEAR(MyControl);

    and take a look at COMtrace which is helpful when you mess around with COM
    http://www.blunck.se/comtrace/comtrace.html

    wakestar
  • dick11dick11 Member Posts: 60
    Perhaps you could make the class IDisposable.
    In the Dispose() you can release your allocated objects by hand.
    Dick van der Sar

    www.dasautomatisering.nl
  • jwikmanjwikman Member Posts: 25
    wakestar:

    That might be a nicer way of error handling! :)

    I've read some about creating com events from .NET to NAV, so I think I'll manage to do so, but how could I trap NAV:s error as COMException? Where should I put my try-catch?!

    I'd be very grateful if you could post a code sample in .NET for this. [-o<

    Btw, does your GetBeer-function realy work?! Could you share it?! :lol:


    dick11:

    I thought of that to, but it doesn't seem that the CCW calls the Dispose()-function. :(
    But the Garbage Collector calls my destructor so I could clean up there, but then it's way too late...
  • todrotodro Member Posts: 117
    wakestar wrote:
    Hi

    I would expose a com - event for CLR exceptions and pass the Exception - Message to Navision.

    In Navision it should look like this:

    MyControl::ExceptionThrown(Exception: Text[1024])
    MESSAGE(Exception);

    This way you can avoid calling "IF NOT GetBeer(1,cold)" a zillion times and you can implement the logic for the errors in Navision instead in your .net assemby.

    Navision Errors are COM errors and the CLR throws them as COMExceptions.
    So you can try to catch exceptions in .Net and fire an event in Navision, for example ClearObject which should look like:

    MyControl::ClearObject()
    CLEAR(MyControl);

    and take a look at COMtrace which is helpful when you mess around with COM
    http://www.blunck.se/comtrace/comtrace.html

    wakestar
    I don't think this will solve his problem because from my point of view the problem is related to the ADO object being still in use if he raises a C/SIDE error (which will result in a dispose of the wrapper). This leads me to the conclusion that the dispose of the wrapper is not sufficient and he has to cleanup inside .NET.
    Torsten
    MCP+I, MCSE NT, Navision MCT (2004,2005)
  • jwikmanjwikman Member Posts: 25
    todro wrote:
    I don't think this will solve his problem because from my point of view the problem is related to the ADO object being still in use if he raises a C/SIDE error (which will result in a dispose of the wrapper). This leads me to the conclusion that the dispose of the wrapper is not sufficient and he has to cleanup inside .NET.

    I think I agree with you... I can make the cleanup inside .NET. But, as I wrote earlier, the disposal of the wrapper isn't equal to the disposal of the .NET component. The cleanup in the .NET component is done too late due to the logic of Garbage Collecting in .NET.
  • wakestarwakestar Member Posts: 207
    todro wrote:
    I don't think this will solve his problem because from my point of view the problem is related to the ADO object being still in use if he raises a C/SIDE error (which will result in a dispose of the wrapper). This leads me to the conclusion that the dispose of the wrapper is not sufficient and he has to cleanup inside .NET.

    Hi
    I have no idea how his .net code looks like, but yes... he's responsible for cleaning up the resources used within his .net class.

    The last .net component I made was one single .net form which I'm closing when an en error happens. After the this.close() I fire the ClearObject in Navision. - The this.close() does it for me in my case. - I never had any problems with the GC.
  • jwikmanjwikman Member Posts: 25
    wakestar wrote:
    Hi
    I have no idea how his .net code looks like, but yes... he's responsible for cleaning up the resources used within his .net class.

    The last .net component I made was one single .net form which I'm closing when an en error happens. After the this.close() I fire the ClearObject in Navision. - The this.close() does it for me in my case. - I never had any problems with the GC.

    Hi,

    The problem for me is if I've got an open transaction and NAV throws an ERROR(), the transaction would be open until the GC chooses to do its thing. In my destructor I check for open transactions -> run a rollback and check for open connections -> close. I can't see any other way to do this earlier than through GC, do you?
  • todrotodro Member Posts: 117
    jwikman wrote:
    todro wrote:
    I don't think this will solve his problem because from my point of view the problem is related to the ADO object being still in use if he raises a C/SIDE error (which will result in a dispose of the wrapper). This leads me to the conclusion that the dispose of the wrapper is not sufficient and he has to cleanup inside .NET.

    I think I agree with you... I can make the cleanup inside .NET. But, as I wrote earlier, the disposal of the wrapper isn't equal to the disposal of the .NET component. The cleanup in the .NET component is done too late due to the logic of Garbage Collecting in .NET.
    Yes, I know what you are talking about but let's have a look at a transaction

    1.) you create aw in C/SIDE which causes a CCW to be created via .NET COM Interop
    2.) you run a function within the wrapper which might return an error
    3.) regardless of whether you raise an ERROR in C/SIDE or simply display a message, the ADO connection has to be released before you continue in Navision with the ERROR or you have to call a "ClearADO" function inside the wrapper before raising the error in Navision.

    Now the question:

    Why don't you close the ADO connections by running a cleanup function in the wrapper from within Navision ?
    aw.closeconnections();
    ERROR('...');
    

    Maybe I got something wrong, but let's mak sure we are talking about the same thing. :lol:

    I only see one situation where you run into problems: if an unexpected C/SIDE runtime error occures, all existing ADO objects (and connections) are alive until the garbage collection of .NET destroys the CCW referenced object.
    Torsten
    MCP+I, MCSE NT, Navision MCT (2004,2005)
  • jlandeenjlandeen Member Posts: 524
    A coding pattern that I have found very effective with Navision 5.0 is to wrap everything in transaction processing up in a codeunit. This means you can use the If Codeunit.run structure. In version 5.0 you can then return the error (using the GetLastErrorText function) properly through your interface to the calling .Net/COM application (Maybe just have the function return false)

    As you have properly trapped the error in Navision and returned to the calling application appropriately this should mean you can fix the condition and then re-execute the logic without haviging to dispose of the object.

    The nice thing about this approach is that you build your transactions properly so that they throw the errors accordingly and will work in the standard Navision process flow. It is only a specific codeunit that is built to interface with your .Net component that has to be built to not throw errors.
    Jeff Landeen - Sr. Consultant
    Epimatic Corp.

    http://www.epimatic.com
  • jwikmanjwikman Member Posts: 25
    todro wrote:
    Maybe I got something wrong, but let's mak sure we are talking about the same thing. :lol:

    I only see one situation where you run into problems: if an unexpected C/SIDE runtime error occures, all existing ADO objects (and connections) are alive until the garbage collection of .NET destroys the CCW referenced object.

    Yes, we’re talking about the same thing! :D

    The reason I would like the component to handle the cleanup instead of having the NAV developer to trigger it before raising an error is that there will be more developers than me that are going to use the component and they are used to the old ADODB automation. I would like to create the new .NET component with the same behavior as the old one. But I don’t know if it’s possible at all… ](*,)


    jlandeen:
    Unfortunately it’ll be used by a 3.70b client, and upgrading runtime is not an option. :(
  • jwikmanjwikman Member Posts: 25
    I just found this thread:
    [SOLVED] Ending .NET component from the NAS

    rspruit created a VB6 wrapper for the .NET wrapper :!:
    Then he called the .NET components cleanup code from the destructor of the VB6 component, which will be called immediately after NAV clears the automation variable.

    Not the most "clean" solution, but I'm convinced that it would do the trick. :D

    I'll give it a try if no one else got a better solution...?
  • jlandeenjlandeen Member Posts: 524
    If I read your original issue correctly - the problem arrises in Navision if you encounter a problem/error after you have started the transaction in the external COM/.Net component. Here is an alternative solution to the proposed VB6 Wrapper

    I would use 3 different objects:
    1) Calling Object (table,codeunit, form, etc. where thread of execution starts)
    2) Transaction Codeunit (calls the Component Wrapper and may encounter error conditions)
    3) Component Wrapper Codeunit(a single instance codeunit which has functions that expose the logic of the component to Navision)

    Calling Object Code:
    //assume WrapperCDU and TransCDU are codeunit variables
    WrapperCDU.StartTrans;
    if not TransCDU.run then begin
      WrapperCDU.AbortTrans;
      WrapperCDU.CleanUp;
    end;
    

    I am assuming that there is some "StartTrans", "AbortTrans" and "CleanUp" functions in the Component Wrapper Codeunit that can be used to start/abort a transaction as well as clean up the connection. This would allow you to address your original problem of a left over connection without the need of a VB6 wrapper.

    While this may also be a bit of a hack - you should be able to do away with your VB6 wrapper class and only have to maintain .Net code and Navision code. I've found as you add layers of complexity it becomes more difficult to maintain and support.
    Jeff Landeen - Sr. Consultant
    Epimatic Corp.

    http://www.epimatic.com
  • jwikmanjwikman Member Posts: 25
    Indeed an interesting solution. As they say; Everything can be done in NAV! :lol:

    But I still think that I'll go for the VB6 wrapper solution. I'm not a big fan of SingelInstance Codeunit solutions... And the VB6 wrapper is made in no time.

    Thanks anyway!
  • jlandeenjlandeen Member Posts: 524
    Just on wondering why you have an aversion to Single Instance Codeunits? Have you had problems with Single Instance Codeunits in the past? If so what were the specifics?

    As far as doing everything in Nav...I still have a love/hate relationship with Navision. I recognize the power of it and the structured development environment can help....but I definately miss the full on power of a true development environment like .Net :(
    Jeff Landeen - Sr. Consultant
    Epimatic Corp.

    http://www.epimatic.com
Sign In or Register to comment.