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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (12投票s)

2008年10月29日

CPOL

7分钟阅读

viewsIcon

74799

downloadIcon

1047

提供程序设计模式是微软在 .NET 2.0 中正式化的一种新模式,旨在提高应用程序性能,无需显式实例化类。

目录

  • 引言
  • 文章有哪些好处?
  • 应用程序架构
  • 应用程序配置
  • 提供程序实现
  • 恢复数据库
  • 调试代码
  • 结论
  • 参考文献

引言

提供程序设计模式是微软在 ASP.NET Whidbey 中正式化的一种新模式。该模式于 2002 年夏天正式命名,当时微软正在设计 Whidbey 的新个性化功能。

提供程序的优势

  1. 无需显式实例化类。 .NET Framework 将自动管理类的实例化,包括重用已实例化的类。这将大大提高应用程序的内存管理效率。
  2. 切换数据源更加容易。将应用程序的数据源从当前数据库更改为任何数据库(无论是 SQL Server、Oracle、XML 还是其他),只需替换现有的具体(实现者)类,并继承自提供程序类即可。仅此而已。您的表示层和业务逻辑层保持不变,从而减少了切换数据源所需的精力以及之后所需的回归测试量。
  3. 学习提供程序设计概念将使自定义内置的 .NET Framework 提供程序变得非常容易。

提供程序实现必须派生自一个 `abstract` 基类,该基类用于定义特定功能的契约。

例如,要为 SQL 创建一个 `Person` 提供程序,您可以创建一个新类 `SqlPersonProvider`,该类派生自 `PersonProvider`。`PersonProvider` 的基类是 `ProviderBase`。`ProviderBase` 类用于标记实现者为提供程序,并强制实现所有提供程序共有的必需方法和属性。

image32.png

文章的好处有哪些?

  • 理解提供程序设计模式的实现
  • 理解三层架构应用程序的实现
  • 理解应用程序架构
  • 命名约定

注意:我强烈建议您使用本文档中我使用的确切名称来开发您的解决方案,以便学习这个概念。一旦您测试并理解了它的工作原理,就可以集成您自己的命名约定。

应用程序实现

我开发了一个名为“电话簿”的桌面应用程序来描述提供程序的概念。

电话簿应用程序采用三层架构开发,如图所示。

数据库对象(如表、字段和存储过程)在 `CompanyName` 库的提供程序信息中表示为面向对象的类、属性和方法。

image26.JPG

表示层调用业务逻辑层返回的结果,然后通过使用数据访问层库中实现的提供程序从数据库检索数据。

解决方案项目 [ 4 个项目]

  1. `BusinessLogicLayer`:应用程序的业务逻辑层
  2. `CompanyName`:包含所有解决方案项目通用的类
  3. `DataAccessLayer`:应用程序的数据访问层
  4. `PhoneBookApplication`:表示层
image27.png

应用程序架构

下图描绘了分布式应用程序的常见层。本文档区分了业务数据和使用这些数据的业务流程;仅在需要澄清时才讨论业务流程层。同样,仅在数据表示方式(例如 Microsoft® ASP.NET 网页如何公开业务数据)有直接影响时才讨论表示层。 阅读更多

image33.png

下图描述了所有解决方案项目的架构

image28.png

下图,ERD,是电话簿的数据库设计
image29.png

下图,用例图,描述了应用程序的主要功能

image30.png

现在,我们可以讨论每个项目的实现

(1) 打开 Visual Studio 2005

  1. 创建 Windows 应用程序并命名为 `PhoneBookApplication`
  2. 选择“文件”菜单 --> “添加” --> “新建项目”并命名为 `BusinessLogicLayer`
  3. 选择“文件”菜单 --> “添加” --> “新建项目”并命名为 `DataAccessLayer`
  4. 选择“文件”菜单 --> “添加” --> “新建项目”并命名为 `CompanyName`

CompanyName

此项目将包含全局用于开发和项目之间共享对象的通用库,因此您需要为所有其他应用程序添加 `CompanyName` 库的引用。

示例:我创建了提供程序信息类,以便在“`BussinessLogicLayer`”和“`DataAccessLayer`”项目之间共享。

image5.png

(2) 为 BusinessLogicLayer 和 DataAccessLayer 添加对以下 DLL 的引用

  • System.Web
  • System.Configuration
  • CompanyName

(3) 为桌面应用程序添加“CompanyName”和“BusinessLogicLayer”的引用

image6.png

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

image8.png

如果您查看 `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 个主要部分

  1. `Person`:个人资料管理
  2. `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` 类中

image13.png
使用 `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 提供程序类

image9.png

Group Provider 类

image10.png

让我解释一下 `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

image14.png

注意:在 `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 应用程序

表示层作为桌面应用程序,用于向客户端实现业务逻辑。

image31.png

如何从表示层使用 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);
image12.png

image17.png

如何从数据库保存和检索图像?

保存到数据库

将图像从 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);
 }

image18.png

在 SQL Server 2005 中恢复数据库

将根目录下的 `DataBase` 目录中的数据库文件 *AhmedEid_PhoneBook.mdf* 附加到 SQL Server 引擎。

调试场景

让我们调试高级搜索按钮代码,以从客户端检索带条件的搜索结果。
以下场景将帮助您理解提供程序的实现。

按以下方式设置断点

1. AdvancedSearch 窗体类(表示层)

image19.png

2. BusinessLogicLayer 的 Person 类(BLL 层)

image20.png

3. DataAccessLayer 的 PersonProvider 类(DAL 层)

image21.png

4. DataAccessLayer 的 InitMember 类(DAL 层)

image22.png

以上代码将提供 SqlPersonProvider 类并将其添加到提供程序列表,以便任何使用都可以使用。

image24.png

5. DataAccessLayer 的 SqlGroupProvider 类(DAL 层)

image23.png

注意:第一次调试代码后,提供程序(`PersonProvider` 和 `groupProvider`)将被添加到 `System.Configuration.Provider.ProviderCollection` 中,第二次运行时将直接检索结果,而无需再次实例化类。

image25.png

参考文献

© . All rights reserved.