4

Let's say I am designing business objects around a poorly optimized database that have no ability to change.

I have a Person object with a ShippingAddress property. The ShippingAddress is very expensive to load and located in a separate table, and a minority of pages use it. There are times when thousands of Person objects need to be loaded on a single page so eating the performance hit is not an option. This makes lazy loading the ShippingAddress ideal for this situation.

However, from a business logic/consumer/signature perspective there is no reason ShippingAddress should be any different the eagerly loaded Name property. For this reason, I have decided to make ShippingAddress' public property signature a regular string while its private backing field is C#'s Lazy. The question is, how should I set it? I have come up with two options that maintain separation of data access logic:

Option 1, using overloaded java style set method seems like better OO and more consistent from a internal perspective since I leverage method overloading:

    Lazy<string> _address;
    public string Address { get { return _address == null ? null : _address.Value; } }
    public void SetAddress(Func<string> address)
    {
        _address = new Lazy<string>(address);
    }
    public void SetAddress(string address)
    {
        SetAddress(() => address);
    }

Option 2 seems much better/consistent from a consumer's perspective, but the two setting methods seem very disjointed in a way that bothers me:

    Lazy<string> _address;
    public string Address 
    { 
        get { return _address == null ? null : _address.Value; }
        set { SetAddress(() => value); }
    }
    public void SetAddress(Func<string> address)
    {
        _address = new Lazy<string>(address);
    }

Does anyone with more experience than I (Most of you I assume) using C#'s lazy class have any ideas as to which is better and why?

  • Entity Framework takes care of a lot of this for you automatically. What does your data store look like? In general, you defer execution when you may not need something, and getting it is expensive. Caching is sometimes involved, so that it's only expensive the first time you get it. – Robert Harvey Jun 5 '15 at 19:49
  • The data store is a relational data base which is missing many indices/pks, has very large pks when they are present, and is completely out of my control. Also, since this is a business object, I don't see how EF is relevant. As for caching, I use it to get around many other bottle necks, but it is inappropriate here since the address is used infrequently. Additionally, my peers love to use SessionState to store things... so memory is scarce on the app server :( – TheCatWhisperer Jun 5 '15 at 19:54
  • 2
    This sounds like a nightmare. – Robert Harvey Jun 6 '15 at 1:21
3

When it comes to a BO, the consumer should not see field is lazy loaded or not and it's better to present as any other property (without exposing methods accepting Func parameters)., I'd rather go with the second approach without having the public method SetAddress

Lazy<string> _address;

public string Address 
{ 
    get { return _address == null ? null : _address.Value; }
    set { _address = new Lazy<string>(() => value); }
}
4

First, an alternative

The ShippingAddress is very expensive to load and located in a separate table, and a minority of pages use it.

In that case, perhaps ShippingAddress doesn't belong in Person in terms of the Business Object model? For the cases where you need to involve ShippingAddress as a first-class concept then you can instead define a ShippingAddress business object, which can of course have a Person as a dependency if needs be, or better yet just represent Shipping Addresses as a service, load them when needed (based on PersonID or whatever) and directly attach them to the Person instance.

Second, an exceptional case

If that doesn't / can't float your goat, then let's stick with Lazy Loading. I slightly disagree with your statement that "from a business logic/consumer/signature perspective there is no reason ShippingAddress should be any different the eagerly loaded Name property".

I totally see the perspective; and in general I would agree - except that in this case you have a specific, real-world situation (horribly slow access to the Shipping Address data store, cannot be changed, these things happen) that unfortunately defies the normal expectancy that if you've seen one getter, you've seen 'em all :)

So I would say that a client accessing the Address** property would be surprised to find that it actually involves a separate database hit, especially considering

  • The other properties don't
  • This is a property on a Business Object, not an obviously "connected" entity.

TL;DR - don't violate POLA - consider what might happen if someone throws a bunch of Person objects into an XmlSerialiser....

Third, a suggested approach

If you are going to use lazy loading from the database for Address, I'd recommend one of two approaches, both of which involve making the actual loader itself an explicitly modelled abstraction.

1. Make the loader function an explicit dependency of Person

public Person(int name, etc, IShippingAddressLoader addressLoader)

2. Make the loader function an explicit dependency of a LoadAddress() method

public void LoadAddress(IShippingAddressLoader addressLoader) { _address=addressLoader.Load(_personID); //or whatever; }

My reasoning is

  • In both ways, I think it's more obvious to client code using the Person class that there is "extra" code involved in getting the Shipping Address; and as a bonus it makes the Person class much more unit-testable because you can mock or stub the loader.

  • With either approach you don't need to declare _address itself as Lazy because you have shifted the "laziness" onto an explicit concept (the loader).

  • When you do find a nice way to cache all of the Addresses (maybe your server gets more RAM, so now it's feasible to load them all into memory at app startup, slap a SqlDependency on them and watch for changes), you only need to change the IShippingAddressLoader implementation, not the Person class.

Finally, a sneaky attempt

to sell you the first idea by suggesting that you just accept that Shipping Address is a tricky piece of data in your case, and that any code that had to deal with it (controllers or whatnot) just get their own IShippingAddressService service, e.g

public ShippingConfirmationModel GenerateConfirmationModel(Person person)
{
    var shippingAddress = _shippingAddressService.GetByPersonID(person.ID);
    person.SetShippingAddress(shippingAddress)
    return new ShippingConfirmationModel(person);
}

This frees you from all shenanigans related to lazy loading in the Person class, allows you to implement caching really easily, and also allows you to pass Person objects around safe in the knowledge that you won't accidentally tip over the database when someone decides to serialise all the people :)

**Minor note - if this is the Person's "ShippingAddress" then I would make sure to refer to it always as "ShippingAddress" in the code, as opposed to just "Address" :)

  • Thank you for the well thought out response Stephen! Although, in this case the Shipping Address is really an essential part of the business entity, I have a policy of "obfuscating" before posting on stackexchange, in this case it's not really a person class but something else where shipping address is more obviously an integral part imo. I very much like your third approach, it is very creative solution. – TheCatWhisperer Jun 8 '15 at 11:44
  • 1
    @TheCatWhisperer - ah, I see. Of course if it's integral it's integral :) But do have a go with the 3rd way, and one last thing - even the stoniest DBA can be bribed :) – Stephen Byrne Jun 8 '15 at 12:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.