DataPortal.Fetch - why return object when no data?

DataPortal.Fetch - why return object when no data?

Old forum URL: forums.lhotka.net/forums/t/2386.aspx


boo posted on Monday, February 19, 2007

This seems non-intutive to me:

BusinessObject bo = BusinessObject.GetBusinessObject(id);

If 'id' is valid you get a business object as you expect.  But if id is a value that doesn't return a record in the database wouldn't you expect 'bo' to be null?

To get around this I've added the following code:

public static BusinessObject GetBusinessObject(id)
{
    BusinessObject o = DataPortal.Fetch<BusinessObject>(new Criteria(id));
    if(o.Id == 0)
   {
       o = null;
    }
    return o;
}

But I'm curious if maybe there should be something different in the DataPortal_Fetch override instead?

rxelizondo replied on Monday, February 19, 2007

Personally, I have never ran into that problem because I will normally throw an error on "DataPortal_Fetch" if the ID is invalid.

 

In my way, in the client app I simply catch the error and do whatever I need to do to handle the situation. For me, this is more intuitive.

Skafa replied on Monday, February 19, 2007

I prefer to use Exceptions too, altough i have used boo's method too

ajj3085 replied on Tuesday, February 20, 2007

Usually that would be a case where you throw an exception.  Very rarely would you be able to continue without the requested business object.  Sometimes you can though, but I would think thats not the norm.

boo replied on Tuesday, February 20, 2007

Interesting.  I think if our overall architecture were changed to where we want it, throwing an exception might be more appropriate, but as it stands, returning null seems the better way to go rather than incurring the cost of throwing and catching an exception.

RockfordLhotka replied on Wednesday, February 21, 2007

Throwing an excepiton is cheap. Catching exceptions is "expensive".

I put that in quotes, because it is all relative. Are you talking to a database? Are you using a remote data portal? If you answer 'yes' to either question, then the overhead of those activities is so great that there's no comparative overhead to catching a single exception.

Seriously, this "catching exceptions is expensive" thing gets blown way out of proportion. Yes, compared to a method call, catching an exception is expensive - so you shouldn't throw tons of exceptions in tight loops.

But they aren't that expensive - especially in a case like this, where you've already incurred overwhelming overhead by talking to a database and/or an app server.

boo replied on Wednesday, February 21, 2007

I guess I prefer returning null because the below is easier then creating an Exists method for every BO (I don't think there's a diff in code generate for calling Exist below first instead, it's the cost of writing all those extra SP's and command objects and having to maintain them). 

Admitedly this is lazier...but then at the same time is not finding a user an exceptional condition?  Yes or no; I guess it would depend on what you're doing...in my example we're just talking about a login screen, so no in this case, but when appending a name to $20 million contract...well that maybe exceptional, but that exception would occur in the BO that's responsible for the $20 million contract...it would be validation rule, IMO.  Use cases are important for a reason right?  Without context, it's hard to say what's exceptional (alternate flow) and what's not (main flow)...but then I guess not finding a user on login would be alternate flow...hmmm...it's really easy to over think this stuff isn't it?  I guess the question is does Sytem.Exception map 1:1 with alternate flows?  Probably not. Eh...long day...thinking to much.  :)

EnterpriseUser _user = EnterpriseUserList.GetUser(Convert.ToInt32(txtUserId.Text));
if(_user != null)
{
    //Continute logic
}
else
{
   //User not found logic
}

RockfordLhotka replied on Wednesday, February 21, 2007

Keep in mind that you can use the exception-based scheme (which is how CSLA is designed) to get the no-data result back from DP_F, and then hide that in your factory. Your factory can be:

public static EnterpriseUser GetUser(...)
{
  try
  {
    return DataPortal.Fetch<EnterpriseUser>(new Criteria(...));
  }
  catch
  {
    return null;
  }
}

The caller gets the semantics you desire, but the overall architecture supported by CSLA remains intact.

boo replied on Wednesday, February 21, 2007

So you're saying DP_F throw an exception if no records are returned as shown below?:

if(dr.Read())
{
   //populate
}
else
{
   throw SomeException();
}

Mark replied on Wednesday, February 21, 2007

That's exactly the code I use.  For example,

using (SafeDataReader dr = new SafeDataReader(cmd.ExecuteReader()))
{
   if (dr.Read())
      PopulateMembers(dr);
   else
      throw new RecordNotFoundException(id.ToString());
}

*Every* root object fetch method I have is retrieved by ID (I use Guids).  If the record can't be found, I definitely consider it an exception.  Since I'm using a custom exception, though, I can handle the situtation a bit more gracefully in the UI as opposed to handling more generic exceptions.

In the instance where you're looking up a customer by SSN (for example), what I typically do is pull back a list of customers who have an SSN that match the user-entered criteria.  If the list only returns one item - I immediately turn around and retrieve the root object and display the appropriate lookup/edit screen.  If the list returns more than one item, I display a search results form where the user can pick from the returned result list.

tchimev replied on Thursday, October 16, 2008

I want to return null object when calling Fetch.
But the exception does not bubble up to the static GetUser(id) method, it says 'Exception was unhandled'

My DataPortal_Fetch method:

            //for test only
            private void DataPortal_Fetch(object criteria)
            {
                throw new Exception();//at this point I get 'Exception was unhandled'
            }

And this is only in my work solution.

When I create a new project in VS 2008 everything is fine and I get a null object.

Why does not the exception bubble up to the factory method?

ajj3085 replied on Thursday, October 16, 2008

Do you have your DataPortal.Fetch call wrapped in a try catch block?

tchimev replied on Thursday, October 16, 2008

Yes, but it still says exception unhandled

ajj3085 replied on Thursday, October 16, 2008

Well if you're running in the debugger, it may be catching the exception for you. 

rsbaker0 replied on Thursday, October 16, 2008

tchimev:
I want to return null object when calling Fetch.
But the exception does not bubble up to the static GetUser(id) method, it says 'Exception was unhandled'...

I wanted to do the same thing, so I just have my static Get__() method test for a null/default object key and return null rather than the empty object instead of throwing an exception.

 

RockfordLhotka replied on Friday, October 17, 2008

The debugger will often consider an exception to be "unhandled" when it crosses dynamic invocation boundaries. CSLA uses dynamic invocation (or reflection in older versions) to call the DataPortal_XYZ methods, and the debugger doesn't properly (imo) handle this scenario.

One solution is to click Debug|Exceptions and then deselect the option for managed user exceptions - thus preventing the debugger from breaking on unhandled exceptions like this.

That turns out to be pretty much a requirement when working in Silverlight - at least for unit testing, because most unit test frameworks (including UnitDriven) dynamically invoke the test methods, and many tests have expected (but "unhandled") exceptions...

tchimev replied on Friday, October 17, 2008

Thank you Rocky for your help.

I tried your solution with Debug | Exceptions but still no result.

I think it is something in my work solution, something is not configured correctly may be.
In my work solution I have few projects, like console, win forms, and only in one of the projects in the solution I use CSLA 3.5.

If I start a new solution and try to return null object it works, NO unhandled exceptions.

If someone can think of something else, I'll be thankful.

EDIT:
I think I found a workaround.

Deselect the User - unhandled checkbox on Common Language Runtime Exceptions
in the Debug | Exception menu.

I don't know if this is right, because in a new solution I have this checkbox checked and it works, no unhandled exception.




tetranz replied on Friday, October 17, 2008

The way I handle the "no object returned" issue is simply for my BO to have a private field called  _objectExists. My DP_Fetch does something like this:

_objectExists = dr.Read();

if (_objectExists)
{
  Load data
}

Then in my factory method

MyClass bo = DataPortal.Fetch<MyClass>(new SingleCriteria<MyClass, int>(id));

if (!bo._objectExists)
{
   bo = null;
}

return bo;

That suits me nicely. If the object doesn't exist then the factory returns null. I usually handle the null rather than throw an exception but I if you want an exception then you could use this method and then throw an exception in the factory method. That would get around the debugger issues with exceptions in dynamically called code.

CBoland replied on Thursday, July 22, 2010

I ran into this issue today and wanted to weigh in on the discussion. This technique uses exception handling as a control structure, which is generally not a good idea. As such, I implemented the exception handling as described since it came from Rocky, and have DataPortal_Fetch throw MyException accordingly. However, the exception received in the factory method contains this hierarchy:

DataPortalException -> CallMethodException -> MyException

Given the principle of "only catch exceptions that you can do something with", I'm interested in catching only MyException, allowing the rest to bubble up the stack (since they really are true exceptions). This means carefully interrogating the exception hierarchy in each factory method that needs to exhibit this behavior. :(  In all, it seems like a lot of work for simply having the factory method return null when no data was found.

There's got to be a better way.

RockfordLhotka replied on Thursday, July 22, 2010

The problem is that there's (potentially) a network boundary between the server-side and client-side code. The most important thing you get from DataPortalException is that it brings the server-side stack trace and exception hierarchy back to the client. That's not normal - that's something CSLA is doing for you via DataPortalException.

If you are OK with losing all that information, you could, on the client side, catch DataPortalException and then throw ex.BusinessException - which means the client would only see the original exception - without the full stack trace, etc.

At runtime maybe that is OK, but at development time it would be really hard to debug some problems without access to the full exception information - I know this because early versions of CSLA didn't have DataPortalException, and it made debugging extremely challenging...

CBoland replied on Thursday, July 22, 2010

Thanks for the reply, Rocky.

Catching DataPortalException and inspecting .BusinessException is definitely reasonable. I'm going to adopt that strategy from now on.

FWIW, here's the catch block I'm using:

        Friend Shared Function GetFoo(ByVal id As Guid) As Foo
            Try
                Return DataPortal.Fetch(Of Foo)(New Criteria(id))
            Catch ex As DataPortalException
                If TypeOf ex.BusinessException Is ApplicationException Then
                    Return Nothing
                Else
                    Throw
                End If
            End Try
         End Function

RockfordLhotka replied on Thursday, July 22, 2010

Oh, but you can use a cool VB feature if you'd like. The Catch statement allows a When clause in VB:

Catch ex As DataPortalException When ex.BusinessException Is ApplicationException

(that's from memory - but it works something like that)

This is one of the coolest features of VB imo Smile

simisreedharan replied on Tuesday, January 28, 2014

I achieved the same thing as follows:

I declared a variable

private bool _objectExists = false;

Then set this variable true inside DataPortal_Fetch as follows:  

if (dr != null && dr.Read())
{

_objectExists = true;

}


Rewrote static method as follows: 

var obj = DataPortal.Fetch<Category>(new Criteria(personid));  

if (!obj._objectExists)    

{

obj = null;

}

 

 

return obj;

Thanks

simi

 
 

Copyright (c) Marimer LLC