views:

1555

answers:

7

Hello Everyone,

I currently have an application which consists of: User Interface (web page) BLL (Manager & Domain Objects) DAL (DataAccess class for each of my Domain Objects).

I use the following in the UI to search for a domain object.

protect sub Button1_Click()
{
    IBook book = BookManager.GetBook(txtID.Text);
}

Here is my BLL

public class BookManager 
{
    public static IBook GetBook(string bookId)
    {
        return BookDB.GetBook(bookId);
    }
}

public class Book : IBook
{
    private int? _id
    private string _name;
    private string _genre;

    public string Name
    {
        get { return _name; }
        private set 
        {
            if (string.IsNullOrEmpty(value))
                throw new Exception("Invalid Name");
            _name = value;
        }
    }

    public string Genre
    {
        get { return _serial; }
        private set 
        {
            if (string.IsNullOrEmpty(value))
                throw new Exception("Invalid Genre");
            _genre = value;
        }
    }

    // Other IBook Implementations

}

And finally here is my DAL

public class BookDB
{
    public static IBook GetBook(int id)
    {
        // Get Book from database using sproc (not allowed to use any ORM)
        // ?? Create IBook Item?
        // return IBook
    }

How would one create a IBook Object and return it to the Manager? I'm thinking of returning a DataTable from BookDB to BookManager and having it create the Book Object and return it, but that doesn't seem right. Is there another way to do this?

Edit: I decided to seperate each layer into a project and ran into a circular dependency problem in the DAL layer when trying to add a reference to the BLL. I can't access the Book Class or Interface or anything in BLL from DAL. Should i just use ado.net objects here and have my manager create the actual object from the ado.net object? Here's how its layed out

BLL.Managers - BookManager
BLL.Interfaces IBook
BLL.Domain - Book
DAL - BookDB.

Thanks!

A: 

The DataTable you want to return is database related, and for BLL, it shouldn't care about what database you are using and what the schema is. You may use a DB-Object Mapper to map the dbtable to an object in DAL.

Colin Niu
DataTable isn't quite database-related. It is specific to the Relational model, but represents an in-memory, disconnected "table", and is not connected to any database. Neither does it expose information about any database that may have filled it.
John Saunders
A: 

If you don't want to return a DataTable, you can pass in an IBook implementation from BookManager for the DAL to populate.

Mufaka
+3  A: 

You could create dummy Book objects that contain only data. Get, set properties and member values. This book, has 1 property for each field in the database, but doesn't validate anything.

You fill the object from the db, then send it to the BLL.

When you want to save the object, you also send it to the BLL.

Your classes in the BLL could wrap aroud those objects, if that makes sense. This way, it is easy to just send it back to the DAL.

Dummy Book:

public class DummyBook:IBook 
{
    private nullable<int> _id;
    private string _name;
    private string _genre;

    public string Id
    {
        get {return _id;}
        set {_id = value;}
    }

    public string Name 
    {
        get {return _name;}
        set {_name = value;}
    }

    public string Genre 
    {
        get {return _genre;}
        set {_genre= value;}
    }

}

DAL Book:

public class DALBook 
{
    public static IBook:GetBook(int id) 
    {
        DataTable dt;
        DummyBook db = new DummyBook();

        // Code to get datatable from database
        // ...
        // 

        db.Id = (int)dt.Rows[0]["id"];
        db.Name = (string)dt.Rows[0]["name"];
        db.Genre = (string)dt.Rows[0]["genre"];

        return db;
    }

    public static void SaveBook(IBook book) 
    {
        // Code to save the book in the database
        // you can use the properties from the dummy book
        // to send parameters to your stored proc.
    }
}

BLL Book:

public class Book : IBook
{
     private DummyBook _book;

     public Book(int id) 
     {
         _book = DALBook.GetBook(id);
     }

     public string Name 
     {
         get {return _book.Name;}
         set 
         {
            if (string.IsNullOrEmpty(value))
            {
                throw new Exception("Invalid Name");
            }
            _book.Name = value;
         }
     }

     // Code for other Properties ...



     public void Save()
     {
         // Add validation if required
         DALBook.Save(_book);
     }

}

Edit1: The dummy classes should go in their own project(Model, just as stated in the comments is fine). The references would work as follow:

The DAL References the Model Project.
The BLL References the Model and the DAL.
The UI References the BLL.

Martin
Which layer would you put the DummyBook in?Right now i have three projects. 1) Web Project (Contains Reference to BLL)2) BLL Class Library (Contains Reference to DAL)3) DAL Class Library (Contains Reference to BLL)
AlteredConcept
@AlteredConcept: you may create another Model project or such
smoothdeveloper
A: 

I would probably use ExecuteReader to create an object in code from the database. The reason for this is that the datatable has more overhead than a reader, because it has more functionality (and was probably created by a reader). Since you aren't doing updates/deletes using the datatable, you don't need the overhead.

That being said, I would make a static helper method in the BookManager class.

internal static IBook BookFromReader(IDataReader reader)
{
     Book B = new Book();
     B.Prop = reader.GetString(0);
     B.Rinse = reader.Repeat();
     return B;
}

The reason for this is because the reason you have an interface is because you might want to change the implementation. You may eventuallu have INovel : IBook, IReference : IBook etc and then you'll want to have an abstract factory implementation in your data layer.

public static IBook GetBook(int id)
{
    // SqlCommand Command = new Command("SQL or sproc", ValidConnection);

    using(IDataReader DR = Command.ExecuteReader(id))
    {
        // checking omitted
        switch(DR.GetInt32(1))
        {
            case 0:
                 return BookManager.BookFromReader(DR);
            case 1:
                 return BookManager.NovelFromReader(DR);
            etc
        }
    }
}

Another benefit of the DAL here is that you can cache lookups. You can have a Dictionary that holds books you've looked up, to reduce extra db calls on objects you've already returned. When an update takes place, you remove the cached entity... That's another post though.

If you're using multiple assemblies, interfaces and helper methods will need to reside in a neutral (non-dependent) assembly. Right now in the blog-o-sphere, there is movement towards less assemblies, which means less dependencies, etc.

Here is a link from a blog I read on this topic: http://codebetter.com/blogs/patricksmacchia/archive/2008/12/08/advices-on-partitioning-code-through-net-assemblies.aspx

Ultimately, I think the answer is that the data layer returns an instance of your interface to the business layer.

Good luck :-)

Ben
Thanks ben. That helps. For some odd reason I thought that since BookManager Calls BookDB.GetBook, it would seem a bit weird to call it from within within the BookDB.GetBook Method. Don't know why i thought that.Thanks again!
AlteredConcept
You could put the static helper method in the DAL or BLL. I suspect I'll be flamed for mentioning placement in the BLL. I pick the BLL because the DAL could be "swapped out" while the helper methods still applied...
Ben
I thought I had the answer here, I'm surprised to see the -1. Would the -1 please elaborate via comment?
Ben
I didn't vote you down. But i did run into a problem when doing this. Check the edit portion in the original entry.
AlteredConcept
Robert C. Barth
Further, if you modify the layout of the returned data from the storage provider, you'd need to rewrite both the BLL and DAL having the static method in the BLL.
Robert C. Barth
I like having helper methods in the BLL. Yes, getting data from SQL should be in the DAL, but the act of converting an IDataReader to a business object can reside in BLL as a helper object. Why? Because even if you switch to an XML DAL, you might still have the ad hoc IDataReader to object need.
Ben
+2  A: 

BookDB should return the IBook instance. I like the repository pattern, which is all about mapping from the db to the domain.

The repository implementation returns instances of the domain objects. This shields the rest of the code from the particular persistence implementation, which can be affected by the technology (database type, web service, [insert something else]) and the format used to save the data.

eglasius
How would you structure the above to use the repository pattern without an orm?
AlteredConcept
A: 

In my opinion you should never let DAL access BLL. That is an unnecessarily dependency.

Putting the Book class into a new project (perhaps named DomainModel) will fix the circular reference. You could do something like this:

Project BLL reference DAL and DomainModel

Project DAL reference DomainModel

Project UI reference BLL and DomainModel

Project DomainModel reference nothing

Mikael
A: 

To follow the intended model. the Data Access Layer (DAL) is responsible for retrieving and sending data from and to the data source.

The DAL must not care about any of the business entities your BLL is using as its only job is to retrieve data and return it in a neutral object. It must be neutral for generic reuability, otherwise you might as well not separate the layers as you are defiting its purpose.

Your Business Logic Layer (BLL) must not care how the DAL achieves retrieveing or writing data.

To communicate between the BLL and the DAL you must use neutral objects.

Your BLL passes an object's properties as individual paramters to the methods in the DAL. the parameters in the DAL are neutral using strings, int, bool, any other .NET objects which are neither specific to a version of the database you are communicating with nor are specific types only existing in your BLL.

The DAL will retrieve the data from where ever by what ever means and return a neutral data object to the caller. This for example could be a DataSet or DataTable or any other object NOT specific to a database type/version your are using. Hence DataSet and DataTable are objects within the System.Data namespace and not the System.Data.SQL,etc... namespace.

In essence: - BLL passes neutral types to the DAL (e.g.: string, int, bool, long,float, etc..) - DAL is responsible for converting those types to database specifci types if required before passing them on to the data source DAL returns neutral data types to the BLL (e.g.: DataSet, DataTable,etc..) - BLL is responsible for using the content of those neutral data types to create, populate and return specifci Business Entities

Your BLL must reference your DAL. that's it.

You can off course completly ignore this model and hack about as many suggested previously using IBOOK,etc... but than your are not using the intended model and might as well throw it all into a single assembly as you won't be able to maintain it independantly anyway.

Belayed Suggestion
I'm quoting wiki here: "[The data] tier keeps data neutral and independent from application servers or business logic." Giving the DAL Business Objects that are "dumb" doesn't contradict this. As long as your business objects aren't validating themselves and are just a store of basic data types wrapped in a class, it's better to use them. You gain nothing if you do Employee_DAL.Save(myEmployee.Id, myEmployee.Name, myEmployee.JobTitle) vs. Employee_DAL.Save(myEmployee). The second is much cleaner.
colithium