通用 DAL 回顾 - 打造你自己的实例,一个实际的例子
当你使用他人的代码时,特别是那些可能会不时更新的代码,记住,你很可能可以在不更改原始代码的情况下,利用面向对象的强大功能来扩展它,并将其打造成你自己的版本。
前言
当你使用 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 首次发布