65.9K
CodeProject 正在变化。 阅读更多。
Home

通用 DAL 回顾 - 打造你自己的实例,一个实际的例子

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2020年4月9日

CPOL

5分钟阅读

viewsIcon

5072

当你使用他人的代码时,特别是那些可能会不时更新的代码,记住,你很可能可以在不更改原始代码的情况下,利用面向对象的强大功能来扩展它,并将其打造成你自己的版本。

前言

当你使用 CodeProject 文章中的代码时,你可能会假设它能完美满足你的需求,当它不符合时,你就会去找作者。如果你运气好,作者还在关注文章的留言,如果你运气更好,他甚至可能会帮你解决你本应自己就能解决的问题。

如果你是这样的人,我的观点(而且不是谦虚地说)是,你应该硬气一点,自己去解决。谷歌是一个非常好的资源,你可以找到几乎任何你可能会遇到的编程问题的答案。这篇文章是关于自力更生的——如何在不求助的情况下,利用别人的代码。

注意:本文没有附带源代码文件,因为所有的代码都在文章本身中,事实上,文章末尾完整地展示了整个连接字符串类。

引言

在我编写本文中的代码——通用 ADO.Net DAL - 回顾 [^]——之后,我开始“吃自己的狗粮”(使用我为他人提供的代码)。那篇文章中的代码没有任何关于你如何使用它的假设,而且代码的大部分方面都实现了保护你免受自身(或代码)损害的机制。

适配连接字符串

我需要一种方法来更改用于构建连接字符串的 `PWConnectionString` 对象中的一个或多个属性。然而,相关的属性是 `protected`(受保护的),这意味着我需要创建一个新类,并继承原始的 `PWConnectionString` 类。这很容易,但是 `BLL` 对象在某些属性发生变化时,不知道如何获取新的连接字符串。

自定义事件来帮忙!

注意:本节中提供的所有代码都放在新的连接字符串类中。

我的新连接字符串类需要调用一个 `BLL` 可以订阅的自定义事件。首先,我们需要定义一个派生自 `EventArgs` 的类。

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;
    }
}

我必须说,我现在实际上不需要知道新旧值是什么,甚至哪个属性发生了变化。然而,可能有一天我确实想知道这些信息,所以我把它们放在事件参数对象中,这样以后就不用再做了。这就是我所说的“防御性编程”——为未来可能合理出现的条件做准备。这是一个很好的习惯,可以让你更深入地思考你的代码,让你更多地思考你如何处理给定的编程任务。

在类中

首先,我们需要定义事件方法的原型。处理事件的方法需要具有指定的返回类型,并接受指定的参数。

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

接下来,我们必须添加委托方法。这个方法创建参数对象,然后调用事件委托。

/// <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);
    }
}

最后,我必须添加 `public` 方法(记住,基类的属性是 `protected` 的,所以我们必须这样做),以便我们能够实际更改属性。更改属性后,会调用 `Changed` 事件。由于所有这些方法基本上都是相同的,我只在本节中展示一个。

/// <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); 
}

适配 DAL

现在你应该知道,你会创建一个继承 `DAL` 的 BLL 对象,并在其中实现你的实际数据库访问器方法。与连接字符串类一样,`DAL` 的关注点非常狭窄,大多数属性和方法都是 `protected` 的。`DAL` 的一个方面是它将连接字符串存储为字符串,这使得在不进行一些更改的情况下响应我们的新事件成为不可能。

设置 BLL

为了能够订阅连接字符串事件,我们需要为其添加一个属性。

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

接下来,我们必须提供构造函数来满足基类 `DAL` 的要求。第一个构造函数是正常的,它简单地使用提供的字符串作为连接字符串。如果你使用这个构造函数,你将无法利用我们刚刚添加的新事件功能。

/// <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;
}

第二个构造函数情况更复杂。它接受一个连接字符串对象。当我们使用这个构造函数时,它会调用匹配的基类构造函数,并设置对象的 `ConnectionString` 属性(这是一个 base64 编码的字符串),并将其自身的 `EFConnString` 属性设置为该对象。完成后,我们为 `OnChanged` 事件添加一个事件处理程序。

请注意,我为 BLL 实现了一个析构函数。原因是,如果你添加了一个自定义事件处理程序,当包含对象变为 null 时,你必须释放它。

/// <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;
    }
}

最后,我们添加事件的处理器。BLL 只是将它的 `ConnectionString` 属性重置为新构建的 `EFConnString.ConnectionString` 属性值。正如我之前所说的,我现在不需要 `ConnStringArgs` 参数中的任何内容,所以我们所做的就是重置基类的 `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;
}

Using the Code

在我的测试应用程序中,我首先创建一个连接“单例”,以及一个 BLL 单例。

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")
}

关注点

当你使用他人的代码时,特别是那些可能会不时更新的代码,记住,你很可能可以在不更改原始代码的情况下,利用面向对象的强大功能来扩展它,并将其打造成你自己的版本。这意味着你必须了解 OOP 的基础知识,并在你选择的语言中应用它们,并利用 .Net 框架的特性。顺便说一句,这段代码也应该与 .Net Core 完全兼容。

连接字符串和 BLL 类的完整代码

/// <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());
    }

}
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
}

历史

  • 2020.04.09 首次发布
     
© . All rights reserved.