wtf?

J

jason

hello everyone. can anyone tell me why this chunk of code:

if (Convert.IsDBNull(oCommand.Parameters["@userid"].Value))
{
p_nErrorCode = 10;
p_sErrorMessage = "Login failed, no matching credentials found";
goto Failed;
}
sReturn = Convert.ToString(oCommand.Parameters["@userid"].Value);

generates the following runtime error:

System.InvalidCastException: Object cannot be cast from DBNull to other
types

this seems impossible. no ErrorCode or ErrorMessage is recorded, which
would suggest that the if condition is false, and the value is not
DBNull. further evidence being that if i comment out the last line
(sReturn = Convert...) then the code generates no runtime exception.
that further suggests the if condition is false, and the value is not
DBNull. so how on earth can the last line generate the DBNull cast
exception? either it can't be NOT DBNull and then suddenly DBNull.

clearly i'm missing something. do you guys see it? do you need to see
more code?

thanks for any help,

jason

(in case anyone is wondering, this is all Failed does:)

Failed:
if (oCommand != null)
oCommand.Dispose();
if (oConn != null)
{
oConn.Close();
oConn.Dispose();
}

(and that's the end of the function. no more tampering with the
ErrorCode or ErrorMessage, or anything that would generate a DBNull
casting exception)
 
J

Jon Skeet [C# MVP]

jason said:
hello everyone. can anyone tell me why this chunk of code:

if (Convert.IsDBNull(oCommand.Parameters["@userid"].Value))
{
p_nErrorCode = 10;
p_sErrorMessage = "Login failed, no matching credentials found";
goto Failed;
}
sReturn = Convert.ToString(oCommand.Parameters["@userid"].Value);

generates the following runtime error:

System.InvalidCastException: Object cannot be cast from DBNull to other
types

this seems impossible. no ErrorCode or ErrorMessage is recorded, which
would suggest that the if condition is false, and the value is not
DBNull. further evidence being that if i comment out the last line
(sReturn = Convert...) then the code generates no runtime exception.
that further suggests the if condition is false, and the value is not
DBNull. so how on earth can the last line generate the DBNull cast
exception? either it can't be NOT DBNull and then suddenly DBNull.

clearly i'm missing something. do you guys see it? do you need to see
more code?

Well, a short but complete example would help - see
http://www.pobox.com/~skeet/csharp/complete.html for details of
what I mean by that.

Note that using "goto" is very rarely a good idea in C# - likewise
error codes and manual disposal. Exceptions usually take care of the
first two (in error cases, at least) and the "using" statement makes
the latter easier to write correctly.
 
J

jason

well, a complete working example would be rather huge, having to
include the whole function, which calls many other functions. i would
have modified the example to attain completeness, as i've done in my
many other postings, except that this problem is specific to this code
context. i'm doing the same kind of test in a dozen other places, and
it works just fine. in fact -- THIS code works just fine when called
from classic ASP. it seems to only choking when called from ASP.NET,
which is equally baffling. so in short, modifying it for completeness
would likely remove whatever peculiarity about the context is causing
the error. my hope is that someone would recognize the error in the
codes present, unaltered shape, as is quite often the possible.

on that note, another synonymous shape the code can take and still
produce the error:

if (!Convert.IsDBNull(oCommand.Parameters["@userid"].Value))
sReturn = Convert.ToString(oCommand.Parameters["@userid"].Value);

this reports the DBNull cast error on the sReturn = ... line. how on
earth this is possible baffles me, simply looking at these two lines
alone!

regarding your other notes: so far as i know, .NET exceptions can't be
gracefully handled by ASP classic code. if they can, do let me know
where to learn more. otherwise, we have to manually code errors so that
ASP classic applications which consume this class library can handle
errors correctly. i'll take a look at the using statement to see if i
grock how that might improve my disposal mechanisms.

thanks for the comment,

jason
 
J

Jon Skeet [C# MVP]

jason said:
well, a complete working example would be rather huge, having to
include the whole function, which calls many other functions.

That sounds unlikely, to be honest. It's pretty rare that a problem
like this *really* can't be shown in isolation. My guess is that you've
actually got a similar problem elsewhere, it just hasn't manifested
itself yet.

However, if you really feel you can't present it to us in a
reproducable form, a few things you can do:

1) Log whether the value of the parameter is null (as opposed to
DBNull).

2) Log the type of the value of the parameter,
i.e. oCommand.Parameters["@userid"].Value.GetType()

What's the scope of oCommand, by the way? Is it possible that another
thread is changing the value after the first line?

How did the parameter value end up in your command in the first place?
regarding your other notes: so far as i know, .NET exceptions can't be
gracefully handled by ASP classic code. if they can, do let me know
where to learn more. otherwise, we have to manually code errors so that
ASP classic applications which consume this class library can handle
errors correctly. i'll take a look at the using statement to see if i
grock how that might improve my disposal mechanisms.

What exactly is the situation here? What does the architecture look
like?

If you absolutely *have* to do exception to error code translation, I'd
strongly recommend doing it at a wrapper layer which does *nothing*
other than error translation.
 
J

jason

hehe, amusing side note. i was looking up "using" and "automatic
disposal" and i've been reading quite a few of your comments in those
threads.

but, to the issue:
That sounds unlikely, to be honest. It's pretty rare that a problem
like this *really* can't be shown in isolation. My guess is that you've
actually got a similar problem elsewhere, it just hasn't manifested
itself yet.

i agree it is rare, i'm quite accustomed to posting isolated examples
in the newsgroups. and since your email, i've been trying to widdle one
down, but so far no luck. i'll keep at it though.
However, if you really feel you can't present it to us in a
reproducable form, a few things you can do:

1) Log whether the value of the parameter is null (as opposed to
DBNull).

2) Log the type of the value of the parameter,
i.e. oCommand.Parameters["@userid"].Value.GetType()

using the following chunk of code to make note these two pieces of
information:

if (oCommand.Parameters["@userid"].Value == null)
{
p_nErrorCode = 10;
p_sErrorMessage = "it's null but not db null.";
goto Failed;
}
else
{
p_nErrorCode = 10;
p_sErrorMessage
= "the type: " + oCommand.Parameters["@userid"].Value.GetType();
goto Failed;
}

produced the following as the error message string:

"the type: System.Guid"

which is what is expected. the reason i am converting Guid's to strings
is for, once again, ease of porting these values to ASP classic. the
properties where something like this userid is stored are like follows:

/// <summary> public string UserID </summary>
public string UserID
{
get
{
if (p_gUserID == Guid.Empty) return null;
else return Convert.ToString(p_gUserID);
}
set
{
if (value == "" || value == null) p_gUserID = Guid.Empty;
else p_gUserID = new Guid(value);
}
}
private Guid p_gUserID;

we're all looking forward to all of the consumer applications being
converted to .NET so that we don't have to have such things in place,
but for now, treating guids like strings is easy, and it works. could
it be part of the problem here? i wouldn't have thought so, since we've
been using this Guid-as-string methodology for months now in dozens of
objects without problem.
What's the scope of oCommand, by the way? Is it possible that another
thread is changing the value after the first line?

ah, the command like it's active connection, is created and destroyed
within the scope of the method. we aren't doing anything with threading
(that's a bit beyond my skills) so i don't think that anything else
should have access to oCommand.
How did the parameter value end up in your command in the first place?

ah, let's see. that's where it gets complicated. the parameters get
added through an xml serialization trick, so that modifying the
parameters is a data change, and not a code compile change. the various
classes involved are ... tricky, but would it suffice to say that if
the type is Guid, which it is, we can be pretty darn sure the xml
serializer is working? or do you want to see that code?
What exactly is the situation here? What does the architecture look
like?

basically there are several applications consuming a class library. the
applications are highly varied, including ASP classic, ASP.NET, console
applications, etc.

the code i am showing you is in one method (the Login method) of one
object (the User object). i don't have access to every consumer app,
but what i can say with certainty is that this method works fine when
called by the ASP classic app, but is producing the aforementioned
error when called by the ASP.NET app. spitting out the values of the
parameters has shown there is no difference in the data they are
passing to the method call.
If you absolutely *have* to do exception to error code translation, I'd
strongly recommend doing it at a wrapper layer which does *nothing*
other than error translation.

well i can't honestly say if we *have* to do exception to error code
translation. it just seemed the easiest way to manage certain errors.

a classic example is when a user object insert fails because the the
alternate key value provided already exists in the database. the
exception that comes from the command's execute method doesn't seem to
have any kind of error code. nor is the type so specific as to tell us
exactly what happened, it's just an "SqlException" with a message that
tells you what went wrong. that's not terribly useful, because
different SqlExceptions mean very different things. and while most of
the time we might be content saying "SqlException", in this case, we
want to tell the user "hey, try another [key value]."

for that, the immediately accessible solution was to put in the stored
procedure a test to see if the value exists, and if so return a certain
code in an output parameter. then in the code, test the value of the
output parameter, and process the specific error when it matters.

i'm of course quite happy to learn a better solution to this type of
condition, if you have such to offer :) it's just what we threw
together with our best information in the trenches.

thanks again for the discourse, and any suggestions.

jason
 
J

jason

error was resolved, btw. but i'd still be very curious to discuss more
the nature of the using statement. i have several using statements at
the top of each file, mostly so that i don't have to type the
namespaces every time i use an object from them. is that the same
statement you're referring to helping with cleanup?

also, if there are other solutions to my other explanations, i'm happy
to hear your opinions.
 
J

Jon Skeet [C# MVP]

jason said:
error was resolved, btw. but i'd still be very curious to discuss more
the nature of the using statement. i have several using statements at
the top of each file, mostly so that i don't have to type the
namespaces every time i use an object from them. is that the same
statement you're referring to helping with cleanup?

No - those aren't using statements, they're using directives. Using
statements appear in methods, like this:

using (FileStream foo = new FileStream (...))
{
// Do stuff with foo here
}

They're equivalent to a try/finally block, with Dispose called in the
finally block.
also, if there are other solutions to my other explanations, i'm happy
to hear your opinions.

I'll have a look now.
 
J

Jon Skeet [C# MVP]

<snip - useful stuff>

You said in the other article that the error had been resolved - ignore
what you don't need in this :)
ah, let's see. that's where it gets complicated. the parameters get
added through an xml serialization trick, so that modifying the
parameters is a data change, and not a code compile change. the various
classes involved are ... tricky, but would it suffice to say that if
the type is Guid, which it is, we can be pretty darn sure the xml
serializer is working? or do you want to see that code?

No, that seems okay. It's done in the same thread, I assume?
basically there are several applications consuming a class library. the
applications are highly varied, including ASP classic, ASP.NET, console
applications, etc.

But how are they hooked together?
the code i am showing you is in one method (the Login method) of one
object (the User object). i don't have access to every consumer app,
but what i can say with certainty is that this method works fine when
called by the ASP classic app, but is producing the aforementioned
error when called by the ASP.NET app. spitting out the values of the
parameters has shown there is no difference in the data they are
passing to the method call.

I don't understand quite how you're calling the method from an ASP
classic app - is this library being exposed through COM or something?
well i can't honestly say if we *have* to do exception to error code
translation. it just seemed the easiest way to manage certain errors.

It may be in some situations, but in that case having it done in a
single wrapper layer would be better than peppering it around your
business logic.
a classic example is when a user object insert fails because the the
alternate key value provided already exists in the database. the
exception that comes from the command's execute method doesn't seem to
have any kind of error code. nor is the type so specific as to tell us
exactly what happened, it's just an "SqlException" with a message that
tells you what went wrong. that's not terribly useful, because
different SqlExceptions mean very different things. and while most of
the time we might be content saying "SqlException", in this case, we
want to tell the user "hey, try another [key value]."

That sounds like you could translate the exception into a different
(business-specific) exception - that's still a lot better than error
codes.
for that, the immediately accessible solution was to put in the stored
procedure a test to see if the value exists, and if so return a certain
code in an output parameter. then in the code, test the value of the
output parameter, and process the specific error when it matters.

i'm of course quite happy to learn a better solution to this type of
condition, if you have such to offer :) it's just what we threw
together with our best information in the trenches.

thanks again for the discourse, and any suggestions.

Basically, using error codes is very error-prone in my experience - it
can be a pain to propagate them out (you're using "goto" here where if
you were using exceptions you could just throw one), and more
importantly, it can be all too easy to just not check them in the app
which uses the method.
 
J

jason

some message consolidation:

re: the using statement

ahh, i get it. it shouldn't be too hard to do something like that. so
after the scope of the using statement ends, everything is
automatically disposed of? very nice. we'll work that conversion in
soon.

re: the solution

turns out it was a classic "error looks like it is somewhere it isn't"
problem. it wasn't actually the "impossible" line that was reporting
the DBNull cast exception, it was a line several calls removed in the
code (not even in the same scope). but by commenting out that line, the
logic shifted such in the sequence of things that the problem line WAS
averted. anyway, it was in the process of trying to widdle down a
small, working example that i found the actual problem line, so thanks
:) even though i didn't get a working example, the attempt still solved
the problem.

and the rest in-line:

But how are they hooked together?

you are correct, the .net class library is compiled for com interop,
and that is registered via COM+ MMC on some ASP classic machines, and
the regsvcs utility on other machines. both methods seem to work fine.
the objects can be instantiated through Server.CreateObject calls, etc.

on .NET apps, of course we put it in the bin child directory of the
consumer .NET executable.
It may be in some situations, but in that case having it done in a
single wrapper layer would be better than peppering it around your
business logic.

this sounds appealing, but i'm not sure how that would work. would it
be a class from which all my objects would inherit? a base class with a
method that can be passed an exception and return some error code? i'm
just guessing aloud really.
That sounds like you could translate the exception into a different
(business-specific) exception - that's still a lot better than error
codes.

except that the ASP classic code still won't be able to intelligently
detect what the exception is saying? i would still need some kind of
codification to give the ASP classic, yes?
Basically, using error codes is very error-prone in my experience - it
can be a pain to propagate them out (you're using "goto" here where if
you were using exceptions you could just throw one), and more
importantly, it can be all too easy to just not check them in the app
which uses the method.

well, the using statement will be a clear improvement to the codebase.
the error codes might be too, if i understand better how the
alternative works. thanks for all the effort and explanations, i
greatly appreciate it.

jason
 
J

Jon Skeet [C# MVP]

jason said:
some message consolidation:

re: the using statement

ahh, i get it. it shouldn't be too hard to do something like that. so
after the scope of the using statement ends, everything is
automatically disposed of? very nice. we'll work that conversion in
soon.

It's one of the nicest features of C# that I wish was in Java too...
re: the solution

turns out it was a classic "error looks like it is somewhere it isn't"
problem. it wasn't actually the "impossible" line that was reporting
the DBNull cast exception, it was a line several calls removed in the
code (not even in the same scope). but by commenting out that line, the
logic shifted such in the sequence of things that the problem line WAS
averted. anyway, it was in the process of trying to widdle down a
small, working example that i found the actual problem line, so thanks
:) even though i didn't get a working example, the attempt still solved
the problem.

That's half of the point of me asking for complete examples - it often
happens that way. It's a very good way of debugging.
you are correct, the .net class library is compiled for com interop,
and that is registered via COM+ MMC on some ASP classic machines, and
the regsvcs utility on other machines. both methods seem to work fine.
the objects can be instantiated through Server.CreateObject calls, etc.

on .NET apps, of course we put it in the bin child directory of the
consumer .NET executable.

Eek. Can't say I know much about COM interop - it always sounds like
the kind of thing which is fine when it's working, but a pain when it
fails :)
this sounds appealing, but i'm not sure how that would work. would it
be a class from which all my objects would inherit? a base class with a
method that can be passed an exception and return some error code? i'm
just guessing aloud really.

I would suggest having a layer which the ASP version calls and which
the .NET client bypasses entirely. That would implement the same API
but using return codes instead of exceptions - and would implement it
by calling through to the real API, and just catching/translating any
exceptions thrown. Of course, it would be worth trying to just throw
the exceptions - I've no idea what it'll do over a COM interop
boundary, but it might just work...
except that the ASP classic code still won't be able to intelligently
detect what the exception is saying? i would still need some kind of
codification to give the ASP classic, yes?

I don't know what the ASP classic code would see, I'm afraid - but if
your business layer threw a reasonable exception, the exception
translation layer would then be able to translate it into a reasonable
error code.
well, the using statement will be a clear improvement to the codebase.
the error codes might be too, if i understand better how the
alternative works. thanks for all the effort and explanations, i
greatly appreciate it.

Exceptions are certainly better than error codes in a situation where
everything supports them - I'm afraid I don't know enough about the
kind of setup you're using to give much help on it though :(
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top