You said in a comment on your question:
The DAL code is more like typical ADO.Net functions used. i.e. Open SQL connection, Define Command, Fill Data Adapter and get DataSet back. Most of the DAL returns either DataSet or DataTable. So, if needs to be re-written, discard and write again
(emphasis, mine)
The first question you should ask is:
What is being done with those DataSet and DataTable objects?
This is your Business Logic Layer!
If your "Business Logic Layer" is just calling "Data Access Code" then you don't have a business logic layer. You have one more layer that does absolutely nothing.
This doesn't mean you don't need a Business Logic Layer. It just means the code using the DataSet
and DataTable
objects enforces your business logic. Everything you are describing in your original post is just data access logic.
Exactly, if DAL function returns dataset or datatable, BL is returning that. If DAL returns bool or int, BL returns same. In short, if you see method signature it is more or less same.
I think this illustrates a misunderstanding of what a "business layer" is. What you have is not a business layer at all, despite it's name. I see this very frequently with ASP.NET WebForms applications, unfortunately.
Yes, you need a "Business Layer".
What you have in your question is not a "Business Layer," and you don't need it.
The code using the DataSet's and DataTable's (probably the code-behind in your Forms and Controls) is your real business layer mixed in with controller behavior as well as presentation behavior in a giant mess of procedural code that isn't utilizing the object oriented language it's written in.
It's like slicing cheese with a chainsaw. Or making confetti with a wood chipper.
Now, there is a design pattern that makes this extra layer between your data access and the rest of the application worthwhile, but you need to abandon the meaningless and non type safe DataSet's in favor of real classes and objects: The Repository Pattern.
I'll use a Blog as an example, because a) it's a simple concept and b) I don't know what entities you are actually using.
First things first. Forget DataSets and DataTables. Let's create an entity class called "Blog" and start using the features of our object oriented language:
public class Blog
{
/// <summary>
/// Initializes a new Blog object that has not been saved yet
/// </summary>
/// <param name="name"></param>
public Blog(string name)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException("name");
Name = name;
}
/// <summary>
/// Initializes a new Blog object that has been previously saved
/// </summary>
/// <param name="id"></param>
/// <param name="name"></param>
public Blog(int id, string name)
: this(name)
{
Id = id;
}
public int Id { get; private set; }
public string Name { get; private set; }
}
Not much business logic is going on here, except that a Blog requires two things: an Id and a name.
What you are calling your "BL" or "Business Layer" should actually be the Blog
class. What you have now could be transformed into a Repository. To facilitate testing, it's usually a good idea to declare an interface for your repository:
public interface IBlogRepository
{
Blog Find(int id);
}
A repository, if you aren't using an ORM like Entity Framework or NHibernate, will need two other pieces: a Gateway and a Factory. This corresponds to two more interfaces, which can make it easier to swap out the exact persistence mechanism should it change (SQL Server to Oracle, or to a Web service or flat file storage). Plus, you can implement caching at different layers of the application later on.
The gateway:
/// <summary>
/// Represents a gateway object that holds the query logic for blogs
/// </summary>
public interface IBlogGateway
{
DataRow Find(int id);
}
Notice that it returns a DataRow
here. Look again at the IBlogRepository
interface and notice that it does not return a generic data object, and instead returns a strongly typed Blog
object. The gateway is responsible for querying the database. This is analogous to your current DAL.
Next up is the "factory" which is responsible for taking the DataRow objects and churning out real Blog
objects. This really is also a Data Mapper, if you want another buzz word (which could be yet another class if you want).
public interface IBlogFactory
{
Blog Create(DataRow row);
}
Now that we've got our interfaces in place, let's take a look at some concrete classes that implement these interfaces.
We'll start with the "repository" since this is the intermediate layer you are calling the "BL" and are questioning it's need for existence. In this design pattern it does have a purpose, and that's to coordinate between the Gateway and the Factory:
public class BlogRepository : IBlogRepository
{
public BlogRepository(IBlogGateway gateway = null, IBlogFactory factory = null)
{
this.gateway = gateway ?? new SqlServerBlogGateway();
this.factory = factory ?? new SqlServerBlogFactory();
}
private IBlogGateway gateway;
private IBlogFactory factory;
public Blog Find(int id)
{
DataRow row = gateway.Find(id);
if (row == null)
return null;
return factory.Create(row);
}
}
Three things to note:
The constructor takes IBlogGateway
and IBlogFactory
objects, which default to null
If null, we fall back to a Sql Server implementation (more on those in a moment)
The Find
method takes the Id, and passes it to the Gateway. The Gateway returns a DataRow, which is then passed to the Factory which returns a Blog
.
Next we will explore the Gateway, since your current DAL seems to fit this piece:
public class SqlServerBlogGateway : IBlogGateway
{
private const string SQL_FIND_BY_ID = "SELECT * FROM [dbo.Blog] WHERE id = @Id";
private string ConnectionString
{
get
{
return System.Configuration.ConfigurationManager.ConnectionStrings["MyApplication"].ConnectionString;
}
}
public DataRow Find(int id)
{
using (var connection = new SqlConnection(ConnectionString))
using (var command = new SqlCommand(SQL_FIND_BY_ID, connection))
{
var adaptor = new SqlDataAdaptor(command);
var data = new DataSet();
command.Parameters.Add(new SqlParameter("Id", id));
connection.Open();
adaptor.Fill(data);
if (data.Tables.Count == 0 || Data.Tables[0].Rows.Count == 0)
return null;
return data.Tables[0].Rows[0];
}
}
}
In the SqlServerBlogGateway
class the Find
method directly interacts with the SQL Server client libraries to execute a query against the database.
The last piece of the puzzle is the factory, which takes a generic data object and returns a strongly typed Blog
object:
public class SqlServerBlogFactory : IBlogFactory
{
public Blog Create(DataRow row)
{
int id = Convert.ToInt32(row["ID"]);
string name = row["NAME"].ToString();
return new Blog(id, name);
}
}
It knows which constructor of the Blog
class to invoke, as well as what the names of the columns are in the database and how to map those to the Blog object.
And of course some hypothetical code using the Repository:
IBlogRepository blogs = new BlogRepository(); // Defaults to SQL Server
// Now we have a real object to use, instead of a meaningless,
// generic data structure with no compile time checks:
Blog blog = blogs.Find(123);
We can even introduce a layer of caching:
public CachedBlogGateway : IBlogGateway
{
public CachedBlogGateway(IBlogGateway innerGateway)
{
this.innerGateway = innerGateway;
this.cache = new Dictionary<int, DataRow>();
}
private IBlogGateway innerGateway;
private Dictionary<int, DataRow> cache;
public DataRow Find(int id)
{
if (cache.ContainsKey(id))
return cache[id];
DataRow row = innerGateway.Find(id);
if (row != null)
cache[id] = row;
return row;
}
}
Now to use this in-memory cache:
IBlogRepository blogs = new BlogRepository(
new CachedBlogGateway(new SqlServerBlogGateway()));
// Hit the database for this one:
Blog blog1 = blogs.Find(123);
// Hit the cache for this one:
Blog blog2 = blogs.Find(123);
While this was a long answer, I hope it addresses the main issue here, which is if your "BL" is needed, and how this intermediate layer could be used to do something more than just be another layer of the application.