Fetch NAV ERROR('') in .NET COM
jwikman
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
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!
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!
0
Comments
-
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...
Are there any COM Interop experts in here!? [-o<0 -
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.0 -
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.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'.ADOwrapperOnRun() 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!
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)0 -
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.
0 -
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
wakestar0 -
Perhaps you could make the class IDisposable.
In the Dispose() you can release your allocated objects by hand.0 -
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?!
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...0 -
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.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
wakestarTorsten
MCP+I, MCSE NT, Navision MCT (2004,2005)0 -
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.0 -
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.0 -
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?0 -
Yes, I know what you are talking about but let's have a look at a transactionjwikman 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.
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.
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)0 -
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.0 -
todro wrote:Maybe I got something wrong, but let's mak sure we are talking about the same thing.

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!
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.
0 -
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.
I'll give it a try if no one else got a better solution...?0 -
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.0 -
Indeed an interesting solution. As they say; Everything can be done in NAV!

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!0 -
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
0
Categories
- All Categories
- 73 General
- 73 Announcements
- 66.6K Microsoft Dynamics NAV
- 18.7K NAV Three Tier
- 38.4K NAV/Navision Classic Client
- 3.6K Navision Attain
- 2.4K Navision Financials
- 116 Navision DOS
- 851 Navision e-Commerce
- 1K NAV Tips & Tricks
- 772 NAV Dutch speaking only
- 617 NAV Courses, Exams & Certification
- 2K Microsoft Dynamics-Other
- 1.5K Dynamics AX
- 322 Dynamics CRM
- 111 Dynamics GP
- 10 Dynamics SL
- 1.5K Other
- 990 SQL General
- 383 SQL Performance
- 34 SQL Tips & Tricks
- 35 Design Patterns (General & Best Practices)
- 1 Architectural Patterns
- 10 Design Patterns
- 5 Implementation Patterns
- 53 3rd Party Products, Services & Events
- 1.6K General
- 1.1K General Chat
- 1.6K Website
- 83 Testing
- 1.2K Download section
- 23 How Tos section
- 252 Feedback
- 12 NAV TechDays 2013 Sessions
- 13 NAV TechDays 2012 Sessions