views:

72

answers:

2

I'm writing a custom DAL (VB.NET) for an ordering system project. I'd like to explain how it is coded now, and receive some alternate ideas to make coding against the DAL easier/more readable. The DAL is part of an n-tier (not n-layer) application, where each tier is in it's own assembly/DLL.

The DAL consists of several classes that have specific behavior. For instance, there is an Order class that is responsible for retrieving and saving orders. Most of the classes have only two methods, a "Get" and a "Save," with multiple overloads for each. These classes are marked as Friend and are only visible to the DAL (which is in it's own assembly).

In most cases, the DAL returns what I will call a "Data Object." This object is a class that contains only data and validation, and is located in a common assembly that both the BLL and DAL can read.

To provide public access to the DAL, I currently have a static (module) class that has many shared members. A simplified version looks something like this:

Public Class DAL
    Private Sub New
    End Sub

    Public Shared Function GetOrder(OrderID as String) as OrderData

        Dim OrderGetter as New OrderClass
        Return OrderGetter.GetOrder(OrderID)

    End Function

End Class

Friend Class OrderClass
    Friend Function GetOrder(OrderID as string) as OrderData
    End Function
End Class

The BLL would call for an order like this:

DAL.GetOrder("123456")

As you can imagine, this gets cumbersome very quickly. I'm mainly interested in structuring access to the DAL so that Intellisense is very intuitive. As it stands now, there are too many methods/functions in the DAL class with similar names.

One idea I had is to break down the DAL into nested classes:

Public Class DAL
    Private Sub New
    End Sub

    Public Class Orders
        Private Sub New
        End Sub

        Public Shared Function Get(OrderID as string) as OrderData
        End Function

    End Class

End Class

So the BLL would call like this:

DAL.Orders.Get("12345")

This cleans it up a bit, but it leaves a lot of classes that only have references to other classes, which I don't like for some reason.

Without resorting to passing DB specific instructions (like where clauses) from BLL to DAL, what is the best or most common practice for providing a single point of access for the DAL?

+1  A: 

What you have done is indeed an improvement. You have made a series of Repository classes who's job in life it to return you the entities. You can also put your Write/Update/Delete methods in your new Orders object and have these things on one place, which is good.

You problem you bring up at the end is an issue we all deal with. If you've ever wondered why LINQ-to-SQL or LINQ-to-Entities is good, this is why. It has to do with the IQuerable interface. Using either of these technologies, your Get method could return an IQuerable(Order) (seemingly returning every order object possible), which you could then do LINQ against; this would actually apply your business criteria to the generated SQL query! It seems like magic at first, but has to do with the nature of the IQuerable.

If you're not using such a 'db' LINQ provider, then you have to earn your professional label and do it the old fashioned way: be clever. You still want to take advantage of LINQ-to-object! Oh yes you do. You can add a GetByCustomer(CustomerID As Int) that takes a customer ID and returns an IEnumerable(Order), and this would return 0 records, or 10 or 50, depending on the records. (Your Customer object would probably have an Orders property that would return the results of this method.) But you don't want to keep adding custom methods for every eventuality. Let's say you often want to get the Customer's latest order; you probably want to add a GetLatestByCustomer(CustomerID as Int).

But what about when you need, just one time, a customers second order. It would be crazy to add a method to you DAL object, GetSecondOrderByCustomer(), right? At this point we're really cluttering up our DAL object. In this case, you could use LINQ against your GetByCustomer() method:

Dim 2ndOrder As Order = (From O in DAL.Orders.GetByCustomer("123")
                            Order By OrderDate).Skip(1).Take(1)

Now, this is pretty clean, clear, and you're not cluttering up your DAL with what we would have to consider a pretty specialized request. Note though that behind the scenes you are getting every order for the given customer! This should not be the end of the world; I'm sure your Orders table has an index by CustomerID, right? But, sometimes you're going to get back 60 order records just to get that 2nd one.

If at some point you have a really crazy query need (get all the orders between two dates, delivered to Milwaukee WI, paid for by credit card, that came in by fax) then you'll probably have to provide a straight-sql option in your DAL. You want to fight to avoid this if you can though, because then you're losing your abstraction. Could you add Orders.GetBetweenDates(FirstDate As date, SecondDate As date). Yeah . . . but see the danger? Some later developer might call that to get all the orders for a couple years and then use LINQ to filter further. This could easily cause a table scan behind the scenes. These are the types of things you have to consider.

You have to be prudent. When you want to abstract (which is good!), you have to make tradeoffs. The attraction of LINQ-to-SQL and such is that you can do a LINQ query similar to the example above and it will just get one record from the db!

Patrick Karcher
A: 

One change I would suggest would be to base the DAL class on an interface and add constructor overloads that would allow you to inject dependencies such as ORM Components, Connections, Commands, etc... This has a number of advantages, with the most important being that you can inject mocks for unit testing in isolation and the DAL itself can be mocked for consuming layer testing.

This is somewhat of a constrived sample, but here is a really basic example of the pattern I can suggesting

Public Interface IDal
    Function GetData(ByVal OrderID As String) As String
End Interface

Public Class Dal : Implements IDal
    Private _connection As IDbConnection

    Public Sub New()
        Me.New(IoC.Resolve(Of IDbConnection))
    End Sub

    Friend Sub New(ByVal Connection As IDbConnection)
        _connection = Connection
    End Sub

    Public Function GetData(ByVal OrderID As String) As String Implements IDal.GetData
        ' Use injected connection and Get the data
    End Function
End Class

Keep in mind, that is just to illustrate the pattern which you can modified as needed. Note the Friend scope of he constructor. This is really only meant to be called by Frield classes and assemblies such as test assemblies, although you could certainly make it public and let consuming layers inject all their own depencies.

That "IoC.Resolve(Of IDbConnection)" bit pulls in the notion of using an Inversion of Control (IoC) container of your choosing although it is not really needed. Just make sure you New-up your dependencies using default concrete implementations if none were injected. Dependency Injection and IoC are really powerfull patterns which are ideal for use in a DAL. Hope this helps.

JoeGeeky