NET 2.0 中的提供程序设计模式






4.96/5 (12投票s)
提供程序设计模式是微软在 .NET 2.0 中正式化的一种新模式,旨在提高应用程序性能,无需显式实例化类。
目录
- 引言
- 文章有哪些好处?
- 应用程序架构
- 应用程序配置
- 提供程序实现
- 恢复数据库
- 调试代码
- 结论
- 参考文献
引言
提供程序设计模式是微软在 ASP.NET Whidbey 中正式化的一种新模式。该模式于 2002 年夏天正式命名,当时微软正在设计 Whidbey 的新个性化功能。
提供程序的优势
- 无需显式实例化类。 .NET Framework 将自动管理类的实例化,包括重用已实例化的类。这将大大提高应用程序的内存管理效率。
- 切换数据源更加容易。将应用程序的数据源从当前数据库更改为任何数据库(无论是 SQL Server、Oracle、XML 还是其他),只需替换现有的具体(实现者)类,并继承自提供程序类即可。仅此而已。您的表示层和业务逻辑层保持不变,从而减少了切换数据源所需的精力以及之后所需的回归测试量。
- 学习提供程序设计概念将使自定义内置的 .NET Framework 提供程序变得非常容易。
提供程序实现必须派生自一个 `abstract` 基类,该基类用于定义特定功能的契约。
例如,要为 SQL 创建一个 `Person` 提供程序,您可以创建一个新类 `SqlPersonProvider`,该类派生自 `PersonProvider`。`PersonProvider` 的基类是 `ProviderBase`。`ProviderBase` 类用于标记实现者为提供程序,并强制实现所有提供程序共有的必需方法和属性。
文章的好处有哪些?
- 理解提供程序设计模式的实现
- 理解三层架构应用程序的实现
- 理解应用程序架构
- 命名约定
注意:我强烈建议您使用本文档中我使用的确切名称来开发您的解决方案,以便学习这个概念。一旦您测试并理解了它的工作原理,就可以集成您自己的命名约定。
应用程序实现
我开发了一个名为“电话簿”的桌面应用程序来描述提供程序的概念。
电话簿应用程序采用三层架构开发,如图所示。
数据库对象(如表、字段和存储过程)在 `CompanyName` 库的提供程序信息中表示为面向对象的类、属性和方法。
表示层调用业务逻辑层返回的结果,然后通过使用数据访问层库中实现的提供程序从数据库检索数据。
解决方案项目 [ 4 个项目]
- `BusinessLogicLayer`:应用程序的业务逻辑层
- `CompanyName`:包含所有解决方案项目通用的类
- `DataAccessLayer`:应用程序的数据访问层
- `PhoneBookApplication`:表示层

应用程序架构
下图描绘了分布式应用程序的常见层。本文档区分了业务数据和使用这些数据的业务流程;仅在需要澄清时才讨论业务流程层。同样,仅在数据表示方式(例如 Microsoft® ASP.NET 网页如何公开业务数据)有直接影响时才讨论表示层。 阅读更多。
下图描述了所有解决方案项目的架构
下图,用例图,描述了应用程序的主要功能
现在,我们可以讨论每个项目的实现
(1) 打开 Visual Studio 2005
- 创建 Windows 应用程序并命名为 `PhoneBookApplication`
- 选择“文件”菜单 --> “添加” --> “新建项目”并命名为 `BusinessLogicLayer`
- 选择“文件”菜单 --> “添加” --> “新建项目”并命名为 `DataAccessLayer`
- 选择“文件”菜单 --> “添加” --> “新建项目”并命名为 `CompanyName`
CompanyName
此项目将包含全局用于开发和项目之间共享对象的通用库,因此您需要为所有其他应用程序添加 `CompanyName` 库的引用。
示例:我创建了提供程序信息类,以便在“`BussinessLogicLayer`”和“`DataAccessLayer`”项目之间共享。
(2) 为 BusinessLogicLayer 和 DataAccessLayer 添加对以下 DLL 的引用
System.Web
System.Configuration
CompanyName
(3) 为桌面应用程序添加“CompanyName”和“BusinessLogicLayer”的引用

(4) 配置“PhoneBookApplication”的 App.config

如果您查看 `app.config` 中的 XML 元素,我们会定义一个名为“`PhoneBook`”的 sectiongroup,并在其中添加了两个 section:“`Person`”和“`Group`”。
<sectionGroup name="PhoneBook">
<section name="Person"
type="CompanyName.PhoneBook.DataAccessLayer.SectionConfig, DataAccessLayer" />
<section name="Group"
type="CompanyName.PhoneBook.DataAccessLayer.SectionConfig, DataAccessLayer" />
</sectionGroup>
然后将该 section group 定义为
<PhoneBook>
<Person>
<providers>
<add name="SqlPerson"
type="CompanyName.PhoneBook.DataAccessLayer.SqlPersonProvider, DataAccessLayer"
connectionStringName="strcon" />
<add name="OraclePerson"
type="CompanyName.PhoneBook.DataAccessLayer.OraclePersonProvider,
DataAccessLayer"
connectionStringName="OracleConnection" />
<add name="AccessPerson"
type="CompanyName.PhoneBook.DataAccessLayer.AccessPersonProvider,
DataAccessLayer"
connectionStringName="AccessConnection" />
</providers>
</Person>
<Group>
<providers>
<add name="SqlGroup"
type="CompanyName.PhoneBook.DataAccessLayer.SqlGroupProvider, DataAccessLayer"
connectionStringName="strcon" />
</providers>
</Group>
</PhoneBook>
然后定义“SQL、Oracle 和 Access”数据存储的连接字符串
<connectionStrings>
<add name="strcon" connectionString="Data Source=.;
Initial Catalog=AhmedEid_PhoneBook;Integrated Security=True" />
<add name="OracleConnection" connectionString="oracle_connection_string" />
<add name="AccessConnection" connectionString="Access_connection_string" />
</connectionStrings>
Person 提供程序可以从 SQL、Oracle 或 Access 数据库检索数据。
Group 提供程序只能从 SQL 检索数据
(5) 实现 DataAccessLayer
假设 PhoneBook 是一个中型企业,并且 `PhoneBookApplicaion` 包含 2 个主要部分
- `Person`:个人资料管理
- `Groups`:人员分类。
以下代码读取 `web.config` 中定义的所有提供程序。您只需要这样做,即可使提供程序信息可供其他类使用。
using System;
using System.Configuration;
namespace CompanyName.PhoneBook.DataAccessLayer
{
public class SectionConfig : ConfigurationSection
{
[ConfigurationProperty("providers")]
public ProviderSettingsCollection Providers
{
get
{
return (ProviderSettingsCollection)base["providers"];
}
}
}
}
我们需要创建另一个类来访问 Framework 提供程序集合,并将我们的新提供程序添加到提供程序集合中。
using System;
using System.Configuration.Provider;
namespace CompanyName.PhoneBook.DataAccessLayer
{
public class ProviderList : ProviderCollection
{
public override void Add(ProviderBase provider)
{
if (provider == null) throw new ArgumentNullException
("The provider parameter cannot be null.");
base.Add(provider);
}
}
}
创建另一个类来初始化提供程序(通过提供程序名称)。
示例:使用提供程序名称“`SQLGroup`”初始化 `SQLGroupProvider` 类的 **Group 提供程序**。
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.Configuration;
using System.Configuration;
using CompanyName.PhoneBook.DataAccessLayer;
namespace CompanyName.PhoneBook.DataAccessLayer
{
public abstract class InitMember<T>
{
public static ProviderList Providers(string _providerSectionName)
{
SectionConfig qc =
(SectionConfig)ConfigurationManager.GetSection(_providerSectionName);
providerCollection = new ProviderList();
// this will instantiate PersonProvider with the class
// "personimpl" which inherit it
ProvidersHelper.InstantiateProviders(qc.Providers,
providerCollection, typeof(T));
providerCollection.SetReadOnly();
return providerCollection;
}
private static ProviderList providerCollection;
}
}
该类接受您要初始化的提供程序,即 `PersonProvider`,它的类以及提供程序名称。
这个 `enum` 存在于 `CompanyName.Globals` 类中
使用 `PersonProvider` 类的 `Instance` 方法中的该类
/// <summary>
/// This will initialize the provider and add instance to the providers list
/// </summary>
/// <param name="_Provider"></param>
/// <returns></returns>
public static PersonProvider Instance(Globals.Providers _Provider)
{
return (DataAccessLayer.PersonProvider)DataAccessLayer.InitMember
<DataAccessLayer.PersonProvider>.Providers
("PhoneBook/Person")[_Provider.ToString()];
}
此方法将初始化提供程序并将实例添加到提供程序列表。
现在,我们的“`DataAccessLayer`”项目拥有了稍后将要开发的所有提供程序所需的类。
所以我们将开发两个提供程序
PersonProvider
GroupProvider
现在,我们创建提供程序模型
`BaseProvider` --> `xxxProvider` --> `SQLxxxProvider` xxx 是实体的名称,例如 `Person`、`Group` 等。
Person 提供程序类
Group Provider 类

让我解释一下 `PersonProvider` 的实现,您可以自己创建 `GroupProvider`:首先,我们创建一个名为“*PersonProvider.cs*”的类
using System;
using CompanyName.PhoneBook.Providers;
using System.Configuration.Provider;
using System.Configuration;
using System.Web.Configuration;
namespace CompanyName.PhoneBook.DataAccessLayer
{
public abstract class PersonProvider : ProviderBase
{
/// <summary>
/// This will initialize the provider and add instance to the providers list
/// </summary>
/// <param name="_Provider"></param>
/// <returns></returns>
public static PersonProvider Instance(Globals.Providers _Provider)
{
return (DataAccessLayer.PersonProvider)DataAccessLayer.InitMember
<DataAccessLayer.PersonProvider>.Providers
("PhoneBook/Person")[_Provider.ToString()];
}
/// <summary>
/// Add new person
/// </summary>
/// <param name="_info"></param>
/// <returns></returns>
public abstract bool Add(PersonInfo _info);
/// <summary>
/// Modify selected person
/// </summary>
/// <param name="_info"></param>
/// <returns></returns>
public abstract bool Modify(PersonInfo _info);
/// <summary>
/// Delete selected person
/// </summary>
/// <param name="_PersonId"></param>
/// <returns></returns>
public abstract bool Delete(int _PersonId);
/// <summary>
/// Get all persons
/// </summary>
/// <returns></returns>
public abstract PersonInfo[] Find();
/// <summary>
/// Get info of person
/// </summary>
/// <param name="_PersonId"></param>
/// <returns></returns>
public abstract PersonInfo GetInfo(int _PersonId);
/// <summary>
/// Get persons that match a given criteria
/// </summary>
/// <param name="_Searchinfo"></param>
/// <returns></returns>
public abstract PersonInfo[] Find(SearchCriteriaInfo _Searchinfo);
}
}
`Instance()` 方法负责实例化我们的具体(实现者)类(*SqlGeneralProvider.cs*),该类已在 *Web.config* 中定义,并且“`PersonProvider`”`abstract` 类继承自 `ProviderBase` 并包含 Person 的抽象函数。
(6) BusinessLogicLayer
注意:在 `BusinessLogicLayer` 项目中添加一个 `Helper` 类是个好主意。这样,您可以通过简单地继承这个辅助类来将一些通用功能暴露给所有 `BusinessLogicLayer` 类。
using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
namespace CompanyName.PhoneBook.BusinessLogicLayer
{
/// <summary>
/// You can use helper to provide common info./data
/// needed OR to massage or add more info.
/// to your data before sending it to presentation.
/// </summary>
public abstract class Helper
{
protected static string MachineName
{
get
{
return Environment.MachineName;
}
}
}
// Add more methods/properties below
}
然后为 `Person` 和 `Group` 提供程序定义业务类。
Person.cs
using System;
using System.Collections.Generic;
using System.Text;
using CompanyName.PhoneBook.Providers;
using CompanyName.PhoneBook.DataAccessLayer;
namespace CompanyName.PhoneBook.BusinessLogicLayer
{
public abstract class Person : Helper
{
static PersonProvider objPersonProvider;
/// <summary>
/// Person Constructor
/// </summary>
//Class Constructor: will be invoked 1 time only / Appdomain
static Person()
{
objPersonProvider = PersonProvider.Instance(Globals.Providers.SqlPerson);
}
// static methods for person
/// <summary>
/// Add new person
/// </summary>
/// <param name="_info"></param>
/// <returns></returns>
public static bool Add(PersonInfo _info)
{
// You can use helper to provide common info./data needed OR to
// massage or add more info. to your data before sending it to
// presentation.
// Here we use helper class to get MachineName and pass it along
// with data to presentation.
return objPersonProvider.Add(_info);
}
/// <summary>
/// Modify selected person
/// </summary>
/// <param name="_info"></param>
/// <returns></returns>
public static bool Modify(PersonInfo _info)
{
return objPersonProvider.Modify(_info);
}
/// <summary>
/// Delete selected person
/// </summary>
/// <param name="_PersonId"></param>
/// <returns></returns>
public static bool Delete(int _PersonId)
{ return objPersonProvider.Delete(_PersonId); }
/// <summary>
/// Get all persons
/// </summary>
/// <returns></returns>
public static PersonInfo[] Find() { return objPersonProvider.Find(); }
/// <summary>
/// Get info of person
/// </summary>
/// <param name="_PersonId"></param>
/// <returns></returns>
public static PersonInfo GetInfo(int _PersonId)
{ return objPersonProvider.GetInfo(_PersonId); }
/// <summary>
/// Get persons that match a given criteria
/// </summary>
/// <param name="_Searchinfo"></param>
/// <returns></returns>
public static PersonInfo[] Find(SearchCriteriaInfo _Searchinfo)
{ return objPersonProvider.Find(_Searchinfo); }
}
}
`objPersonProvider` 定义为 `PersonProvider`,以便在 `Person` 类的实例之间共享。然后在类构造函数中初始化它,该构造函数将为每个 `Appdomain` 调用一次。
Person objPerson_1 = new Person(); // on class A
Person objPerson_2 = new Person(); // on class B
Person objPerson_3 = new Person(); // on class C
//Class Constructor: will be invoked 1 time only / Appdomain
static Person()
{
objPersonProvider = PersonProvider.Instance(Globals.Providers.SqlPerson);
}
`objPersonProvider` 将仅在类 A 的 `objPerson_1` 构造函数中初始化,然后 `objPerson_2` 或 `objPerson_3` 等其他实例将使用 `static` 对象 `objPersonProvider`。您可以在调用数据访问提供程序之前,将业务逻辑添加到每个方法中,例如
public static bool Add(PersonInfo _info)
{
// Here: you can add you business logic to every method
// before calling the data access provider
return objPersonProvider.Add(_info);
}
PhoneBook 应用程序
表示层作为桌面应用程序,用于向客户端实现业务逻辑。

如何从表示层使用 BL 层?示例代码
// build the search criteria
SearchCriteriaInfo objSearchInfo = new SearchCriteriaInfo();
objSearchInfo.FilterNameBy = (Globals.FilterNameBy)this.cboName.SelectedValue;
objSearchInfo.GroupId = (int)cboGroup.SelectedValue;
objSearchInfo.Name = txtName.Text.Trim();
if(chkFrom.Checked)
objSearchInfo.FromDate = datefrom.Value;
if(chkTo.Checked)
objSearchInfo.ToDate = dateto.Value;
objSearchInfo.SortBy = (rdoasc.Checked) ? Globals.SortBy.Asc : Globals.SortBy.Desc;
objSearchInfo.SortByBirthDate = chkbirthdate.Checked; ;
objSearchInfo.SortByGroup = chkGroup.Checked;
objSearchInfo.SortByName = chkName.Checked;
objSearchInfo.SortByTele = chkTele.Checked;
objSearchInfo.TeleNumber = txtTeleNumber.Text.Trim();
objSearchInfo.TelephoneType = (Globals.TelephoneTypes)this.cboTeleTypes.SelectedValue;
// get result from bus logic layer
PersonInfo[] objresult = Person.Find(objSearchInfo);

如何从数据库保存和检索图像?
保存到数据库
将图像从 PictureBox 保存到 `MemoryStream`,然后将其作为 `Byte[]` 保存到数据库
// prepare the image
if (null != imgperson.Image) // picturebox control
{
MemoryStream stream = new MemoryStream();
imgperson.Image.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
info.Image = stream.ToArray();
}
从数据库检索
// display the image
byte[] PImage = info.Image;
if (null != PImage)
{
MemoryStream stream = new MemoryStream(PImage);
imgperson.Image = Image.FromStream(stream);
}
在 SQL Server 2005 中恢复数据库
将根目录下的 `DataBase` 目录中的数据库文件 *AhmedEid_PhoneBook.mdf* 附加到 SQL Server 引擎。
调试场景
让我们调试高级搜索按钮代码,以从客户端检索带条件的搜索结果。
以下场景将帮助您理解提供程序的实现。
按以下方式设置断点
1. AdvancedSearch 窗体类(表示层)
2. BusinessLogicLayer 的 Person 类(BLL 层)

3. DataAccessLayer 的 PersonProvider 类(DAL 层)

4. DataAccessLayer 的 InitMember 类(DAL 层)

以上代码将提供 SqlPersonProvider 类并将其添加到提供程序列表,以便任何使用都可以使用。
5. DataAccessLayer 的 SqlGroupProvider 类(DAL 层)
注意:第一次调试代码后,提供程序(`PersonProvider` 和 `groupProvider`)将被添加到 `System.Configuration.Provider.ProviderCollection` 中,第二次运行时将直接检索结果,而无需再次实例化类。
参考文献
- http://msdn.microsoft.com/en-us/library/ms972319.aspx
- http://msdn.microsoft.com/en-us/library/ms972370.aspx
- http://msdn.microsoft.com/en-us/library/ms978496.aspx
- http://en.wikipedia.org/wiki/Design_Patterns
- http://www.c-sharpcorner.com/UploadFile/webmaster3/
ProviderPattern12242007184126PM/ProviderPattern.aspx