Wrox Programmer Forums
Go Back   Wrox Programmer Forums > .NET > .NET 2.0 and Visual Studio. 2005 > Visual Studio 2005
|
Visual Studio 2005 For discussing Visual Studio 2005. Please post code questions about a specific language (C#, VB, ASP.NET, etc) in the correct language forum instead.
Welcome to the p2p.wrox.com Forums.

You are currently viewing the Visual Studio 2005 section of the Wrox Programmer to Programmer discussions. This is a community of software programmers and website developers including Wrox book authors and readers. New member registration was closed in 2019. New posts were shut off and the site was archived into this static format as of October 1, 2020. If you require technical support for a Wrox book please contact http://hub.wiley.com
 
Old April 30th, 2009, 06:18 PM
edurazee
Guest
 
Posts: n/a
Default Layered/Tiered Architecture complexity...

I am working hard to standardize one single way of Layered/n-Tiered design of my all applications.

I am trying to make all my applications 5 tiered.

Code:
--------------------
|        UI        |
--------------------
         |
--------------------
| Business Object  |
--------------------
         |
--------------------
|    OR-Mapper     |
--------------------
         |
--------------------
|    Data Access   |
--------------------
         |
--------------------
|      RDBMS       |
--------------------
Suppose I am developing an application with a log-in/log-out capability for users. I am creating 4 projects under a VS2005 solution. Each project is for one of the upper 4 layers.

I am designing my Business Object class as follows:-

Code:
public class User
{
    private string _usercode;
    public string UserCode
    {
        get { return _usercode; }
        set { _usercode = value; }
    }

    private DateTime _regDate;
    public DateTime RegDate
    {
        get { return _regDate; }
        set { _regDate = value; }
    }

    private string _username;
    public string Username
    {
        get { return _username; }
        set { _username = value; }
    }

    private string _password;
    public string Password
    {
        get { return _password; }
        set { _password = value; }
    }

    private string _userTypeCode;
    public string UserTypeCode
    {
        get { return _userTypeCode; }
        set { _userTypeCode = value; }
    }

    private string _loginMessage;
    public string LoginMessage
    {
        get { return _loginMessage; }
    }


    public User()
    {
    }

    public bool LogIn(String username, String password)
    {
        bool success = false;

        if (RegisteredUserMapper.UsernameExists(username))
        {
            success = RegisteredUserMapper.UsernamePasswordExists(username, password);

            if (success)
            {
                _loginMessage = "Login was successful!";
            }
            else
            {
                _loginMessage = "Password mismatch!";
            }
        }
        else
        {
            _loginMessage = "Username does not exist!";
        }

        return success;
    }

    public bool LogOut()
    {
           bool success;
        //----some logic
           return success;
    }

    public static User GetUserByUsername(string username)
    {
        return UserMapper.GetUserByUsername(username);
    }

    public static UserCollection GetByUserTypeCode(string code)
    {
        return UserMapper.GetByUserTypeCode(code);
    }
}
This is how I am giving my objects some functionality that matches the real-world scenario. Here GetByUsername() and GetByUserTypeCode() are getter functions. These functions does't match a real-world logic. Coz, in real-world, a User never "Gets by Username" or "Gets by UserTypeCode". So these functions are kept static.

My class for O-R Mapper layer is as follows:-

Code:
public static class UserMapper
{
    public static bool UsernameExists(String username)
    {
        bool exists = false;

        if (UserDA.CountUsername(username) == 1)
        {
            exists = true;
        }

        return exists;
    }

    public static bool UsernamePasswordExists(String username, String password)
    {
        bool exists = false;

        if (UserDA.CountUsernameAndPassword(username, password) == 1)
        {
            exists = true;
        }

        return exists;
    }

    public static User GetUserByUsername(string username)
    {
        User user = null;

        SqlDataReader dataReader = UserDA.GetUserByUsername(username);

        if (dataReader != null)
        {
            if (user == null)
            {
                user = new User();
            }

            while (dataReader.Read())
            {
                user.Username = dataReader.GetString(1);
                user.UserCode = dataReader.GetString(2);
                user.Password = dataReader.GetString(3);
                user.RegDate = dataReader.GetDateTime(4);
                user.UserTypeCode = dataReader.GetString(5);
            }

            dataReader.Close();
        }

        return user;
    }

    public static UserCollection GetByUserTypeCode(string code)
    {
        UserCollection users = null;

        SqlDataReader dataReader = UserDA.GetUserByUserTypeCode(code);


        if (dataReader != null)
        {
            if (users == null)
            {
                users = new UserCollection();
            }
            User user = null;
            while (dataReader.Read())
            {
                user = new User();

                user.Username = dataReader.GetString(1);
                user.UserCode = dataReader.GetString(2);
                user.Password = dataReader.GetString(3);
                user.RegDate = dataReader.GetDateTime(4);
                user.UserTypeCode = dataReader.GetString(5);

                users.AddItem(user);
            }

            dataReader.Close();
        }

        return users;
    }
}
And finally, the DA class is as follows:-

Code:
public static class UserDA
{
    #region Checks the availability of a Username
    public static int CountUsername(string username)
    {
        int count = -1;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT COUNT(*) 
                                FROM User 
                                WHERE User_name = @User_name";
                command.Parameters.AddWithValue("@User_name", username);

                command.Connection.Open();
                object idRaw = command.ExecuteScalar();
                command.Connection.Close();

                if (idRaw == DBNull.Value)
                {
                    count = 0;
                }
                else
                {
                    count = (int)idRaw;
                }
            }
            catch (Exception ex)
            {
                count = -1;
            }
        }

        return count;
    }
    #endregion Checks the availability of a Username

    #region Checks the availability of a Username OR Code/Roll
    public static int CountUsernameOrCodeRoll(string username, string codeOrRoll)
    {
        int count = -1;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT COUNT(*) 
                                FROM User 
                                WHERE User_name = @User_name
                                OR ActualCodeOrRoll = @ActualCodeOrRoll";
                command.Parameters.AddWithValue("@User_name", username);
                command.Parameters.AddWithValue("@ActualCodeOrRoll", codeOrRoll);

                command.Connection.Open();
                object idRaw = command.ExecuteScalar();
                command.Connection.Close();

                if (idRaw == DBNull.Value)
                {
                    count = 0;
                }
                else
                {
                    count = (int)idRaw;
                }
            }
            catch (Exception ex)
            {
                count = -1;
            }
        }

        return count;
    }
    #endregion Checks the availability of a Username OR Code/Roll

    public static int CountUsernameAndPassword(string username, string password)
    {
        int count = 0;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT COUNT(*) 
                                FROM User 
                                WHERE User_name = @User_name AND Pass_word = @Pass_word";
                command.Parameters.AddWithValue("@User_name", username);
                command.Parameters.AddWithValue("@Pass_word", password);

                command.Connection.Open();
                object idRaw = command.ExecuteScalar();
                command.Connection.Close();

                if (idRaw == DBNull.Value)
                {
                    count = 0;
                }
                else
                {
                    count = (int)idRaw;
                }
            }
            catch (Exception ex)
            {
                count = 0;
            }
        }

        return count;
    }

    public static int InsertUser(params object[] objects)
    {
        int count = -1;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"INSERT INTO User(ID, User_name, Pass_word, RegDate, UserTypeCode, ActualCodeOrRoll) 
                                                            VALUES(@ID, @User_name, @Pass_word, @RegDate, @UserTypeCode, @ActualCodeOrRoll)";
                command.Parameters.AddWithValue("@ID", objects[0]);
                command.Parameters.AddWithValue("@User_name", objects[1]);
                command.Parameters.AddWithValue("@Pass_word", objects[2]);
                command.Parameters.AddWithValue("@RegDate", objects[3]);
                command.Parameters.AddWithValue("@UserTypeCode", objects[4]);
                command.Parameters.AddWithValue("@ActualCodeOrRoll", objects[5]);

                command.Connection.Open();
                count = command.ExecuteNonQuery();
                command.Connection.Close();
            }
            catch (Exception ex)
            {
                count = -1;
            }
        }

        return count;
    }

    public static SqlDataReader GetUserByUsername(string username)
    {
        SqlDataReader dataReader = null;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT * FROM User WHERE User_name = @User_name";
                command.Parameters.AddWithValue("@User_name", username);

                command.Connection.Open();

                dataReader = command.ExecuteReader(CommandBehavior.CloseConnection);

            }
            catch (Exception ex)
            {
                dataReader.Close();
                dataReader.Dispose();
            }
        }

        return dataReader;
    }

    public static SqlDataReader GetUserByUserTypeCode(string userTypeCode)
    {
        SqlDataReader dataReader = null;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT * FROM User WHERE UserTypeCode = @UserTypeCode";
                command.Parameters.AddWithValue("@UserTypeCode", userTypeCode);

                command.Connection.Open();

                dataReader = command.ExecuteReader(CommandBehavior.CloseConnection);

            }
            catch (Exception ex)
            {
                dataReader.Close();
                dataReader.Dispose();
            }
        }

        return dataReader;
    }
}
If anyone closely examine the these classes he can understand that, O-R Mapper layer needs the reference of BusinessObject-layer. BusinessObject- layer also needs a reference of O-R Mapper-layer.

This should create a circular dependency.

How can I avoid this problem?

N.B. I don't know whether it is an ideal 5-tiered application or not, I have found some logic in favor of this design.
 
Old May 3rd, 2009, 03:48 AM
Imar's Avatar
Wrox Author
 
Join Date: Jun 2003
Posts: 17,089
Thanks: 80
Thanked 1,576 Times in 1,552 Posts
Default

Hi there,

One solution is to create yet another layer with plain data objects, also called DTOs or Data Transfer Objects.

You could put them in a BusinessEntities namespace and they would contain just the data, like this:

Code:
 
public class Person
{
  public string FirstName { get; set; }
  public string LastName { get; set; }        
}
Then both your BusinessObject Layer and the OR-Mapper layer can have a reference to this separate project, removing the circular reference.

You can do pretty much the same using interfaces: define a separate interface, implement it in your data class in the BusinessObject and program against your interface in the BusinessObject and in the OR-Mapper layer.

Take a look here for some ideas on implementing this: http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=476

Hope this helps,

Imar
__________________
Imar Spaanjaars
http://Imar.Spaanjaars.Com
Follow me on Twitter

Author of Beginning ASP.NET 4.5 : in C# and VB, Beginning ASP.NET Web Pages with WebMatrix
and Beginning ASP.NET 4 : in C# and VB.
Did this post help you? Click the button below this post to show your appreciation!
The Following User Says Thank You to Imar For This Useful Post:
 
Old May 3rd, 2009, 10:21 AM
edurazee
Guest
 
Posts: n/a
Default Layered/Tiered Architecture complexity (AGAIN...)

Quote:
Originally Posted by Imar View Post
Hi there,

One solution is to create yet another layer with plain data objects, also called DTOs or Data Transfer Objects.

You could put them in a BusinessEntities namespace and they would contain just the data, like this:

Code:
 
public class Person
{
  public string FirstName { get; set; }
  public string LastName { get; set; }        
}
Then both your BusinessObject Layer and the OR-Mapper layer can have a reference to this separate project, removing the circular reference.

You can do pretty much the same using interfaces: define a separate interface, implement it in your data class in the BusinessObject and program against your interface in the BusinessObject and in the OR-Mapper layer.

Take a look here for some ideas on implementing this: http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=476

Hope this helps,

Imar
Thanks a lot for your reply.

Personally I don't like the idea of Data Transfer Objects(DTO). Coz this technique don't match OOP fundamentals at all.

But can you please tell me something about my view of Object-Orientation in my design (plz. recall the placing of two static methods in the "User"-class)?

Can you suggest any better way of placing those to "Getter" methods in the application?

Thanks again.
 
Old May 3rd, 2009, 10:26 AM
Imar's Avatar
Wrox Author
 
Join Date: Jun 2003
Posts: 17,089
Thanks: 80
Thanked 1,576 Times in 1,552 Posts
Default

I don't agree that DTOs "don't match OOP fundamentals at all". Why would that be the case?

Your question was about avoiding the circular references. DTOs or Interfaces can remove them. Is that an option for you?

The static methods in your class make sense to me. Why wouldn't they? The provide easy access to common functionality without the need for an instance.

Did you check the article series I linked to? It shows a way to move the static methods from the User class into a UserManager class.

Cheers,

Imar
__________________
Imar Spaanjaars
http://Imar.Spaanjaars.Com
Follow me on Twitter

Author of Beginning ASP.NET 4.5 : in C# and VB, Beginning ASP.NET Web Pages with WebMatrix
and Beginning ASP.NET 4 : in C# and VB.
Did this post help you? Click the button below this post to show your appreciation!
The Following User Says Thank You to Imar For This Useful Post:
 
Old May 3rd, 2009, 10:50 AM
edurazee
Guest
 
Posts: n/a
Default Layered/Tiered Architecture complexity (AGAIN-2)

Quote:
Originally Posted by Imar View Post
I don't agree that DTOs "don't match OOP fundamentals at all". Why would that be the case?

Your question was about avoiding the circular references. DTOs or Interfaces can remove them. Is that an option for you?

The static methods in your class make sense to me. Why wouldn't they? The provide easy access to common functionality without the need for an instance.

Did you check the article series I linked to? It shows a way to move the static methods from the User class into a UserManager class.

Cheers,

Imar
Thanks for your opinion.

I like the idea of Interfaces. It makes much sense to me.

As far as I know, according to OOP, attributes and functionality of a real-world object should be grouped together as a class. If I use DTO then how can I encapsulate functionality into a class? Moreover I am creating another class without any attribute (BO). To me that is breach of OOP in both ways. If I do so, then what is OOP for in this world?

The same answer can be applied for "UserManager" classes.
 
Old May 3rd, 2009, 11:00 AM
Imar's Avatar
Wrox Author
 
Join Date: Jun 2003
Posts: 17,089
Thanks: 80
Thanked 1,576 Times in 1,552 Posts
Default

I am not so rigid about that. You can have DTOs and Manager classes and still use all good things from OO including inheritance, polymorhism, encapsulation and so on.

In your case, I think interfaces are the way to handle it.

BTW: can you please not quote the entire message when replying? That adds a lot of unnecessary noise to the thread.

Cheers,

Imar
__________________
Imar Spaanjaars
http://Imar.Spaanjaars.Com
Follow me on Twitter

Author of Beginning ASP.NET 4.5 : in C# and VB, Beginning ASP.NET Web Pages with WebMatrix
and Beginning ASP.NET 4 : in C# and VB.
Did this post help you? Click the button below this post to show your appreciation!
The Following User Says Thank You to Imar For This Useful Post:
 
Old May 3rd, 2009, 11:04 AM
edurazee
Guest
 
Posts: n/a
Default

Thanks a lot.

Hoping for more problem-solving answers in future.





Similar Threads
Thread Thread Starter Forum Replies Last Post
Removal of same tiered nodes, based on text overcast XSLT 2 August 19th, 2008 12:43 PM
Kindly help for datagrid complexity avixorld ASP.NET 2.0 Professional 0 July 30th, 2007 02:09 AM
Layered Applications Muhammad Zeeshan General .NET 5 July 25th, 2007 04:08 AM
Two tiered collections (user-defined) sholom VB How-To 0 November 26th, 2003 02:51 PM





Powered by vBulletin®
Copyright ©2000 - 2020, Jelsoft Enterprises Ltd.
Copyright (c) 2020 John Wiley & Sons, Inc.