views:

397

answers:

2

Hi all. This questions doesn't let me sleep as it's since one year I'm trying to find a solution but... still nothing happened in my mind. Probably you can help me, because I think this is a very common issue.

I've a n-layered application: presentation layer, business logic layer, model layer. Suppose for simplicity that my application contains, in the presentation layer, a form that allows a user to search for a customer. Now the user fills the filters through the UI and clicks a button. Something happens and the request arrives to presentation layer to a method like CustomerSearch(CustomerFilter myFilter). This business logic layer now keeps it simple: creates a query on the model and gets back results.

Now the question: how do you face the problem of loading data? I mean business logic layer doesn't know that that particular method will be invoked just by that form. So I think that it doesn't know if the requesting form needs just the Customer objects back or the Customer objects with the linked Order entities.

I try to explain better: our form just wants to list Customers searching by surname. It has nothing to do with orders. So the business logic query will be something like:

(from c in ctx.CustomerSet
where c.Name.Contains(strQry) select c).ToList();

now this is working correctly. Two days later your boss asks you to add a form that let you search for customers like the other and you need to show the total count of orders created by each customer. Now I'd like to reuse that query and add the piece of logic that attach (includes) orders and gets back that.

How would you front this request?

Here is the best (I think) idea I had since now. I'd like to hear from you: my CustomerSearch method in BLL doesn't create the query directly but passes through private extension methods that compose the ObjectQuery like:

private ObjectQuery<Customer> SearchCustomers(this ObjectQuery<Customer> qry, CustomerFilter myFilter)

and

private ObjectQuery<Customer> IncludeOrders(this ObjectQuery<Customer> qry)

but this doesn't convince me as it seems too complex.

Thanks, Marco

+1  A: 

Consider moving to DTO's for the interface between the presentation layer and the business layer, see for example:- http://msdn.microsoft.com/en-us/magazine/ee236638.aspx

Something like Automapper can relieve much of the pain associated with moving to DTOs and the move will make explicit what you can and cannot do with the results of a query, i.e. if it's on the DTO it's loaded, if it's not you need a different DTO.

Your current plan sounds a rather too tightly coupled between presentation layer and data layer.

Hightechrider
Thanks for your very very fast answer. So you think that this is a case where using DTO in the presentation layer instead of business entities is a must?ThanksMarco
Marconline
It sounds like your application is approaching the size/complexity point where it becomes worthwhile. There's always a trade-off in moving to DTOs as they increase complexity overall, but tools like Automapper can reduce the point at which it becomes worthwhile.
Hightechrider
Thanks for you suggestions. I'll absolutely check for DTOs and Automapper. Thank you again.
Marconline
+1  A: 

I would agree with the comment from Hightechrider in reference to using DTOs, however you have a valid question with regard to business entities.

One possible solution (I'm using something along these lines on a project I'm developing) is to use DTOs that are read-only (at least from the presentation layer perspective. Your query/get operations would only return DTOs, this would give you the lazy loading capability.

You could setup your business layer to return an Editable object that wraps the DTO when an object/entity is updated/created. Your editable object could enforce any business rules and then when it was saved/passed to the business layer the DTO it wrapped (with the updated values) could be passed to the data layer.

public class Editable
{
    //.......initialize this, other properties/methods....

    public bool CanEdit<TRet>(Expression<Func<Dto, TRet>> property)
    {
        //do something to determine can edit
        return true;
    }

    public bool Update<TRet>(Expression<Func<Dto, TRet>> property, TRet updatedValue)
    {
        if (CanEdit(property))
        {
            //set the value on the property of the DTO (somehow)
            return true;
        }
        return false;
    }

    public Dto ValueOf { get; private set;}
}

This gives you the ability to enforce if the user can get editable objects from the business layer as well as allowing the business object to enforce if the user has permission to edit specific properties of an object. A common problem I run into with the domain I work in is that some users can edit all of the properties and others can not, while anyone can view the values of the properties. Additionally the presentation layer gains the ability to determine what to expose as editable to the user as dictated and enforced by the business layer.

Other thought I had is can't your Business Layer expose IQueryable or take standard expressions as arguments that you pass to your data layer. For example I have a page building query something like this:

public class PageData
{
    public int PageNum;
    public int TotalNumberPages;
    public IEnumerable<Dto> DataSet;
}

public class BL
{
    public PageData GetPagedData(int pageNum, int itemsPerPage, Expression<Func<Dto, bool>> whereClause)
    {
        var dataCt = dataContext.Dtos.Where(whereClause).Count();
        var dataSet = dataContext.Dtos.Where(whereClause).Skip(pageNum * itemsPerPage).Take(itemsPerPage);

        var ret = new PageData
                        { 
                          //init this
                        };

        return ret;
    }
}
confusedGeek
Thank for your suggestions! Fortunately I don't have authorization problems, but this is a good solution for other projects. About passing the query (as expression) to the BL: I don't like it too much, but probably this is just a style issue for me. I'd like the presentation layer to be the most readable and light as possible. i.e. i like to see something like GetCustomersByCountry and GetCustomersByCity instead of a "generic" GetCustomers(query), but - again - I think it's just a sylistic problem.
Marconline