views:

51

answers:

1

Its probarbly a simple 3-tier problem. I just want to make sure we use the best practice for this and I am not that familiary with the structures yet.

We have the 3 tiers:

  • GUI: ASP.NET for Presentation-layer (first platform)
  • BAL: Business-layer will be handling the logic on a webserver in C#, so we both can use it for webforms/MVC + webservices
  • DAL: LINQ to SQL in the Data-layer, returning BusinessObjects not LINQ.
  • DB: The SQL will be Microsoft SQL-server/Express (havent decided yet).

Lets think of setup where we have a database of [Persons]. They can all have multiple [Address]es and we have a complete list of all [PostalCode] and corresponding citynames etc.

The deal is that we have joined a lot of details from other tables.

{Relations}/[tables]

  • [Person]:1 --- N:{PersonAddress}:M --- 1:[Address]
  • [Address]:N --- 1:[PostalCode]

Now we want to build the DAL for Person. How should the PersonBO look and when does the joins occure? Is it a business-layer problem to fetch all citynames and possible addressses pr. Person? or should the DAL complete all this before returning the PersonBO to the BAL ?

Class PersonBO 
{
    public int ID {get;set;}
    public string Name {get;set;}
    public List<AddressBO> {get;set;} // Question #1
} 

// Q1: do we retrieve the objects before returning the PersonBO and should it be an Array instead? or is this totally wrong for n-tier/3-tier??

Class AddressBO 
{
    public int ID {get;set;}
    public string StreetName {get;set;}
    public int PostalCode {get;set;} // Question #2
} 

// Q2: do we make the lookup or just leave the PostalCode for later lookup?

Can anyone explain in what order to pull which objects? Constructive criticism is very welcome. :o)

+1  A: 

You're kind of reinventing the wheel; ORMs already solve most of this problem for you and you're going to find it a little tricky to do yourself.

The way ORMs like Linq to SQL, Entity Framework and NHibernate do this is a technique called lazy loading of associations (which can optionally be overriden with an eager load).

When you pull up a Person, it does not load the Address until you specifically ask for it, at which point another round-trip to the database occurs (lazy load). You can also specify on a per-query basis that you want the Address to be loaded for every person (eager load).

In a sense, with this question you are basically asking whether or not you should perform lazy or eager loads of the AddressBO for the PersonBO, and the answer is: neither. There isn't one single approach that universally works. By default you should probably lazy load, so that you don't do a whole lot of unnecessary joins; in order to pull this off, you'll have to build your PersonBO with a lazy-loading mechanism that maintains some reference to the DAL. But you'll still want to have the option to eager-load, which you'll need to build into your "business-access" logic.

Another option, if you need to return a highly-customized data set with specific properties populated from many different tables, is to not return a PersonBO at all, but instead use a Data Transfer Object (DTO). If you implement a default lazy-loading mechanism, you can sometimes substitute this as the eager-loading version.


FYI, lazy loaders in data access frameworks are usually built with the loading logic in the association itself:

public class PersonBO
{
    public int ID { get; set; }
    public string Name { get; set; }
    public IList<AddressBO> Addresses { get; set; }
}

This is just a POCO, the magic happens in the actual list implementation:

// NOT A PRODUCTION-READY IMPLEMENTATION - DO NOT USE

internal class LazyLoadList<T> : IList<T>
{
    private IQueryable<T> query;
    private List<T> items;

    public LazyLoadList(IQueryable<T> query)
    {
        if (query == null)
            throw new ArgumentNullException("query");
        this.query = query;
    }

    private void Materialize()
    {
        if (items == null)
            items = query.ToList();
    }

    public void Add(T item)
    {
        Materialize();
        items.Add(item);
    }

    // Etc.
}

(This obviously isn't production-grade, it's just to demonstrate the technique; you start with a query and don't materialize the actual list until you have to.)

Aaronaught
Thanks for your time and effort, I know about the lazy-load principle, I think its more a question of where to put what code or what class should perform what action in order to be "good tier" design. I would like to seperate the tiers "by best practice", but I have trouble understanding how a "PersonBAL.GetAll()" function will return data and if this is truely "unLINQed" elements as it could be a webservice asking for the data + how the h*** do we keep the object/ID for saving/updating when the GUI-tier posts back?
BerggreenDK
@Berggreen: I'm fairly certain I answered that question; the lazy-loading code goes in the collection itself or in the property getter (if it's a 1:1 association). If you're trying to expose this type of API over a web service then you're doing it wrong; web services expose operations, not data. Having said that, for those rare occasions when you do need to return deeply-nested relationships through a web service, the typical solution is to allow the consumer to specify, in the request, which associations to load, and leave the rest as `null`/empty (no lazy loading).
Aaronaught
okay, I might just not have understood your answer completly, but I will mark it as the answer then. :o) thanks again!!
BerggreenDK
My question regarding when to "pull data" out of the db was regarding the PostalCode. Because I will probarbly need to present the user with the cityname on the screen. but then I somehow need to make sure that its also updateable when someone has edit access to the City name. Ofcause citynames does not change, but lets think of the same structure for other sorts of tables. Could be phonenumbers, departments etc. Something that might change. I guess I will need different approaches for different usage. One for Reading/presenting to end user and one for admin users (updating)
BerggreenDK
Perhaps the BAL should decide on the users accesslevel, or perhaps the DAL should filter the BO/DTO data before saving data. its these sorts of questions that arrise when thinking in this structure. Where to put what "decision" and thereby, how much data to transfer and when to transfer. Less is best, Just-in-time is great, and lazy-load is perfect for this, if made good. Just planning, thinking, discussing. :o)
BerggreenDK
For postalcode example, I will get Postalcode back from the users screen. It will be in some sort of [PersonBO] profile-object. Including the [Cityname]. But the [PostalCode.Cityname] would not be needed to submit for validation, as its a sub-table and the normal users shouldn't be allowed to change the city lookup table. Do I explain myself so it make any sense?
BerggreenDK