Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Generic DAL Revisit - Making it Your Own, A Practical Example

0.00/5 (No votes)
9 Apr 2020CPOL5 min read 4.8K  
When you're using other people's code, especially code that might get updated from time to time, it's important to remember that you can probably extend it and make it in your own image, without necessarily changing the original code, and by using the power of object-oriented programming.
In this article we go through adapting the connection string, we define the prototype of the event method, and go through adapting the DAL, and setting up the BLL.

Preface

When you use code from a CodeProject article, you may be among those that assume it will fit your needs precisely, and when it doesn't, you go running to the author. If you're lucky, the author is keeping an eye on the messages posted for the article, and if you're even luckier, he might even help you with the problem that YOU should have been able to fix on your own.

If you are someone like this, it is my opinion (and not a humble one) that you should cowboy up, and figure it out on your own. Google is a wonderful resource, and you can find the answer to almost ANY programming question you might come up with. This article is about self-sufficiency - how to muscle through using someone else' code without waving your panties for help.

NOTE: There is no source file associated with this article, as all the code is in the article proper, and in point of fact, the entire connection string class discussed herein is presented in its entirety at the end of the article.

Introduction

After writing the acode presented in this article - Generic DAL for ADO.Net - A Revisit [^], I started to "eat my own dog food" (use the code that I made available for others). The code in that article makes no assumptions about how you might want to use it, and most of the aspects of the code are implemented in such a way as to protect you from hurting yourself (or the code).

Adapting the Connection String

I needed a way to change one or more properties in the PWConnectionString object that are used to construct the connection string. However, the properties in question are protected, which means I needed to create a new class, and inherit the original PWConnectionString class. That was easy enough, but the BLL object didn't know how to get the new connection string if something changed.

Custom Events to the Rescue!

NOTE: All of the code presented in this section are put into the new connection string class.

My new connection string class needed to invoke a custom event that the BLL could subscribe to. First, we need to define a class derived from EventArgs.

C#
public enum ConnStringChangedItem { Server, Database, UserID, Password, EncryptTraffic };
public class ConnStringArgs : EventArgs
{
    /// <summary>
    /// Get/set the enumerated property name that changed
    /// <summary>
    public ConnStringChangedItem Changed  { get; set; }
    /// <summary>
    /// Get/set the old value of the property
    /// <summary>
    public string                OldValue { get; set; }
    /// <summary>
    /// Get/set the new value of the property
    /// <summary>
    public string                NewValue { get; set; }

	/// <summary>
	/// Constructor
	/// <summary>
    public ConnStringArgs(ConnStringChangedItem changed, string oldValue, string newValue)
    {
        this.Changed = changed;
        this.NewValue = newValue;
    }
}

I have to say that I don't actually need to know what the new or old values are, or even which property changed - right now. However, there may come a time that I *do* want to know those things, so I put them in the event arguments object so I don't have to do it later. This is what I call "defensive programming" - preparing for requirements that may reasonably surface in the future. It's a good practice to get into, and can cause you to be more introspective about your code, causing you to think more about how you approach a given programming task.

In the Class

First, we need to define the prototype of the event method. The method that handles the event needs to have the indicated return type, and accept the specified parameters.

C#
/// <summary>
/// The event handler delegate
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public delegate void ConstringChangeHandler(object sender, ConnStringArgs e);

Next, we have to add the delegate method. This method creates the argument object, and then invokes the event delegate.

C#
/// <summary>
/// Delegate method that invokes the event
/// </summary>
/// <param name="changed">What property changed</param>
/// <param name="oldValue">The old property value</param>
/// <param name="newValue">The new property value</param>
protected void Changed(ConnStringChangedItem changed, string oldValue, string newValue)
{
    // Make sure someone is listening to event
    if (OnChanged != null)
    {
        ConnStringArgs args = new ConnStringArgs(changed, oldValue, newValue);
        this.OnChanged(this, args);
    }
}

Finally, I had to add public methods (remember, the base class properties are protected, so we have to do it this way) that allow us to actually change the properties. After changing the property, the Changed event is invoked. Since all of these methods are essentially the same, I'll just present one in this section.

C#
/// <summary>
/// Change the database (catalog) name
/// </summary>
/// <param name="name">The new name to assign</param>
public void ChangeDatabase       (string name)  
{
    string oldValue = this.Database;
    this.Database   = name; 
	// invoke the event   
    this.Changed(ConnStringChangedItem.Database, oldValue, this.Database); 
}

Adapting the DAL

You should know by now, that you create a BLL object that inherits the DAL, and that you implement your actual database accessor methods in it. Like the connection string class, the DAL is very narrowly focused and most of the properties and methods are protected. One aspect of the <code>DAL is that it stores the connection string as a string, which makes responding to our new event impossible without some changes.

Setting Up the BLL

In order to be able to subscribe to a connection string event, we need to add a property for it.

C#
public partial class BLL : DAL
{
	/// <summary>
	/// Get/set the FactoryConnectionString object
	/// </summary>
	public FactoryConnectionString EFConnString { get; set; }

Next, we have to provide constructors to satisfy the base DAL class requirements. The first constructor is normal and simply uses the string provided as the connection string. If you use this constructor, you will not be able to utilize the new event feature we just added.

C#
/// <summary>
/// Init the BLL with a regular (optionally base64-encoded) string.
/// </summary>
/// <param name="connstr"></param>
public BLL(string connstr) : base(connstr)
{
    this.EFConnString = null;
}

Things get much more serious with the second constructor. This one accepts a connection string object. When we use this constructor, it calls the base matching constructor and sets the object's ConnectionString property (which is a base64 encoded string), and sets its own EFConnString property to the object. Once that's done, we add an event handler for the OnChanged event.

Notice I've implemented a destructor for the BLL. The reason is that if you add a custom event handler, you have to release it when the containing object goes null.

C#
/// <summary>
/// Init BLL with a connectionstring object, and handle changed event
/// </summary>
/// <param name="connStr"></param>
public BLL(FactoryConnectionString connStr) : base(connStr)
{
    this.EFConnString = connStr;
    // add our event handler
    this.EFConnString.OnChanged += this.EFConnString_OnChanged;
    // the DAL only knows about connection *strings*, and that forces us to handle the 
    // inevitable changes to the connection string in this app.
    this.ConnectionString = this.EFConnString.ConnectionString;
}

/// <summary>
/// Destructor (for cleanup)
/// </summary>
~BLL()
{
    // when you add an event handler for a custom event, you are also responsible for 
    // cleaning it up.
    if (this.EFConnString != null)
    {
        this.EFConnString.OnChanged -= this.EFConnString_OnChanged;
    }
}

Finallty, we add the handler for the event. The BLL simply resets it's ConnectionString property to the newly constructed EFConnString.ConnectionString property value. Like I stated before, I don't need anything from the ConnStringArgs parameter right now, so all we do is reset the base class' ConnectionString property.

C#
/// <summary>
/// Event handler for connectionstring OnChange event.
/// </summary>
/// <param name="sender">The FactoryConnectionString object that changed</param>
/// <param name="e">Contains the item that was changed, and the old and new (string) values.</param>
private void EFConnString_OnChanged(object sender, ConnStringArgs e)
{
    // if the connectionstring used by this bll object changed, we 
    // need to reset the ConnectionString property in this object.
    this.ConnectionString = this.EFConnString.ConnectionString;
}

Using the Code

In my test app, I first create a connection "stringleton", and a bll singleton.

C#
private static FactoryConnectionString EFConnString { get { return FactoryConnectionStringleton.Instance.EFConnString; } }
private static BLL BLL { get { return BLLSingleton.Instance.BLL; } }
static void Main(string[] args)
{
    FactoryConnectionStringleton.Instance.Init("Test", "localhost", "ABCDEFG");
    BLLSingleton.Instance.Init(EFConnString);

    EFConnString.ChangeDatabase("HIJKLMNOP");

    string test = BLL.ConnectionString.Base64Decode();

    // set a breakpoint on the following curly brace and inspect the 
    // [test] variable. It should reflect the database name you changed 
    // it to (usingn the example above, it should be "HIJKLMNOP")
}

Points of Interest

When you're using other people's code, especially code that might get updated from time to time, it's important to remember that you can probably extend it and make it in your own image, without necessarily changing the original code, and by using the power of object-oriented programming. This means you have to know OOP basics, and to apply them in the language of choice, and exploit the features of the .Net framework. By the way, this same code should be perfectly compatible with .Net Core as well.

The connection string and BLL classes in their entirety:

C#
/// <summary>
/// Our custom connectionstring class inherited from PWConnectionString
/// </summary>
public class FactoryConnectionString : PWConnectionString
{
    /// <summary>
    /// The event handler delegate 
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public delegate void ConstringChangeHandler(object sender, ConnStringArgs e);

    /// <summary>
    /// The event method 
    /// </summary>
    public event ConstringChangeHandler OnChanged;

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="name">The name of the connectionsstring object</param>
    /// <param name="server">The name of the server</param>
    /// <param name="database">The name of the initial database</param>
    /// <param name="encryptTraffic">Whether to encrypt traffic</param>
    /// <param name="uid">The user id if using sql server credentials</param>
    /// <param name="pwd">The password if using sql server credentials</param>
    /// <remarks>If using Sql Server credentials, you must specify BOTH a userid AND a password.</remarks>
    public FactoryConnectionString(string name, string server, string database, bool encryptTraffic=false, string uid="", string pwd="") 
        : base(name, server, database, encryptTraffic, uid, pwd)
    {
    }

    /// <summary>
    /// Delegate method that invokes the event
    /// </summary>
    /// <param name="changed">What property changed</param>
    /// <param name="oldValue">The old property value</param>
    /// <param name="newValue">The new property value</param>
    protected void Changed(ConnStringChangedItem changed, string oldValue, string newValue)
    {
        // Make sure someone is listening to event
        if (OnChanged != null)
        {
            ConnStringArgs args = new ConnStringArgs(changed, oldValue, newValue);
            this.OnChanged(this, args);
        }
    }

    /// <summary>
    /// Change the database (catalog) name
    /// </summary>
    /// <param name="name">The new name to assign</param>
    public void ChangeDatabase       (string name)  
    {
        string oldValue = this.Database;
        this.Database   = name;    
        this.Changed(ConnStringChangedItem.Database, oldValue, this.Database); 
    }

    /// <summary>
    /// Change the server name
    /// </summary>
    /// <param name="name">The new name to assign</param>
    public void ChangeServer         (string name)  
    { 
        string oldValue = this.Server;
        this.Server     = name;    
        this.Changed(ConnStringChangedItem.Server, oldValue, this.Server);
    }

    /// <summary>
    /// Change the userID 
    /// </summary>
    /// <param name="uid">The new user id to assign</param>
    public void ChangeUserID         (string uid)   
    { 
        string oldValue = this.UserID;
        this.UserID         = uid;     
        this.Changed(ConnStringChangedItem.UserID, oldValue, this.UserID);
    }

    /// <summary>
    /// Change the password name
    /// </summary>
    /// <param name="pwd">The new password to assign</param>
    public void ChangePassword       (string pwd)   
    { 
        string oldValue = this.Password;
        this.Password       = pwd;     
        this.Changed(ConnStringChangedItem.Password, oldValue, this.Password);
    }

    /// <summary>
    /// Change the encrypt traffic) value
    /// </summary>
    /// <param name="encrypt">The new value to assign</param>
    public void SetTrafficEncryption (bool encrypt) 
    {
        string oldValue = this.EncryptTraffic.ToString();
        this.EncryptTraffic = encrypt; 
        this.Changed(ConnStringChangedItem.EncryptTraffic, oldValue, this.EncryptTraffic.ToString());
    }

}
C#
public partial class BLL : DAL
{
    /// <summary>
    /// Get/set the FactoryConnectionString object
    /// </summary>
    public FactoryConnectionString EFConnString { get; set; }
    /// <summary>
    /// Get the flag that indicates whether you can change the connection string
    /// </summary>
    public bool CanChangeConnString { get { return (this.EFConnString  != null); } }

    #region Constructors and Init

    /// <summary>
    /// Init the BLL with a regular (optionally base64-encoded) string.
    /// </summary>
    /// <param name="connstr"></param>
    public BLL(string connstr) : base(connstr)
    {
        this.EFConnString = null;
    }

    /// <summary>
    /// Destructor (for cleanup)
    /// </summary>
    ~BLL()
    {
        // when you add an event handler for a custom event, you are also responsible for 
        // cleaning it up.
        if (this.EFConnString != null)
        {
            this.EFConnString.OnChanged -= this.EFConnString_OnChanged;
        }
    }

    /// <summary>
    /// Init BLL with a connectionstring object, and handle changed event
    /// </summary>
    /// <param name="connStr"></param>
    public BLL(FactoryConnectionString connStr) : base(connStr)
    {
        this.EFConnString = connStr;
        // add our event handler
        this.EFConnString.OnChanged += this.EFConnString_OnChanged;
        // the DAL only knows about connection *strings*, and that forces is to handle the 
        // inevitable changes to the connection string in this app.
        this.ConnectionString = this.EFConnString.ConnectionString;
    }

    /// <summary>
    /// Event handler for connectionstring OnChange event.
    /// </summary>
    /// <param name="sender">The FactoryConnectionString object that changed</param>
    /// <param name="e">Contains the item that was changed, and the old and new (string) values.</param>
    private void EFConnString_OnChanged(object sender, ConnStringArgs e)
    {
        // if the connectionstring used by this bll object changed, we 
        // need to reset the ConnectionString property in this object.
        this.ConnectionString = this.EFConnString.ConnectionString;
    }

    #endregion Constructors and Init

    #region business methods go here

    #endregion business methods go here
}

History

  • 2020.04.09 Initial release
     

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)