Zephyrrr's profileZephyrrr's BlogPhotosBlogLists Tools Help

Blog


    March 12

    Enterprise Library

    1. Introduction: http://msdn2.microsoft.com/en-us/library/aa480453.aspx
    2. Homepage: http://msdn2.microsoft.com/zh-cn/practices/default.aspx
    3. Community: http://www.codeplex.com/entlib

    Data Access Application Block

    Sample 1

    • Database db = DatabaseFactory.CreateDatabase();
    • DbCommand dbCommand = db.GetStoredProcCommand("GetProductsByCategory");
    • db.AddInParameter(dbCommand, "CategoryID", DbType.Int32, 7);
    • DataSet productDataSet = db.ExecuteDataSet(dbCommand);

    Should know

    • Dabasebase
    • DatabaseFactory
    • DbComand

    Sample 2

    • Database.ExecuteReader,Database.ExecuteDataSet,Databse.ExecuteScalar,Database.ExecuteNonQuery
    • Databse.GetStoredProcCommand, Database.GetSqlStringCommand
    • DbCommad.AddInParameter, DbCommand.AddOutParameter, AddOutParameter.GetParameterValue

    Transaction

    • using (DbConnection connection = db.CreateConnection())
    • {
    •      connection.Open();
    •      DbTransaction transaction = connection.BeginTransaction();
    •      transaction.Commit(); (transaction.Rollback();)
    • }

     

    Security Application Block

    authenticate: use MembershipProvider which is integrated in .Net

    • bool authenticated = Membership.ValidateUser(username, password);
    • IIdentity identity = new GenericIdentity(username, Membership.Provider.Name);

    Token:

    • IToken token = ISecurityCacheProvider.SaveIdentity(this.identity);
    • IIdentity savedIdentity = ISecurityCacheProvider.GetIdentity(this.token);
    • ISecurityCacheProvider.ExpireIdentity(this.token);

    Priciple and authorize: (Roles is integrated in .Net)

    • string[] roles = Roles.GetRolesForUser(identity);
    • IPrincipal principal = new GenericPrincipal(new GenericIdentity(identity), roles);
    • bool authorized = this.ruleProvider.Authorize(principal, rule);
    • principal.IsInRole(role1);

    Profile: (also in integrated in .Net)

    • ProfileBase userProfile = ProfileBase.Create(this.identity.Name);
    • userProfile["FirstName"] = this.profile.FirstName;
    • userProfile.Save();

    Caching Application Block

    CacheManager productsCache = CacheFactory.GetCacheManager();
    Product product = new Product(id, name, price);
    productsCache.Add(product.ProductID, product, CacheItemPriority.Normal, null, new SlidingTime(TimeSpan.FromMinutes(5)));
    product = (Product) productsCache.GetData(id);

    productsCache.Flush();

    productsCache.Remove(id);

    Expiration: Absolute, Sliding, Extended format, File dependency
    Scavenging: Low, Normal, High, or Not Removable

     

    Cryptography Application Block
    // String encrypt and decrypt
    string encryptedContentsBase64 = Cryptographer.EncryptSymmetric("symmProvider", "SensitiveData");
    readableString = Cryptographer.DecryptSymmetric("symmProvider", encryptedContentsBase64);

    // byte encrypt and decrypt
    byte[] valueToEncrypt = Encoding.Unicode.GetBytes("password");
    byte[] encryptedContents = Cryptographer.EncryptSymmetric("symmProvider", valueToEncrypt);
    // Clear the byte array memory that holds the password.
    Array.Clear(valueToEncrypt, 0, valueToEncrypt.Length);

    byte[] decryptedContents = Cryptographer.DecryptSymmetric("symmProvider", encryptedContents);
    string plainText = (new UnicodeEncoding()).GetString(decryptedContents);

    // Obtaining a Hash Value
    byte[] valueToHash = (new UnicodeEncoding()).GetBytes("password");
    byte[] generatedHash = Cryptographer.CreateHash("hashProvider", valueToHash);
    // Clear the byte array memory.
    Array.Clear(valueToHash, 0, valueToHash.Length);

    // CompareHash
    // generatedHash contains a hash value for a previously hashed string.
    byte[] stringToCompare = (new UnicodeEncoding()).GetBytes("TestValue");
    bool comparisonSucceeded = Cryptographer.CompareHash("hashProvider", stringToCompare, generatedHash);

     

    Exception Handling Application Block

    Exception Handlers

    • Wrap handler. This exception handler wraps one exception around another.
      Replace handler. This exception handler replaces one exception with another.
    • Logging handler. This exception handler formats exception information, such as the message and the stack trace. Then the logging handler gives this information to the Enterprise Library Logging Application Block so that it can be published.
      Fault Contract Exception Handler. This exception handler is designed for use at Windows Communication Foundation (WCF) service boundaries, and generates a new Fault Contract from the exception.

    Exception Policies

    • Base policy. This policy logs the exception and rethrows the original exception.
    • Secure policy. This policy logs the exception, replaces the original exception with a custom exception, and throws the new exception.
    • Expressive policy. This policy wraps the original exception inside another exception and throws the new exception.

     

    • Logging Policy
    • Replace Policy
    • Wrap Policy
    • Propagate Policy

    Logging Application Block

    • LogEntry logEntry = new LogEntry();
      Logger.Write(logEntry);
    • Dictionary<string, object> dictionary = new Dictionary<string, object>();
      ManagedSecurityContextInformationProvider informationHelper = new ManagedSecurityContextInformationProvider();   
      informationHelper.PopulateDictionary(dictionary);
    • using (new Tracer("Log Category"))
      {
        // Perform processing to be timed here
      }
    • if (Logger.ShouldLog(logEntry))
      if (Logger.GetFilter<CategoryFilter>().ShouldLog(logEntry))

    Validation Application Block

    • public class Customer
      {
          [StringLengthValidator(0, 20)]
          public string CustomerName;

             [SelfValidation]            
             public void CheckTemperature(ValidationResults results){}
          }

    • Customer myCustomer = new Customer("A name that is too long");
      ValidationResults r = Validation.Validate<Customer>(myCustomer);
      // Validator<Customer> validator = ValidationFactory.CreateValidator<Customer>(customerRuleSetCombo.Text);
      // ValidationResults results = validator.Validate(customer);
      if (!r.IsValid)
      {
          throw new InvalidOperationException("Validation error found.");
      }
    • ValidatorComposition(CompositionType.And, CompositionType.Or)
      ContainsCharactersValidator
      DateTimeRangeValidator
      DomainValidator
      EnumConversionValidator
      NotNullValidator
      ObjectCollectionValidator
      ObjectValidator
      PropertyComparisonValidator
      RangeValidator
      RegexValidator
      RelativeDateTimeValidator
      StringLengthValidator
      TypeConversionValidator
    • Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WinForms.ValidationProvider provider
      form.ValidateChildren();
      类似于ErrorProvider的使用方式(Extender,在每个控件上都有扩展属性)
      provider.RuleName
      provider.SetPerformValidation(control, bool)
      provider.SetSourcePropertyName(congrol, sourcePropertyName)
      provider.SetValidatedProperty(control, validatedPropertyName)
    • You can also specify an instance of the ErrorProvider class for the control. If you do this, the validation errors that result if the validation fails are sent to the error provider as a properly formatted error message.

    October 12

    关于实体类的一篇文章

     

    掌握 ASP.NET 之路:自定义实体类简介

    发布日期: 5/24/2005 | 更新日期: 5/24/2005

    Karl Seguin
    Microsoft Corporation

    摘要:有些情况下,非类型化的 DataSet 可能并非数据操作的最佳解决方案。本指南的目的就是探讨 DataSet 的一种替代解决方案,即:自定义实体与集合。(本文包含一些指向英文站点的链接。)

    *
    本页内容
    引言 引言
    DataSet 存在的问题 DataSet 存在的问题
    自定义实体类 自定义实体类
    对象关系映射 对象关系映射
    自定义集合 自定义集合
    管理关系 管理关系
    高级内容 高级内容
    小结 小结

    引言

    ADODB.RecordSet 和常常被遗忘的 MoveNext 的时代已经过去,取而代之的是 Microsoft ADO.NET 强大而又灵活的功能。我们的新武器就是 System.Data 名称空间,它的特点是具有速度极快的 DataReader 和功能丰富的 DataSet,而且打包在一个面向对象的强大模型中。能够使用这样的工具一点都不奇怪。任何 3 层体系结构都依靠可靠的数据访问层 (DAL) 将数据层与业务层完美地连接起来。高质量的 DAL 有助于改善代码的重新使用,它是获得高性能的关键,而且是完全透明的。

    随着工具的改进,我们的开发模式也发生了变化。告别 MoveNext 并不只是让我们摆脱了繁琐的语法,它还让我们认识了断开连接的数据,这种数据对我们开发应用程序的方式产生了深刻的影响。

    因为我们已经熟悉了 DataReader(其行为与 RecordSet 非常类似),所以没花多长时间就进一步开发出 DataAdapterDataSetDataTableDataView。正是在开发这些新对象的过程中不断得到磨炼的技能改变了我们的开发方式。断开连接的数据使我们可以利用新的缓存技术,从而大大提高了应用程序的性能。这些类的功能使我们能够编写出更智能、更强大的函数,同时还能减少(有时候甚至是大大减少)常见活动所需的代码数量。

    有些情况下非常适合使用 DataSet,例如在设计原型、开发小型系统和支持实用程序时。但是,在企业系统中使用 DataSet 可能并不是最佳的解决方案,因为对企业系统来说,易于维护要比投入市场的时间更重要。本指南的目的就是探讨一种适合处理此类工作的 DataSet 的替代解决方案,即:自定义实体与集合。尽管还存在其他替代解决方案,但它们都无法提供相同的功能或无法获得更多的支持。我们的首要任务是了解 DataSet 的缺点,以便理解我们要解决的问题。

    记住,每种解决方案都有优缺点,所以 DataSet 的缺点可能比自定义实体的缺点(我们也将进行讨论)更容易让您接受。您和您的团队必须自己决定哪个解决方案更适合您的项目。记住要考虑解决方案的总成本,包括要求改变的实质所在以及生产后所需的时间比实际开发代码的时间更长的可能性。最后请注意,我所说的 DataSet 并不是类型化的 DataSet,但它确实可以弥补非类型化的 DataSet 的一些缺点。

    DataSet 存在的问题

    缺少抽象

    寻找替代解决方案的第一个也是最明显的原因就是 DataSet 无法从数据库结构中提取代码。DataAdapter 可以很好地使您的代码独立于基础数据库供应商(Microsoft、Oracle、IBM 等),但不能抽象出数据库的核心组件:表、列和关系。这些核心数据库组件也是 DataSet 的核心组件。DataSet 和数据库不仅共享通用组件,不幸的是,它们还共享架构。假定有下面这样一个 Select 语句:

    SELECT UserId, FirstName, LastName
    FROM Users
    

    我们知道这些值可以从 DataSet 中的 UserIdFirstNameLastName 这些 DataColumn 中获得。

    为什么会这么复杂?让我们看一个基本的日常示例。首先我们有一个简单的 DAL 函数:

    'Visual Basic .NET
    Public Function GetAllUsers() As DataSet
    Dim connection As New SqlConnection(CONNECTION_STRING)
    Dim command As SqlCommand = New SqlCommand("GetUsers", connection)
    command.CommandType = CommandType.StoredProcedure
    Dim da As SqlDataAdapter = New SqlDataAdapter(command)
    Try
    Dim ds As DataSet = New DataSet
    da.Fill(ds)
    Return ds
    Finally
    connection.Dispose()
    command.Dispose()
    da.Dispose()
    End Try
    End Function
    
    //C#
    public DataSet GetAllUsers() {
    SqlConnection connection = new SqlConnection(CONNECTION_STRING);
    SqlCommand command = new SqlCommand("GetUsers", connection);
    command.CommandType = CommandType.StoredProcedure;
    SqlDataAdapter da = new SqlDataAdapter(command);
    try {
    DataSet ds = new DataSet();
    da.Fill(ds);
    return ds;
    }finally {
    connection.Dispose();
    command.Dispose();
    da.Dispose();
     }            
    }
    

    然后我们有一个页面,它使用重复器显示所有用户:

    <HTML>
    <body>
    <form id="Form1" method="post" runat="server">
    <asp:Repeater ID="users" Runat="server">
    <ItemTemplate>
    <%# DataBinder.Eval(Container.DataItem, "FirstName") %>
    <br />
    </ItemTemplate>
    </asp:Repeater>
    </form>
    </body>
    </HTML>
    <script runat="server">
    public sub page_load
    users.DataSource = GetAllUsers()
    users.DataBind()
    end sub
    </script>
    

    正如我们所看到的那样,我们的 ASPX 页面利用 DAL 函数 GetAllUsers 作为重复器的 DataSource。如果由于某种原因(为了性能而降级、为清楚起见而进行了标准化、要求发生了变化)导致数据库架构发生变化,变化就会一直影响 ASPX,即影响使用“FirstName”列名的 Databinder.Eval 行。这将立刻在您脑海中产生一个危险信号:数据库架构的变化会一直影响到 ASPX 代码吗?听起来不太像 N 层,对吗?

    如果我们所要做的只是对列进行简单的重命名,那么更改本例中的代码并不复杂。但是,如果在许多地方都使用了 GetAllUsers,更糟糕的是,如果将其作为为无数用户提供服务的 Web 服务,那又会怎么样呢?怎样才能轻松或安全地传播更改?对于这个基本示例而言,存储过程本身作为抽象层可能已经足够;但是依赖存储过程获得除最基本的保护以外的功能则可能会在以后造成更大的问题。可以将此视为一种硬编码;实质上,使用 DataSet 时,您可能需要在数据库架构(不管使用列名称还是序号位置)和应用层/业务层之间建立一个严格的连接。但愿以前的经验(或逻辑)已经让您了解到硬编码对维护工作以及将来的开发产生的影响。

    DataSet 无法提供适当抽象的另一个原因是它要求开发人员必须了解基础架构。我们所说的不是基础知识,而是关于列名称、类型和关系的所有知识。去掉这个要求不仅使您的代码不像我们看到的那样容易中断,还使代码更易于编写和维护。简单地说:

    Convert.ToInt32(ds.Tables[0].Rows[i]["userId"]);
    

    不仅难于阅读,而且需要非常熟悉列名称及其类型。理想情况下,您的业务层不需要知道有关基础数据库、数据库架构或 SQL 的任何内容。如果您像上述代码字符串中那样使用 DataSet(使用 CodeBehind 并不会有任何改善),您的业务层可能会很薄。

    弱类型

    DataSet 属于弱类型,因此容易出错,还可能会影响您的开发工作。这意味着无论何时从 DataSet 中检索值,值都以 System.Object 的形式返回,您需要对这种值进行转换。您面临转换可能会失败的风险。不幸的是,失败不是在编译时发生,而是在运行时发生。另外,在处理弱类型的对象时,Microsoft Visual Studio.NET (VS.NET) 等工具对您的开发人员并没有太大的帮助。前面我们说过需要深入了解构架的知识,就是指这个意思。我们再来看一个非常常见的示例:

    'Visual Basic.NET
    Dim userId As Integer = 
    ?      Convert.ToInt32(ds.Tables(0).Rows(0)("UserId"))
    Dim userId As Integer = CInt(ds.Tables(0).Rows(0)("UserId"))
    Dim userId As Integer = CInt(ds.Tables(0).Rows(0)(0))
    
    //C#
    int userId = Convert.ToInt32(ds.Tables[0].Rows[0]("UserId"));
    

    这段代码显示了从 DataSet 中检索值的可能方法——可能您的代码中到处都需要检索值(如果不进行转换,而您使用的又是 Visual Basic .NET,您可能会使用 Option Strict Off 这样的代码,而这会给您带来更大的麻烦。)

    不幸的是,这些代码中的每一行都可能会产生大量的运行时错误:

    1.

    转换可能由于以下原因而失败:

    值可能为空。

    开发人员可能对基础数据类型判断有误(还是这个问题,即开发人员需要非常熟悉数据库架构)。

    如果您使用序号值,谁知道位置 X 处实际上是一个什么样的列。

    2.

    ds.Tables(0) 可能返回一个空引用(如果 DAL 方法或存储过程中有任何部分失败)。

    3.

    “UserId”可能由于以下原因而是一个无效的列名称:

    可能已经更改了名称。

    可能不是由存储过程返回的。

    可能包含错别字。

    我们可以修改代码并以更安全的方式编写,即为 null/nothing 添加检查,为转换添加 try/catch,但这些对开发人员都没有帮助。

    更糟糕的是,正如我们前面所说,这不是抽象的。这意味着,每次要从 DataSet 中检索 userId 时,您都将面临上面提到的风险,或者需要对相同的保护性步骤进行重新编程(当然,实用程序功能可能会有助于降低风险)。弱类型对象将错误从设计时或编译时(这时总能够自动检测并轻松修复错误)转移到运行时(这时的错误可能会出现在生产过程中,而且更难查明)。

    非面向对象

    您不能仅仅因为 DataSet 是对象,而 C# 和 Visual Basic .NET 是面向对象 (OO) 的语言就能以面向对象的方式使用 DataSet。OO 编程的“hello world”是一个典型的 Person 类,该类又是 Employee 的子类。但 DataSet 并没有使此类继承或其他大多数 OO 技术成为可能(或者至少使它们变得自然/直观)。Scott Hanselman 是类实体的坚决支持者,他做出了最好的解释:

    “DataSet 是一个对象,对吗?但它并不是域对象,它不是一个‘苹果’或‘桔子’,而是一个‘DataSet’类型的对象。DataSet 是一只碗(它知道支持数据存储)。DataSet 是一个知道如何保存行和列的对象,它非常了解数据库。但是,我不希望返回碗,我希望返回域对象,例如‘苹果’。”1

    DataSet 使数据之间保持一种关系,使它们更强大并且能够在关系数据库中方便地使用。不幸的是,这意味着您将失去 OO 的所有优点。

    因为 DataSet 不能作为域对象,所以无法向它们添加功能。通常情况下,对象具有字段、属性和方法,它们的行为针对的是类的实例。例如,您可能会将 PromoteCalcuateOvertimePay 函数与 User 对象相关联,该对象可以通过 someUser.Promote()someUser.CalculateOverTimePay() 安全地调用。因为无法向 DataSet 添加方法,所以您需要使用实用程序功能来处理弱类型对象,并且在整个代码中包含硬编码值的更多实例。您一般会以过程代码结束,在过程代码中,您要么不断地从 DataSet 中获取数据,要么以繁琐的方式将它们存储在本地变量中并向其他位置传递。两种方法都有缺点,而且都没有任何优点。

    DataSet 相反的情况

    如果您认为数据访问层应返回 DataSet,您可能会漏掉一些重要的优点。其中一个原因是您可能正在使用一个较薄或不存在的业务层,除了其他问题外,它还限制了您进行抽象的能力。另外,因为您使用的是一般的预编译解决方案,所以很难利用 OO 技术。最后,Visual Studio.NET 等工具使开发人员无法轻松地利用弱类型对象(例如 DataSet),因此降低了效率并且增加了出错的可能性。

    所有这些因素都以不同的方式对代码的可维护性产生了直接的影响。缺乏抽象使功能改善和错误修复变得更复杂、更危险。您无法充分利用 OO 提供的代码重新使用或可读性方面的改进。当然还有一点,无论您的开发人员处理的是业务逻辑还是表示逻辑,他们都必须非常了解您的基础数据结构。

    自定义实体类

    DataSet 有关的大多数问题都可以利用 OO 编程的丰富功能在定义明确的业务层中解决。实际上,我们希望获得按照关系组织的数据(数据库),并将数据作为对象(代码)使用。这个概念就是,不是获得保存汽车信息的 DataTable,而是获得汽车对象(称为自定义实体或域对象)。

    在了解自定义实体之前,让我们首先看一看我们将要面临的挑战。最明显的挑战就是所需代码的数量。我们不是简单地获取数据并自动填充 DataSet,而是获取数据并手动将数据映射到自定义实体(必须先创建好)。由于这是一项重复性的任务,我们可以使用代码生成工具或 O/R 映射器(后文有详细的介绍)来减轻工作量。更大的问题是将数据从关系世界映射到对象世界的具体过程。对于简单的系统,映射通常是直接的,但是随着复杂性的增加,这两个世界之间的差异就会产生问题。例如,继承在对象世界中是获得代码重新使用以及可维护性的重要技术。不幸的是,继承对关系数据库来说却是一个陌生的概念。另外一个例子就是处理关系的方式不同:对象世界依靠维护单个对象的引用,而关系世界则是利用外键。

    因为代码的数量以及关系数据和对象之间的差异不断增加,看起来这个方法并不太适合更复杂的系统,但事实正好相反。通过将各种问题隔离到一个层中,即映射过程(同样可以自动化),复杂的系统也可以从此方法获益。另外,此方法已经很常用,这意味着可以通过几种已有的设计模式彻底解决增加的复杂性。前面讨论的 DataSet 的缺点在复杂系统中将成倍扩大,最后您会得出这样一个系统,它欠缺灵活应变能力的缺点恰好超出其构建的难度。

    什么是自定义实体?

    自定义实体是代表业务域的对象,因此,它们是业务层的基础。如果您有一个用户身份验证组件(本指南通篇都使用该示例进行讲解),您就可能具有 UserRole 对象。电子商务系统可能具有 SupplierMerchandise 对象,而房地产公司则可能具有 HouseRoomAddress 对象。在您的代码中,自定义实体只是一些类(实体和“类”之间具有非常密切的关系,就像在 OO 编程中使用的那样)。一个典型的 User 类可能如下所示:

    'Visual Basic .NET
    Public Class User
    #Region "Fields and Properties"
    Private _userId As Integer
    Private _userName As String
    Private _password As String
    Public Property UserId() As Integer
    Get
    Return _userId
    End Get
    Set(ByVal Value As Integer)
    _userId = Value
    End Set
    End Property
    Public Property UserName() As String
    Get
    Return _userName
    End Get
    Set(ByVal Value As String)
    _userName = Value
    End Set
    End Property
    Public Property Password() As String
    Get
    Return _password
    End Get
    Set(ByVal Value As String)
    _password = Value
    End Set
    End Property
    #End Region
    #Region "Constructors"
    Public Sub New()
    End Sub
    Public Sub New(id As Integer, name As String, password As String)
    Me.UserId = id
    Me.UserName = name
    Me.Password = password
    End Sub
    #End Region
    End Class
    
    //C#
    public class User {
    #region "Fields and Properties"
    private int userId;
    private string userName;
    private string password;
    public int UserId {
    get { return userId; }
    set { userId = value; }
      }
    public string UserName {
    get { return userName; }
    set { userName = value; }
     }
    public string Password {
    get { return password; }
    set { password = value; }
     }
    #endregion
    #region "Constructors"
    public User() {}
    public User(int id, string name, string password) {
    this.UserId = id;
    this.UserName = name;
    this.Password = password;
     }
    #endregion
    }
    

    为什么能够从它们获益?

    使用自定义实体获得的主要好处来自这样一个简单的事实,即它们是完全受您控制的对象。具体而言,它们允许您:

    利用继承和封装等 OO 技术。

    添加自定义行为。

    例如,我们的 User 类可以通过为其添加 UpdatePassword 函数而受益(我们可能会使用外部/实用程序函数对数据集执行此类操作,但会影响可读性/维护性)。另外,它们属于强类型,这表示我们可以获得 IntelliSense 支持:


    1User 类的 IntelliSense

    最后,因为自定义实体为强类型,所以不太需要进行容易出错的强制转换:

    Dim userId As Integer = user.UserId
    '与
    Dim userId As Integer = 
    ?         Convert.ToInt32(ds.Tables("users").Rows(0)("UserId"))
    

    对象关系映射

    正如前文所讨论的那样,此方法的主要挑战之一就是处理关系数据和对象之间的差异。因为我们的数据始终存储在关系数据库中,所以我们只能在这两个世界之间架起一座桥梁。对于上文的 User 示例,我们可能希望在数据库中建立一个如下所示的用户表:


    2User 的数据视图

    从这个关系架构映射到自定义实体是一个非常简单的事情:

    'Visual Basic .NET
    Public Function GetUser(ByVal userId As Integer) As User
    Dim connection As New SqlConnection(CONNECTION_STRING)
    Dim command As New SqlCommand("GetUserById", connection)
    command.Parameters.Add("@UserId", SqlDbType.Int).Value = userId
    Dim dr As SqlDataReader = Nothing
    Try
    connection.Open()
    dr = command.ExecuteReader(CommandBehavior.SingleRow)
    If dr.Read Then
    Dim user As New User
    user.UserId = Convert.ToInt32(dr("UserId"))
    user.UserName = Convert.ToString(dr("UserName"))
    user.Password = Convert.ToString(dr("Password"))
    Return user
    End If
    Return Nothing
    Finally
    If Not dr is Nothing AndAlso Not dr.IsClosed Then
    dr.Close()
    End If
    connection.Dispose()
    command.Dispose()
    End Try
    End Function
    
    //C#
    public User GetUser(int userId) {
    SqlConnection connection = new SqlConnection(CONNECTION_STRING);
    SqlCommand command = new SqlCommand("GetUserById", connection);
    command.Parameters.Add("@UserId", SqlDbType.Int).Value = userId;
    SqlDataReader dr = null;
    try{
    connection.Open();
    dr = command.ExecuteReader(CommandBehavior.SingleRow);
    if (dr.Read()){
    User user = new User();
    user.UserId = Convert.ToInt32(dr["UserId"]);
    user.UserName = Convert.ToString(dr["UserName"]);
    user.Password = Convert.ToString(dr["Password"]);
    return user;            
      }
    return null;
    }finally{
    if (dr != null && !dr.IsClosed){
    dr.Close();
      }
    connection.Dispose();
    command.Dispose();
     }
    }
    

    我们仍然按照通常的方式设置连接和命令对象,但接着创建了 User 类的一个新实例并从 DataReader 中填充该实例。您仍然可以在此函数中使用 DataSet 并将其映射到您的自定义实体,但 DataSet 相对于 DataReader 的主要好处是前者提供了数据的断开连接的视图。在本例中,User 实例提供了断开连接的视图,使我们可以利用 DataReader 的速度。

    等一下!您并没有解决任何问题!

    细心的读者可能注意到我前面提到 DataSet 的问题之一是它们并非强类型,这导致效率降低并增加了出现运行时错误的可能性。它们还需要开发人员深入了解基础数据结构。看一看上文的代码,您可能会注意到这些问题依然存在。但请注意,我们已经将这些问题封装到一个非常孤立的代码区域内;这表示您的类实体的使用者(Web 界面、Web 服务使用者、Windows 表单)仍然完全没有意识到这些问题。相反,使用 DataSet 可以将这些问题分散到整个代码中。

    改进

    上文的代码对显示映射的基本概念很有用,但可以在两个关键的方面进行改进。首先,我们需要提取并将代码填充到其自己的函数中,因为代码有可能会被重新使用:

    'Visual Basic .NET
    Public Function PopulateUser(ByVal dr As IDataRecord) As User
    Dim user As New User
    user.UserId = Convert.ToInt32(dr("UserId"))
    '检查 NULL 的示例
    If Not dr("UserName") Is DBNull.Value Then
    user.UserName = Convert.ToString(dr("UserName"))
    End If
    user.Password = Convert.ToString(dr("Password"))
    Return user
    End Function
    
    //C#
    public User PopulateUser(IDataRecord dr) {
    User user = new User();
    user.UserId = Convert.ToInt32(dr["UserId"]);
    //检查 NULL 的示例
    if (dr["UserName"] != DBNull.Value){
    user.UserName = Convert.ToString(dr["UserName"]);   
     }
    user.Password = Convert.ToString(dr["Password"]);
    return user;
    }
    

    第二个需要注意的事项是,我们不对映射函数使用 SqlDataReader,而是使用 IDataRecord。这是所有 DataReader 实现的接口。使用 IDataRecord 使我们的映射过程独立于供应商。也就是说,我们可以使用上一个函数从 Access 数据库中映射 User,即使它使用 OleDbDataReader 也可以。如果您将这个特定的方法与 Provider Model Design Pattern(链接 1链接 2)结合使用,您的代码就可以轻松地用于不同的数据库提供程序。

    最后,以上代码说明了封装的强大功能。处理 DataSet 中的 NULL 并非最简单的事,因为每次提取值时都需要检查它是否为 NULL。使用上述填充方法,我们在一个地方就轻松地解决了此问题,使我们的客户无需处理它。

    映射到何处?

    关于此类数据访问和映射函数的归属问题存在一些争论,即究竟是作为独立类的一部分,还是作为适当自定义实体的一部分。将所有用户相关的任务(获取数据、更新和映射)都作为 User 自定义实体的一部分当然很不错。这在数据库架构与自定义实体很相似时会很有用(比如在本例中)。随着系统复杂性的增加,这两个世界的差异开始显现出来,将数据层和业务层明确分离对简化维护有很大的帮助(我喜欢将其称为数据访问层)。将访问和映射代码放在其自己的层 (DAL) 上有一个副作用,即它为确保数据层与业务层的明确分离提供了一个严格的原则:

    永远不要从 System.Data 返回类或从 DAL 返回子命名空间

    自定义集合

    到目前为止,我们只了解了如何处理单个实体,但您经常需要处理多个对象。一个简单的解决方案是将多个值存储在一个一般的集合(例如 Arraylist)中。这并非最理想的解决方案,因为它又产生了与 DataSet 有关的一些问题,即:

    它们不是强类型,并且

    无法添加自定义行为。

    最能满足我们需求的解决方案是创建我们自己的自定义集合。幸亏 Microsoft .NET Framework 提供了一个专门为了此目的而继承的类:CollectionBaseCollectionBase 的工作原理是,将所有类型的对象都存储在专有 Arraylist 中,但是通过只接受特定类型(例如 User 对象)的方法来提供对这些专有集合的访问。也就是说,将弱类型代码封装在强类型的 API 中。

    虽然自定义集合可能看起来有很多代码,但大多数都可以由代码生成功能或通过剪切和粘贴方便地完成,并且通常只需要一次搜索和替换即可。让我们看一看构成 User 类的自定义集合的不同部分:

    'Visual Basic .NET
    Public Class UserCollection
    Inherits CollectionBase
    Default Public Property Item(ByVal index As Integer) As User
    Get
    Return CType(List(index), User)
    End Get
    Set
    List(index) = value
    End Set
    End Property
    Public Function Add(ByVal value As User) As Integer
    Return (List.Add(value))
    End Function
    Public Function IndexOf(ByVal value As User) As Integer
    Return (List.IndexOf(value))
    End Function
    Public Sub Insert(ByVal index As Integer, ByVal value As User)
    List.Insert(index, value)
    End Sub
    Public Sub Remove(ByVal value As User)
    List.Remove(value)
    End Sub
    Public Function Contains(ByVal value As User) As Boolean
    Return (List.Contains(value))
    End Function
    End Class
    
    //C#
    public class UserCollection :CollectionBase {
    public User this[int index] {
    get {return (User)List[index];}
    set {List[index] = value;}
     }
    public int Add(User value) {
    return (List.Add(value));
     }
    public int IndexOf(User value) {
    return (List.IndexOf(value));
     }
    public void Insert(int index, User value) {
    List.Insert(index, value);
     }
    public void Remove(User value) {
    List.Remove(value);
     }
    public bool Contains(User value) {
    return (List.Contains(value));
     }
    }
    

    通过实现 CollectionBase 可以完成更多任务,但上面的代码代表了自定义集合所需的核心功能。观察一下 Add 函数,可以看出我们只是简单地将对 List.Add(它是一个 Arraylist)的调用封装到仅允许 User 对象的函数中。

    映射自定义集合

    将我们的关系数据映射到自定义集合的过程与我们对自定义实体执行的过程非常相似。我们不再创建一个实体并将其返回,而是将该实体添加到集合中并循环到下一个:

    'Visual Basic .NET
    Public Function GetAllUsers() As UserCollection
    Dim connection As New SqlConnection(CONNECTION_STRING)
    Dim command As New SqlCommand("GetAllUsers", connection)
    Dim dr As SqlDataReader = Nothing
    Try
    connection.Open()
    dr = command.ExecuteReader(CommandBehavior.SingleResult)
    Dim users As New UserCollection
    While dr.Read()
    users.Add(PopulateUser(dr))
    End While
    Return users
    Finally
    If Not dr Is Nothing AndAlso Not dr.IsClosed Then
    dr.Close()
    End If
    connection.Dispose()
    command.Dispose()
    End Try
    End Function
    
    //C#
    public UserCollection GetAllUsers() {
    SqlConnection connection = new SqlConnection(CONNECTION_STRING);
    SqlCommand command =new SqlCommand("GetAllUsers", connection);
    SqlDataReader dr = null;
    try{
    connection.Open();
    dr = command.ExecuteReader(CommandBehavior.SingleResult);
    UserCollection users = new UserCollection();
    while (dr.Read()){
    users.Add(PopulateUser(dr));
      }
    return users;
    }finally{
    if (dr != null && !dr.IsClosed){
    dr.Close();
      }
    connection.Dispose();
    command.Dispose();
     }
    }
    

    我们从数据库中获得数据、创建自定义集合,然后通过在结果中循环来创建每个 User 对象并将其添加到集合中。同样要注意 PopulateUser 映射函数是如何重新使用的。

    添加自定义行为

    在讨论自定义实体时,我们只是泛泛地提到可以将自定义行为添加到类中。您向实体中添加的功能类型很大程度上取决于您要实现的业务逻辑的类型,但您可能希望在自定义集合中实现某些常见的功能。一个示例就是返回一个基于某个键的实体,例如基于 userId 的用户:

    'Visual Basic .NET
    Public Function FindUserById(ByVal userId As Integer) As User
    For Each user As User In List
    If user.UserId = userId Then
    Return user
    End If
    Next
    Return Nothing
    End Function
    
    //C#
    public User FindUserById(int userId) {
    foreach (User user in List) {
    if (user.UserId == userId){
    return user;
      }
     }
    return null;
    }
    

    另一个示例可能是返回基于特定标准(例如部分用户名)的用户子集:

    'Visual Basic .NET
    Public Function FindMatchingUsers(ByVal search As String) As UserCollection
    If search Is Nothing Then
    Throw New ArgumentNullException("search cannot be null")
    End If
    Dim matchingUsers As New UserCollection
    For Each user As User In List
    Dim userName As String = user.UserName
    If Not userName Is Nothing And userName.StartsWith(search) Then
    matchingUsers.Add(user)
    End If
    Next
    Return matchingUsers
    End Function
    
    //C#
    public UserCollection FindMatchingUsers(string search) {
    if (search == null){
    throw new ArgumentNullException("search cannot be null");
     }
    UserCollection matchingUsers = new UserCollection();
    foreach (User user in List) {
    string userName = user.UserName;
    if (userName != null && userName.StartsWith(search)){
    matchingUsers.Add(user);
      }
     }
    return matchingUsers;
    }
    

    可以通过 DataTable.Select 以相同的方式使用 DataSets。需要说明的重要一点是,尽管创建自己的功能使您可以完全控制您的代码,但 Select 方法为完成同样的操作提供了一个非常方便且不需要编写代码的方法。但另一方面,Select 需要开发人员了解基础数据库,而且它不是强类型。

    绑定自定义集合

    我们看到的第一个示例是将 DataSet 绑定到 ASP.NET 控件。考虑到它很普通,您会高兴地发现自定义集合绑定同样很简单(这是因为 CollectionBase 实现了用于绑定的 Ilist)。自定义集合可以作为任何控件的 DataSource,而 DataBinder.Eval 只能像您使用 DataSet 那样使用:

    'Visual Basic .NET
    Dim users as UserCollection = DAL.GetallUsers()
    repeater.DataSource = users
    repeater.DataBind()
    
    //C#
    UserCollection users = DAL.GetAllUsers();
    repeater.DataSource = users;
    repeater.DataBind();
    
    <!-- HTML -->
    <asp:Repeater onItemDataBound="r_IDB" ID="repeater" Runat="server">
    <ItemTemplate>
    <asp:Label ID="userName" Runat="server">
    <%# DataBinder.Eval(Container.DataItem, "UserName") %><br />
    </asp:Label>
    </ItemTemplate>
    </asp:Repeater>
    

    您可以不使用列名称作为 DataBinder.Eval 的第二个参数,而指定您希望显示的属性名称,在本例中为 UserName

    对于在许多数据绑定控件提供的 OnItemDataBoundOnItemCreated 中执行处理的人来说,您可能会将 e.Item.DataItem 强制转换成 DataRowView。当绑定到自定义集合时,e.Item.DataItem 则被强制转换成自定义实体,在我们的示例中为 User 类:

    'Visual Basic .NET
    Protected Sub r_ItemDataBound (s As Object, e As RepeaterItemEventArgs)
    Dim type As ListItemType = e.Item.ItemType
    If type = ListItemType.AlternatingItem OrElse
    ?   type = ListItemType.Item Then
    Dim u As Label = CType(e.Item.FindControl("userName"), Label)
    Dim currentUser As User = CType(e.Item.DataItem, User)
    If Not PasswordUtility.PasswordIsSecure(currentUser.Password) Then
    ul.ForeColor = Drawing.Color.Red
    End If
    End If
    End Sub
    
    //C#
    protected void r_ItemDataBound(object sender, RepeaterItemEventArgs e) {
    ListItemType type = e.Item.ItemType;
    if (type == ListItemType.AlternatingItem || 
    ?    type == ListItemType.Item){
    Label ul = (Label)e.Item.FindControl("userName");
     User currentUser = (User)e.Item.DataItem;
    if (!PasswordUtility.PasswordIsSecure(currentUser.Password)){
    ul.ForeColor = Color.Red;
      }
     }
    }
    

    管理关系

    即使在最简单的系统中,实体之间也存在关系。对于关系数据库,可以通过外键维护关系;而使用对象时,关系只是对另一个对象的引用。例如,根据我们前面的示例,User 对象完全可以具有一个 Role

    'Visual Basic .NET
    Public Class User
    Private _role As Role
    Public Property Role() As Role
    Get
    Return _role
    End Get
    Set(ByVal Value As Role)
    _role = Value
    End Set
    End Property
    End Class
    
    //C#
    public class User {
    private Role role;
    public Role Role {
    get {return role;}
    set {role = value;}
     }
    }
    

    或者一个 Role 集合:

    'Visual Basic .NET
    Public Class User
    Private _roles As RoleCollection
    Public ReadOnly Property Roles() As RoleCollection
    Get
    If _roles Is Nothing Then
    _roles = New RoleCollection
    End If
    Return _roles
    End Get
    End Property
    End Class
    
    //C#
    public class User {
    private RoleCollection roles;
    public RoleCollection Roles {
    get {
    if (roles == null){
    roles = new RoleCollection();
       }
    return roles;
      }
     }
    }
    

    在这两个示例中,我们有一个虚构的 Role 类或 RoleCollection 类,它们就是类似于 UserUserCollection 类的其他自定义实体或集合类。

    映射关系

    真正的问题在于如何映射关系。让我们看一个简单的示例,我们希望根据 userId 及其角色来检索一个用户。首先,我们看一看关系模型:


    3User Role 之间的关系

    这里,我们看到了一个 User 表和一个 Role 表,我们可以将这两个表都以直观的方式映射到自定义实体。我们还有一个 UserRoleJoin 表,它代表了 UserRole 之间的多对多关系。

    然后,我们使用存储过程来获取两个单独的结果:第一个代表 User,第二个代表该用户的 Role

    CREATE PROCEDURE GetUserById(
    @UserId INT
    )AS
    SELECT UserId, UserName, [Password]
    FROM Users
    WHERE UserId = @UserID
    SELECT R.RoleId, R.[Name], R.Code
    FROM Roles R INNER JOIN
    UserRoleJoin URJ ON R.RoleId = URJ.RoleId
    WHERE  URJ.UserId = @UserId
    

    最后,我们从关系模型映射到对象模型:

    'Visual Basic .NET
    Public Function GetUserById(ByVal userId As Integer) As User
    Dim connection As New SqlConnection(CONNECTION_STRING)
    Dim command As New SqlCommand("GetUserById", connection)
    command.Parameters.Add("@UserId", SqlDbType.Int).Value = userId
    Dim dr As SqlDataReader = Nothing
    Try
    connection.Open()
    dr = command.ExecuteReader()
    Dim user As User = Nothing
    If dr.Read() Then
    user = PopulateUser(dr)
    dr.NextResult()
    While dr.Read()
    user.Roles.Add(PopulateRole(dr))
    End While
    End If
    Return user
    Finally
    If Not dr Is Nothing AndAlso Not dr.IsClosed Then
    dr.Close()
    End If
    connection.Dispose()
    command.Dispose()
    End Try
    End Function
    
    //C#
    public User GetUserById(int userId) {
    SqlConnection connection = new SqlConnection(CONNECTION_STRING);
    SqlCommand command = new SqlCommand("GetUserById", connection);
    command.Parameters.Add("@UserId", SqlDbType.Int).Value = userId;
    SqlDataReader dr = null;
    try{
    connection.Open();
    dr = command.ExecuteReader();
    User user = null;
    if (dr.Read()){
    user = PopulateUser(dr);
    dr.NextResult();
    while(dr.Read()){
    user.Roles.Add(PopulateRole(dr));
       }            
      }
    return user;
    }finally{
    if (dr != null && !dr.IsClosed){
    dr.Close();
      }
    connection.Dispose();
    command.Dispose();
     }
    }
    

    User 实例即被创建和填充;我们转移到下一个结果/选择并进行循环,填充 Role 并将它们添加到 User 类的 RolesCollection 属性中。

    高级内容

    本指南的目的是介绍自定义实体与集合的概念及使用。使用自定义实体是业界广泛采用的做法,因此,也就产生了同样多的模式以处理各种情况。设计模式具有优势的原因有很多。首先,在处理具体的情况时,您可能不是第一次碰到某个给定的问题。设计模式使您可以重新使用给定问题的已经过尝试和测试的解决方案(虽然设计模式并不意味着全盘照抄,但它们几乎总是能够为解决方案提供一个可靠的基础)。相应地,这使您对系统随着复杂性增加而进行缩放的能力充满了信心,不仅因为它是一个广泛使用的方法,还因为它具有详尽的记录。设计模式还为您提供了一个通用的词汇表,使知识的传播和传授更容易实现。

    不能说设计模式只适用于自定义实体,实际上许多设计模式都并非如此。但是,如果您找机会试一下,您可能会惊喜地发现许多记载详尽的模式确实适用于自定义实体和映射过程。

    最后这一部分专门介绍大型或较复杂的系统可能会碰到的一些高级情况。因为大多数主题都可能值得您单独学习,所以我会尽量为您提供一些入门资料。

    Martin Fowler 的 Patterns of Enterprise Application Architecture 就是一个很好的入门材料,它不仅可以作为常见设计模式的优秀参考(具有详细的解释和大量的示例代码),而且它的前 100 页确实可以让您透彻地了解整个概念。另外,Fowler 还提供了一个联机模式目录,它对于已经熟悉概念但需要一个便利参考的人士很有用。

    并发

    前面的示例介绍的都是从数据库中提取数据并根据这些数据创建对象。总体而言,更新、删除和插入数据等操作是很直观的。我们的业务层负责创建对象、将对象传递给数据访问层,然后让数据访问层处理对象世界与关系世界之间的映射。例如:

    'Visual Basic .NET
    Public sub UpdateUser(ByVal user As User)
    Dim connection As New SqlConnection(CONNECTION_STRING)
    Dim command As New SqlCommand("UpdateUser", connection)
    ' 可以借助可重新使用的函数对此进行反向映射
    command.Parameters.Add("@UserId", SqlDbType.Int)
    command.Parameters(0).Value = user.UserId
    command.Parameters.Add("@Password", SqlDbType.VarChar, 64)
    command.Parameters(1).Value = user.Password
    command.Parameters.Add("@UserName", SqlDbType.VarChar, 128)
    command.Parameters(2).Value = user.UserName
    Try
    connection.Open()
    command.ExecuteNonQuery()
    Finally
    connection.Dispose()
    command.Dispose()
    End Try
    End Sub
    
    //C#
    public void UpdateUser(User user) {
    SqlConnection connection = new SqlConnection(CONNECTION_STRING);
    SqlCommand command = new SqlCommand("UpdateUser", connection);
    // 可以借助可重新使用的函数对此进行反向映射
    command.Parameters.Add("@UserId", SqlDbType.Int);
    command.Parameters[0].Value = user.UserId;
    command.Parameters.Add("@Password", SqlDbType.VarChar, 64);
    command.Parameters[1].Value = user.Password; 
    command.Parameters.Add("@UserName", SqlDbType.VarChar, 128);
    command.Parameters[2].Value = user.UserName;
    try{
    connection.Open();
    command.ExecuteNonQuery();
    }finally{
    connection.Dispose();
    command.Dispose();
     }
    }
    

    但在处理并发时就不那么直观了,也就是说,当两个用户试图同时更新相同的数据时会出现什么情况呢?默认的行为(如果您没有执行任何操作)是最后提交数据的人将覆盖以前所有的工作。这可能不是理想的情况,因为一个用户的工作将在未获得任何提示的情况下被覆盖。要完全避免所有冲突,一种方法就是使用消极的并发技术;但此方法需要具有某种锁定机制,这可能很难通过可缩放的方式实现。替代方法就是使用积极的并发技术。让第一个提交的用户控制并通知后面的用户是通常采取的更温和、更用户友好的方法。这可以通过某种行版本控制(例如时间戳)来实现。

    参考资料:

    Introduction to Data Concurrency in ADO.NET

    CSLA.NET's concurrency techniques

    Unit of Work design pattern

    Optimistic offline lock design pattern

    Pessimistic offline lock design pattern

    性能

    与合理的灵活性和功能问题相对的是,我们经常担心细小的性能差异。尽管性能的确很重要,但提供适用于一切情况而不是最简单情况的通用原则通常很难。例如,将自定义集合与 DataSet 相比,哪个更快?使用自定义集合,您可以大量使用 DataReader,这是从数据库中提取数据的较快方式。但答案实际上取决于您使用它们的方式以及处理的数据类型,所以一般性的说明没有任何用。更重要的一点是要认识到,不管您能节省多少处理时间,与维护性方面的差异相比都可能微不足道。

    当然,并不是说您不可能找到一个既具有高性能又可维护的解决方案。虽然我强调说答案实际上取决于您的使用方式,但的确有一些模式可以帮助您最大程度地提高性能。但是,首先要知道的是自定义实体与集合缓存以及 DataSet,并且能够利用相同的机制(类似于 HttpCache)。DataSet 的优势之一是它能够编写 Select 语句,以便只获取所需的信息。使用自定义实体时,您常常感到不得不填充整个实体以及子实体。例如,如果要通过 DataSet 显示一个 Organization 列表,您可以只提取 OganizationIdNameAddress 并将其绑定到重复器。使用自定义实体时,我总觉得还需要获取所有其他的 Organization 信息,如果该组织通过了 ISO 认证,则可能是一个位标记,即所有员工、其他联系信息等的集合。可能其他人没有碰到这个大难题,但幸运的是,如果我们愿意,我们可以对自定义实体进行很好的控制。最常用的方法是使用一种延迟加载模式,它只在首次需要时获取信息(可以很好地封装在属性中)。这种对各个属性的控制提供了通过其他方式无法轻易获得的巨大灵活性(请想象一下在 DataColumn 级别执行类似操作的情况)。

    参考资料:

    Lazy Load 设计模式

    CSLA.NET lazy load

    排序与筛选

    虽然 DataView 对排序和筛选的内置支持需要您了解有关 SQL 和基础数据结构的知识,但它提供的方便确实是自定义集合所不具备的。我们仍然可以排序和筛选,但首先需要编写功能。因为技术不一定是最先进的,所以代码的完整描述不属于本节要讨论的范围。大多数技术都很相似,例如使用筛选器类筛选集合以及使用比较器类进行排序,我认为不存在固定的模式。但是,的确存在一些参考资料:

    Generic sort function

    Sorting & Filtering Custom Collections 教程

    代码生成

    解决概念上的障碍后,自定义实体与集合的主要缺点就是灵活性、抽象和维护性差所导致的代码数量的增加。实际上,您可能会认为我所说的维护成本和错误的降低这一切都抵不上代码的增加。虽然这一观点是成立的(同样,因为任何解决方案都不是完美无缺的),但可以通过设计模式和框架(例如 CSLA.NET)大大缓解此问题。代码生成工具与模式和框架完全不同,这些工具可以大大降低您实际需要编写的代码数量。本指南最初打算专门辟出一节详细介绍代码生成工具,特别是流行的免费 CodeSmith;但现有的许多参考资料都可能超出了我自己对该产品的认识。

    在继续之前,我认识到代码生成听起来像天方夜谭一样。但经过正确的使用和理解后,它的确是您工具包中不可缺少的一个强大的武器,即使您没有处理自定义实体也是如此。虽然代码生成的确不仅仅适用于自定义实体,但很多都是专为自定义实体而设计的。原因很简单:自定义实体需要大量重复代码。

    简言之,代码生成是如何工作的?构想听起来好像遥不可及甚至反而会降低效率,但您基本上通过编写代码(模板)来生成代码。例如,CodeSmith 附带了许多强大的类,使您可以连接到数据库并获取所有属性:表、列(类型、大小等)和关系。获得这些信息后,我们前面讨论的大部分工作都可以自动完成。例如,开发人员可以选择一个表,然后使用正确的模板自动创建自定义实体(带有正确的字段、属性和构造函数),并获得映射函数、自定义集合以及基本的选择、插入、更新和删除功能。甚至还可以更进一步,实现排序、筛选以及我们提到的其他高级功能。

    CodeSmith 还附带了许多现成的模板,可以作为很好的学习资料。最后,CodeSmith 还为实现 CSLA.NET 框架提供了许多模板。我最初只花了几个小时来学习基本概念、熟悉 CodeSmith 的功能,但它为我节省的时间已经多得无法计算了。另外,如果所有的开发人员都使用相同的模板,代码的高度一致性将使您能够轻松地继续其他人的工作。

    参考资料:

    Code Generation with CodeSmith

    CodeSmith 主页

    O/R 映射器

    即使因为对 O/R 映射器知之甚少使我不敢随便对它们发表议论,但它们自身的潜在价值使其不容忽视。代码生成器生成基于模板的代码,供您复制并粘贴到您自己的源代码中,而 O/R 映射器则在运行时通过某种配置机制动态生成代码。例如,在 XML 文件中,您可以指定某个表的列 X 映射到某个实体的属性 Y。您仍然需要创建自定义实体,但是集合、映射和其他数据访问函数(包括存储过程)都是动态创建的。从理论上讲,O/R 映射器几乎可以完全解决自定义实体存在的问题。随着关系世界和对象世界的差异越来越明显以及映射过程越来越复杂,O/R 映射器的价值就变得越发不可限量了。O/R 映射器的两个缺点据说就是不够安全和性能较差(至少在 .NET 环境中是这样)。根据我所阅读的资料,我确信它们并不是不够安全,虽然在有些情况下性能较差,但在另外一些情况下却表现突出。O/R 映射器并不适合所有情况,但如果您要处理复杂的系统,则应尝试一下它们的功能。

    参考资料:

    Mapper 设计模式

    Data Mapper 设计模式

    Wilson ORMapper

    Frans Bouma 关于 O/R 映射的帖子

    LLBGenPro

    NHibernate

    .NET Framework 2.0 的功能

    即将面世的 .NET Framework 2.0 版将改变我们在本指南中讨论的一些实施细节。这些改变将减少支持自定义实体所需的代码数量,并有助于处理映射问题。

    泛型

    议论颇多的泛型之所以存在,主要原因之一就是为了向开发人员提供现成的强类型的集合。我们避开 Arraylist 等现有集合是因为它们属于弱类型。泛型提供了与当前集合同样的方便性,而且它们属于强类型。这是通过在声明时指定类型来实现的。例如,我们可以替换 UserCollection 而不需要增加代码,然后只需创建一个 List<T> 泛型的新实例并指定我们的 User 类即可:

    'Visual Basic .NET
    Dim users as new IList(of User)
    
    //C#
    IList<User> users = new IList<user>();
    

    声明后,我们的 user 集合就只能处理 User 类型的对象了,这为我们提供了编译时检查和优化的所有优点。

    参考资料:

    Introducing .NET Generics

    An Introduction to C# Generics

    可以为空的类型

    可以为空的类型实际上就是由于其他原因而非上述原因而使用的泛型。处理数据库时面临的挑战之一就是正确一致地处理支持 NULL 的列。在处理字符串和其他类(称为引用类型)时,您只需为代码中的某个变量指定 nothing/null

    'Visual Basic .NET
    if dr("UserName") Is DBNull.Value Then
    user.UserName = nothing
    End If
    
    //C#
    if (dr["UserName"] == DBNull.Value){
    user.UserName = null;
    }
    

    也可以什么都不做(默认情况下,引用类型为 nothing/null)。这对值类型(例如整数布尔值小数等)并不完全一样。您当然也可以为这些值指定 nothing/null,但这样将会指定一个默认值。如果您只声明整数,或者为其指定 nothing/null,变量的值实际上将为 0。这使其很难映射回数据库:值究竟为 0 还是 null?可以为空的类型允许值类型具有具体的值或者为空,从而解决了这个问题。例如,如果我们要在 userId 列中支持 null 值(并不是很符合实际情况),我们会首先将 userId 字段和对应的属性声明为可以为空的类型:

    'Visual Basic .NET
    Private _userId As Nullable(Of Integer)
    Public Property UserId() As Nullable(Of Integer)
    Get
    Return _userId
    End Get
    Set(ByVal value As Nullable(Of Integer))
    _userId = value
    End Set
    End Property
    
    
    //C#
    private Nullable<int> userId;
    public Nullable<int> UserId {
    get { return userId; }
    set { userId = value; }
    }
    

    然后利用 HasValue 属性判断是否指定了 nothing/null

    'Visual Basic .NET
    If UserId.HasValue Then
    Return UserId.Value
    Else
    Return DBNull.Value
    End If
    
    //C#
    if (UserId.HasValue) {
    return UserId.Value;
    } else {
    return DBNull.Value;
    }
    

    参考资料:

    Nullable types in C#

    Nullable types in VB.NET

    迭代程序

    我们前面讨论的 UserCollection 示例只展示了自定义集合中可能需要的基本功能。有一个操作无法通过所提供的实现来完成,即通过一个 foreach 循环在集合中循环。要完成此操作,您的自定义集合必须具有实现 IEnumerable 接口的枚举数支持类。这是一个非常直观且重复性较强的过程,但却引入了更多的代码。C# 2.0 引入了新的 yield 关键字来为您处理此接口的实现细节。Visual Basic .NET 中当前没有与新的 yield 关键字等效的关键字。

    参考资料:

    What's new In C# 2.0 - Iterators

    C# Iterators

    小结

    请勿轻率地做出向自定义实体与集合转换的决定。这里有许多需要考虑的因素。例如,您对 OO 概念的熟悉程度、可用来熟悉新方法的时间以及您打算部署它的环境。虽然总体上它们有很大的优点,但并不一定适合您的特定情况。即使适合您的情况,它们的缺点也可能会打消您使用它们的念头。还要记住有许多可替代的解决方案。Jimmy Nilsson 在他的 Choosing Data Containers for .NET 中概述了其中的某些替代方案,此专栏系列包括 5 部分(12345)。

    自定义实体使您获得了面向对象的编程的丰富功能,并帮助您构建了可靠、可维护的 N 层体系结构的框架。本指南的目的之一是让您从构成系统的业务实体,而不是一般的 DataSetDataTable 的角度来考虑您的系统。我们还讨论了一些关键的问题,不管您选择的途径(即设计模式)、对象世界与关系世界的差异(了解详细信息)以及 N 层体系结构是什么,您都应注意这些问题。请记住,您之前花费的时间会在系统的整个生命周期内为您带来更多的回报。

    相关书籍

    Microsoft ASP.NET Coding Strategies with the Microsoft ASP.NET Team

    Expert C# Business Objects

    Expert One-on-One Visual Basic .NET Business Objects

    1http://www.hanselman.com/blog/PermaLink.aspx?guid=d88f7539-10d8-4697-8c6e-1badb08bb3f5

    © 2005 Microsoft Corporation 版权所有。保留所有权利。使用规定

    浅谈“三层结构”原理与用意

     
    在刚刚步入“多层结构”Web应用程序开发的时候,我阅读过几篇关于“asp.net三层结构开发”的文章。但其多半都是对PetShop3.0和Duwamish7的局部剖析或者是学习笔记。对“三层结构”通体分析的学术文章几乎没有。

    2005年2月11日,Bincess BBS彬月论坛开始试运行。不久之后,我写了一篇题目为《浅谈“三层结构”原理与用意》的文章。旧版文章以彬月论坛程序中的部分代码举例,通过全局视角阐述了什么是“三层结构”的开发模式?为什么要这样做?怎样做?……而在这篇文章的新作中,配合这篇文章我写了7个程序实例(TraceLWord1~TraceLWord7留言板)以帮助读者理解“三层结构”应用程序。这些程序示例可以在随带的CodePackage目录中找到——

      对于那些有丰富经验的Web应用程序开发人员,他们认为文章写的通俗易懂,很值得一读。可是对于asp.net初学者,特别是没有任何开发经验的人,文章阅读起来就感到非常困难,不知文章所云。甚至有些读者对“三层结构”的认识更模糊了……
      关于“多层结构”开发模式,存在这样一种争议:一部分学者认为“多层结构”与“面向对象的程序设计思想”有着非常紧密的联系。而另外一部分学者却认为二者之间并无直接联系。写作这篇文章并不是要终结这种争议,其行文目的是希望读者能够明白:在使用asp.net进行Web应用程序开发时,实现“多层结构”开发模式的方法、原理及用意。要顺利的阅读这篇文章,希望读者能对“面向对象的程序设计思想”有一定深度的认识,最好能懂一些“设计模式”的知识。如果你并不了解前面这些,那么这篇文章可能并不适合你现在阅读。不过,无论这篇文章面对的读者是谁,我都会尽量将文章写好。我希望这篇文章能成为学习“三层结构”设计思想的经典文章!
    “三层结构”是什么?
      “三层结构”一词中的“三层”是指:“表现层”、“中间业务层”、“数据访问层”。其中:
     表 现 层:位于最外层(最上层),离用户最近。用于显示数据和接收用户输入的数据,为用户提供一种交互式操作的界面。
    中间业务层:负责处理用户输入的信息,或者是将这些信息发送给数据访问层进行保存,或者是调用数据访问层中的函数再次读出这些数据。中间业务层也可以包括一些对“商业逻辑”描述代码在里面。
    数据访问层:仅实现对数据的保存和读取操作。数据访问,可以访问数据库系统、二进制文件、文本文档或是XML文档。
    对依赖方向的研究将是本文的重点,数值返回方向基本上是没有变化的。

    为什么需要 “三层结构”?——通常的设计方式
      在一个大型的Web应用程序中,如果不分以层次,那么在将来的升级维护中会遇到很大的麻烦。但在这篇文章里我只想以一个简单的留言板程序为示例,说明通常设计方式的不足——
    功能说明:
        ListLWord.aspx(后台程序文件 ListLWord.aspx.cs)列表显示数据库中的每条留言。
        PostLWord.aspx(后台程序文件 PostLWord.aspx.cs)发送留言到数据库。
     
    仅仅通过两个页面,就完成了一个基于Access数据库的留言功能。

    程序并不算复杂,非常简单清楚。但是随后你会意识到其存在着不灵活性!
     
    为什么需要“三层结构”?——数据库升迁、应用程序变化所带来的问题

    留言板正式投入使用!但没过多久,我准备把这个留言板程序的数据库升迁到Microsoft SQL Server 2000服务器上去!除了要把数据导入到SQL Server 2000中,还得修改相应的.aspx.cs程序文件。也就是说需要把调用OleDbConnection的地方修改成SqlConnection,还要把调用OleDbAdapter的地方,修改成SqlAdapter。虽然这并不是一件很困难的事情,因为整个站点非常小,仅仅只有两个程序文件,所以修改起来并不费劲。但是,如果对于一个大型的商业网站,访问数据库的页面有很多很多,如果以此方法一个页面一个页面地进行修改,那么费时又费力!只是修改了一下数据库,却可能要修改上千张网页。一动百动,这也许就是程序的一种不灵活性……
     
    再假如,我想给留言板加一个限制:
    每天上午09时之后到11时之前可以留言,下午则是13时之后到17时之前可以留言
    如果当天留言个数小于 40,则可以继续留言

    那么就需要把相应的代码,添加到PostLWord.aspx.cs程序文件中。但是过了一段时间,我又希望去除这个限制,那么还要修改PostLWord.aspx.cs文件。但是,对于一个大型的商业网站,类似于这样的限制,或者称为“商业规则”,复杂又繁琐。而且这些规则很容易随着商家的意志为转移。如果这些规则限制被分散到各个页面中,那么规则一旦变化,就要修改很多的页面!只是修改了一下规则限制,却又可能要修改上千张网页。一动百动,这也许又是程序的一种不灵活性……
      最后,留言板使用过一段时间之后,出于某种目的,我希望把它修改成可以在本地运行的Windows程序,而放弃原来的Web型式。那么对于这个留言板,可以说是“灭顶之灾”。所有代码都要重新写……当然这个例子比较极端,在现实中,这样的情况还是很少会发生的——
     
    为什么需要“三层结构”?——初探,就从数据库的升迁开始
     

    一个站点中,访问数据库的程序代码散落在各个页面中,就像夜空中的星星一样繁多。这样一动百动的维护,难度可想而知。最难以忍受的是,对这种维护工作的投入,是没有任何价值的……
     

    有一个比较好的解决办法,那就是将访问数据库的代码全部都放在一个程序文件里。这样,数据库平台一旦发生变化,那么只需要集中修改这一个文件就可以了。我想有点开发经验的人,都会想到这一步的。这种“以不变应万变”的做法其实是简单的“门面模式”的应用。如果把一个网站比喻成一家大饭店,那么“门面模式”中的“门面”,就像是饭店的服务生,而一个网站的浏览者,就像是一个来宾。来宾只需要发送命令给服务生,然后服务生就会按照命令办事。至于服务生经历了多少辛苦才把事情办成?那个并不是来宾感兴趣的事情,来宾们只要求服务生尽快把自己交待事情办完。我们就把ListLWord.aspx.cs程序就看成是一个来宾发出的命令,而把新加入的LWordTask.cs程序看成是一个饭店服务生,那么来宾发出的命令就是:
    “给我读出留言板数据库中的数据,填充到DataSet数据集中并显示出来!”

    而服务生接到命令后,就会依照执行。而PostLWord.aspx.cs程序,让服务生做的是:
    “把我的留言内容写入到数据库中!”
    而服务生接到命令后,就会依照执行。这就是TraceLWord2!可以在CodePackage/TraceLWord2目录中找到——

    把所有的有关数据访问的代码都放到LWordTask.cs文件里,LWordTask.cs程序文件如下:
     
    如果将数据库从Access 2000修改为SQL Server 2000,那么只需要修改LWordTask.cs这一个文件。如果LWordTask.cs文件太大,也可以把它切割成几个文件或“类”。如果被切割成的“类”还是很多,也可以把这些访问数据库的类放到一个新建的“项目”里。当然,原来的ListLWord.aspx.cs文件应该作以修改,LWord_DataBind函数被修改成:
     

      从前面的程序段中可以看出,ListLWord.aspx.cs和PostLWord.aspx.cs这两个文件已经找不到和数据库相关的代码了。只看到一些和LWordTask类有关系的代码,这就符合了“设计模式”中的一种重要原则:“迪米特法则”。“迪米特法则”主要是说:让一个“类”与尽量少的其它的类发生关系。在TraceLWord1中,ListLWord.aspx.cs这个类和OleDbConnection及OleDbDataAdapter都发生了关系,所以它破坏了“迪米特法则”。利用一个“中间人”是“迪米特法则”解决问题的办法,这也是“门面模式”必须遵循的原则。下面就引出这个LWordTask门面类的示意图:

    ListLWord.aspx.cs和PostLWord.aspx.cs两个文件对数据库的访问,全部委托LWordTask类这个“中间人”来办理。利用“门面模式”,将页面类和数据库类进行隔离。这样就作到了页面类不依赖于数据库的效果。以一段比较简单的代码来描述这三个程序的关系:
     
    应用中间业务层,实现“三层结构”
     
    前面这种分离数据访问代码的形式,可以说是一种“三层结构”的简化形式。因为它没有“中间业务层”也可以称呼它为“二层结构”。一个真正的“三层”程序,是要有“中间业务层”的,而它的作用是连接“外观层”和“数据访问层”。换句话说:“外观层”的任务先委托给“中间业务层”来办理,然后“中间业务层”再去委托“数据访问层”来办理……
     
    那么为什么要应用“中间业务层”呢?“中间业务层”的用途有很多,例如:验证用户输入数据、缓存从数据库中读取的数据等等……但是,“中间业务层”的实际目的是将“数据访问层”的最基础的存储逻辑组合起来,形成一种业务规则。例如:“在一个购物网站中有这样的一个规则:在该网站第一次购物的用户,系统为其自动注册”。这样的业务逻辑放在中间层最合适:
     
    在“数据访问层”中,最好不要出现任何“业务逻辑”!也就是说,要保证“数据访问层”的中的函数功能的原子性!即最小性和不可再分。“数据访问层”只管负责存储或读取数据就可以了。
     

      在新TraceLWord3中,应用了“企业级模板项目”。把原来的LWordTask.cs,并放置到一个单一的项目里,项目名称为:AccessTask。解决方案中又新建了一个名称为:InterService的项目,该项目中包含一个LWordService.cs程序文件,它便是“中间业务层”程序。为了不重复命名,TraceLWord3的网站被放置到了WebUI项目中。更完整的代码,可以在CodePackage/TraceLWord3目录中找到——
     
    从LWordService.cs程序文件的行#021和行#030可以看出,“中间业务层”并没有实现什么业务逻辑,只是简单的调用了“数据访问层”的类方法……这样做是为了让读者更直观的看明白“三层结构”应用程序的调用顺序,看清楚它的全貌。加入了“中间业务层”,那么原来的ListLWord.aspx.cs文件应该作以修改:
     
    当一个用户访问TraceLWord5的ListLWord.aspx页面的时候,会触发该页面后台程序中的Page_Load函数。而在该函数中调用了LWord_DataBind函数来获取留言板信息。由图中可以看到出,LWord_DataBind在被调用的期间,会建立一个新的LWordService类对象,并调用这个对象的ListLWord函数。在LWordService.ListLWord函数被调用的期间,会建立一个新的LWordTask类对象,并调用这个对象的ListLWord来获取留言板信息的。PostLWord.aspx页面时序图,和上面这个差不多。就是这样,经过一层又一层的调用,来获取返回结果或是保存数据。

    注意:从时序图中可以看出,当子程序模块未执行结束时,主程序模块只能处于等待状态。这说明将应用程序划分层次,会带来其执行速度上的一些损失……
     
    对“三层结构”的深入理解——怎样才算是一个符合“三层结构”的Web应用程序?
     
    在一个ASP.NET Web应用程序解决方案中,并不是说有aspx文件、有dll文件、还有数据库,就是“三层结构”的Web应用程序,这样的说法是不对的。也并不是说没有对数据库进行操作,就不是“三层结构”的。其实“三层结构”是功能实现上的三层。例如,在微软的ASP.NET示范实例“Duwamish7”中,“表现层”被放置在“Web”项目中,“中间业务层”是放置在“BusinessFacade”项目中,“数据访问层”则是放置在“DataAccess”项目中……而在微软的另一个ASP.NET示范实例“PetShop3.0”中,“表现层”被放置在“Web”项目中,“中间业务层”是放置在“BLL”项目中,而“数据访问层”则是放置在“SQLServerDAL”和“OracleDAL”两个项目中。在Bincess.CN彬月论坛中,“表现层”是被放置在“WebForum”项目中,“中间业务(服务)层”是被放置在“InterService”项目中,而“数据访问层”是被放置在“SqlServerTask”项目中。
     

      如果只以分层的设计角度看,Duwamish7要比PetShop3.0复杂一些!而如果较为全面的比较二者,PetShop3.0则显得比较复杂。但我们先不讨论这些,对PetShop3.0和Duwamish7的研究,并不是本文的重点。现在的问题就是:既然“三层结构”已经被分派到各自的项目中,那么剩下来的项目是做什么的呢?例如PetShop3.0中的“Model”、“IDAL”、“DALFactory”这三个项目,再例如Duwamish7中的“Common”项目,还有就是在Bincess.CN彬月论坛中的“Classes”、“DbTask”、这两个项目。它们究竟是做什么用的呢?

    对“三层结构”的深入理解——从一家小餐馆说起

      一个“三层结构”的Web应用程序,就好象是一家小餐馆。
    n            表 现 层,所有的.aspx页面就好像是这家餐馆的菜谱。
    n            中间业务层,就像是餐馆的服务生。
    n            数据访问层,就像是餐馆的大厨师傅。
    n            而我们这些网站浏览者,就是去餐馆吃饭的吃客了……

    我们去一家餐馆吃饭,首先得看他们的菜谱,然后唤来服务生,告诉他我们想要吃的菜肴。服务生记下来以后,便会马上去通知大厨师傅要烹制这些菜。大厨师傅收到通知后,马上起火烧菜。过了不久,服务生便把一道一道香喷喷的、热气腾腾的美味端到我们的桌位上——
     
    而我们访问一个基于asp.net技术的网站的时候,首先打开的是一个aspx页面。这个aspx页面的后台程序会去调用中间业务层的相应函数来获取结果。中间业务层又会去调用数据访问层的相应函数来获取结果。在一个用户访问TraceLWord3打开ListLWord.aspx页面查看留言的时候,其后台程序ListLWord.aspx.cs会去调用位于中间业务层LWordService的ListLWord(DataSet ds)函数。然后这个函数又会去调用位于数据访问层AccessTask的ListLWord(DataSet ds)函数。最后把结果显示出来……

    从示意图看,这两个过程是否非常相似呢?

    不同的地方只是在于,去餐馆吃饭,需要吃客自己唤来服务生。而访问一个asp.net网站,菜单可以代替吃客唤来服务生。在最后的返回结果上,把结果返回给aspx页面,也就是等于把结果返回给浏览者了。
    高度的“面向对象思想”的体现——封装
    在我们去餐馆吃饭的这个过程中,像我这样在餐馆中的吃客,最关心的是什么呢?当然是:餐馆的饭菜是不是好吃,是不是很卫生?价格是不是公道?……而餐馆中的服务生会关心什么呢?应该是:要随时注意响应每位顾客的吩咐,要记住顾客在哪个桌位上?还要把顾客点的菜记在本子上……餐馆的大厨师傅会关心什么呢?应该是:一道菜肴的做法是什么?怎么提高烧菜的效率?研究新菜式……大厨师傅,烧好菜肴之后,只管把菜交给服务生就完事了。至于服务生把菜送到哪个桌位上去了?是哪个顾客吃了他做的菜,大厨师傅才不管咧——服务生只要记得把我点的菜肴端来,就成了。至于这菜是怎么烹饪的?顾客干麻要点这道菜?他才不管咧——而我,只要知道这菜味道不错,价格公道,干净卫生,其他的我才不管咧——
     
    这里面不正是高度的体现了“面向对象思想”的“封装”原则吗?
     
    无论大厨师傅在什么时候研究出新的菜式,都不会耽误我现在吃饭。就算服务生忘记我的桌位号是多少了,也不可能因此让大厨师傅忘记菜肴的做法?  在我去餐馆吃饭的这个过程中,我、餐馆服务生、大厨师傅,是封装程度极高的三个个体。当其中的一个个体内部发生变化的时候,并不会波及到其他个体。这便是面向对象封装特性的一个益处!
    土豆炖牛肉盖饭与实体规范
     
      在我工作过的第一家公司楼下,有一家成都风味的小餐馆,每天中午我都和几个同事一起去那家小餐馆吃饭。公司附近只有这么一家餐馆,不过那里的饭菜还算不错。我最喜欢那里的“土豆炖牛肉盖饭”,也很喜欢那里的“鸡蛋汤”,那种美味至今难忘……所谓“盖饭”,又称是“盖浇饭”,就是把烹饪好的菜肴直接遮盖在铺在盘子里的米饭上。例如“土豆炖牛肉盖饭”,就是把一锅热气腾腾的“土豆炖牛肉”遮盖在米饭上——
    当我和同事再次来到这家餐馆吃饭,让我们想象以下这样的情形:

    情形一:

    我对服务生道:给我一份好吃的!
    服务生道:什么好吃的?
    我答道:一份好吃的——
    三番几次……
    我对服务生大怒道:好吃的,好吃的,你难道不明白吗?!——
    这样的情况是没有可能发生的!因为我没有明确地说出来我到底要吃什么?所以服务生也没办法为我服务……
    问题后果:我可能被送往附近医院的精神科……
    情形二:
    我对服务生道:给我一份土豆炖牛肉盖饭!

    服务生对大厨师傅道:做一份宫爆鸡丁——
    这样的情况是没有可能发生的!因为我非常明确地说出来我要吃土豆炖牛肉盖饭!但是服务生却给我端上了一盘宫爆鸡丁?!
    问题后果:我会投诉这个服务生的……
    情形三:
    我对服务生道:给我一份土豆炖牛肉盖饭!
    服务生对大厨师傅道:做一份土豆炖牛肉盖饭——
    大厨师傅道:宫爆鸡丁做好了……
    这样的情况是没有可能发生的!因为我非常明确地说出来我要吃土豆炖牛肉盖饭!服务生也很明确地要求大厨师傅做一份土豆炖牛肉盖饭。但是厨师却烹制了一盘宫爆鸡丁?!
    问题后果:我会投诉这家餐馆的……

    情形四:
    我对服务生道:给一份土豆炖牛肉盖饭!
    服务生对大厨师傅道:做一份土豆炖牛肉盖饭——
    大厨师傅道:土豆炖牛肉盖饭做好了……
    服务生把盖饭端上来,放到我所在的桌位。我看着香喷喷的土豆炖牛肉盖饭,举勺下口正要吃的时候,却突然发现这盘土豆炖牛肉盖饭变成了石头?!
    这样的情况更是没有可能发生的!必定,现实生活不是《西游记》。必定,这篇文章是学术文章而不是《哈里波特》……
    问题后果:……

    如果上面这些荒唐的事情都成了现实,那么我肯定永远都不敢再来这家餐馆吃饭了。这些让我感到极大的不安。而在TraceLWord3这个项目中呢?似乎上面这些荒唐的事情都成真了。(我想,不仅仅是在TraceLWord3这样的项目中,作为这篇文章的读者,你是否也经历过像这一样荒唐的项目而全然未知呢?)
     
    看到这里,我感到很是欣慰!我只能说我们的大厨师傅是一个厚道的人,而且还是很知我心的人。

      我们不能只坐在那里期盼着我们的程序会往好的方向发展,这样很被动。写出上面的这些程序段,必须小心翼翼。就连数据库表中的字段命名都要一审再审。一旦变化,就直接影响到位于“表现层”的ListLWord.aspx文件。仅仅是为了顺利的完成TraceLWord3这个“大型项目”,页面设计师要和程序员还有数据库管理员要进行额外的沟通。我们需要一个“土豆炖牛肉盖饭”式的强制标准!——
    引入实体规范

    为了达到一种“土豆炖牛肉盖饭”式的强制标准,所以在TraceLWord4中,引入了Classes项目。在这个项目里,只有一个LWord.cs程序文件。这是一个非常重要的文件,它属于“实体规范层”,如果是在一个Java项目中,Classes可以看作是:“实体Bean”。更完整的代码,可以在CodePackage/TraceLWord4目录中找到——
      这样,即便是将LWordTask.cs文件中的ListLWords方法修改成访问[RegUser]数据表的代码,也依然不会影响到外观层。因为函数只返回一个LWord类型的数组。再有,因为位于外观层的重复器控件绑定的是LWord类对象,而LWord类中就必有对TextContent字段的定义。这样也就达到了规范数据访问层返回结果的目的。这便是为什么在Duwamish7中会出现Common项目的原因。不知道你现在看明白了么?而Bincess.CN的做法和PetShop3.0一样,是通过自定义类来达到实体规范层的目的!PetShop3.0是通过Modal项目,而Bincess.CN则是通过Classes项目。

    餐馆又来了一位新大厨师傅——谈谈跨越数据库平台的问题
    餐馆面积不大,但生意很火。每天吃饭的人都特别多。为了加快上菜的速度,所以餐馆又找来了一位新的大厨师傅。假如,TraceLWord4为了满足一部分用户对性能的较高需要,要其数据库能使用MS SQL Server 2000。那么我们该怎么办呢?数据库要从Access 2000升迁到MS SqlServer 2000,那么只要集中修改AccessTask项目中的程序文件就可以了。但是,我又不想让这样经典的留言板失去对Access 2000数据库的支持。所以,正确的做法就是把原来所有的程序完整的拷贝一份放到另外的一个目录里。然后集中修改AccessTask项目,使之可以支持MS SQL Server 2000。这样这个留言板就有了两个版本,一个是Access 2000版本,另外一个就是MS SQL Server 2000版本……新的大厨师傅过来帮忙了,我们有必要让原来表现极佳的大厨师傅下课吗?可这样,新大厨师傅不是等于没来一样?新的大厨师傅过来帮忙了,我们有必要为新来的大厨师傅重新配备一套餐馆服务生系统、菜单系统吗?当然也没必要!那么,可不可以让TraceLWord4同时支持Access 2000又支持MS SQL Server 2000呢?也就是说,不用完整拷贝原来的程序,而是在解决方案里加入一个新的项目,这个项目存放的是可以访问MS SQL Server 2000数据库的代码。然后,我们再通过一个“开关”来进行控制,当开关指向Access 2000一端时,TraceLWord4就可以运行在Access 2000数据库平台上,而如果开关指向MS SQL Server 2000那一端时,TraceLWord4就运行在MS SQL Server 2000数据库平台上……
    在TraceLWord5中,加入了一个新项目SqlServerTask,这个项目的代码是访问的MS SQL Server 2000数据库。还有一个新建的项目DALFactory,这个项目就是一个“开关”。这个“开关”项目中仅有一个DbTaskDriver.cs程序文件,就是用它来控制TraceLWord5到底运行载那个数据库平台上?
     
    关于TraceLWord5,更完整的代码,可以在CodePackage/TraceLWord5目录中找到——
    DALFactory项目,其实就是“数据访问层工厂”,而DbTaskDriver类就是一个工厂类。也就是说DALFactory项目是“工厂模式”的一种应用。关于“工厂模式”,顾名思义,工厂是制造产品的地方,而“工厂模式”,就是通过“工厂类”来制造对象实例。“工厂类”可以通过给定的条件,动态地制造不同的对象实例。就好像下面这个样子:

    // 水果基类
    public class Fruit;
    // 苹果是一种水果
    public class Apple : Fruit;
    // 句子是一种水果
    public class Orange : Fruit;
     // 水果工厂类
    public class FruitFactory

      工厂类制造对象实例,实际通常是要通过语言所提供的RTTI(RunTime Type Identification运行时类型识别)机制来实现。在Visual C#.NET中,是通过“反射”来实现的。它被封装在“System.Reflection”名称空间下,通过C#反射,我们可以在程序运行期间动态地建立对象。关于C#.NET反射,你可以到其它网站上搜索一下相关资料,这里就不详述了。左边是工厂模式的UML示意图。
     

    通过修改配置文件中的关键信息,就可以修改留言板的数据库运行平台。这样便做到了跨数据库平台的目的。
    用户在访问TraceLWord5的ListLWord.aspx页面时序图:
    当一个用户访问TraceLWord5的ListLWord.aspx页面的时候,会触发该页面后台程序中的Page_Load函数。而在该函数中调用了LWord_DataBind函数来获取留言板信息。由图中可以看到出,LWord_DataBind在被调用的期间,会建立一个新的LWordService类对象,并调用这个对象的ListLWord函数。在LWordService.ListLWord函数被调用的期间,会建立一个新的DALFactory.DbTaskDriver类对象,并调用这个对象的DriveLWordTask函数来建立一个真正的数据访问层对象。在代码中,DriveLWordTask函数需要读取应用程序配置文件。当一个真正的数据访问层类对象被建立之后,会返给调用者LWordService.ListLWord,调用者会继续调用这个真正的数据访问层类对象的GetLWords函数,最终取到留言板数据。PostLWord.aspx页面时序图,和上面这个差不多。就是这样,经过一层又一层的调用,来获取返回结果或是保存数据。

    注意:从时序图中可以看出,当子程序模块未执行结束时,主程序模块只能处于等待状态。这说明将应用程序划分层次,会带来其执行速度上的一些损失……
    烹制土豆烧牛肉盖饭的方法论
    TraceLWord5已经实现了跨数据库平台的目的。但是稍微细心一点就不难发现,TraceLWord5有一个很致命的缺点。那就是如果要加入对新的数据库平台的支持,除去必要的新建数据访问层项目以外,还要在中间业务层InsetService项目中添加相应的依赖关系和代码。例如,新加入了对Oracle9i的数据库支持,那么除去要新建一个OracleTask项目以外,还要在LWordService中添加对OracleTask项目的依赖关系,并增加代码如下:

    每加入对新数据库的支持,就要修改中间业务层,这是件很麻烦的事情。再有就是,这三个数据访问层,获取留言板信息的方法似乎是各自为政,没有统一的标准。在AccessTask项目中使用的是ListLWord函数来获取留言信息;而在SqlServerTask项目中则是使用GetLWords函数来获取;再到了OracleTask又是换成了FetchLWords……
     
    餐馆服务生也许会对新来的大厨师傅很感兴趣,或许也会对新来的大厨师傅的手艺很感兴趣。但是这些餐馆服务生,绝对不会去背诵哪位大厨师傅会做什么样的菜,哪位大厨师傅不会做什么样的菜?也不会去在意同样的一道菜肴,两位大厨师傅不同的烹制步骤是什么?对于我所点的“土豆炖牛肉盖饭”,餐馆服务生只管对着厨房大声叫道:“土豆炖牛盖饭一份!”,饭菜马上就会做好。至于是哪个厨师做出来的,服务生并不会关心。其实服务生的意思是说:“外面有个顾客要吃‘土豆炖牛肉盖饭’,你们两个大厨师傅,哪位会做这个,马上给做一份……”。如果新来的大厨师傅不会做,那么原来的大厨师傅会担起此重任。如果新来的大厨师傅会做,那么两个大厨师傅之间谁现在更悠闲一些就由谁来做。
     
    在TraceLWord5中,两个数据访问层,都可以获取和保存留言信息,只是他们各自的函数名称不一样。但是对于中间业务层,却必须详细的记录这些,这似乎显得有些多余。仅仅是为了顺利的完成TraceLWord5这个“大型项目”,负责中间业务层的程序员要和负责数据访问层的程序员进行额外的沟通。TraceLWord5中,一个真正的数据访问层对象实例,是由DALFactory名称空间中的DbTaskDriver类制造的。如果中间业务层只需要知道“这个真正的数据访问层对象实例”有能力获取留言板和存储留言板,而不用关心其内部实现,那么就不会随着数据访问层项目的增加,而修改中间业务层了。换句直白的话来说就是:如果所有的数据访问层对象实例,都提供统一的函数名称“ListLWord函数”和“PostLWord函数”,那么中间业务层就不需要判断再调用了。我们需要“烹制土豆烧牛肉盖饭的方法论”的统一!——

    烹制土豆炖牛肉盖饭方法论的统一——接口实现

    怎么实现“烹制土豆烧牛肉盖饭方法论”的统一呢?答案是应用接口。在TraceLWord6中,新建了一个DbTask项目,里面只有一个ILWordTask.cs程序文件,在这里定义了一个接口。DbTask项目应该属于“抽象的数据访问层”。更完整的代码,可以在CodePackage/TraceLWord6目录中找到——

    至此为止,一个简单的“三层结构”Web应用程序的执行全过程已经尽显在你眼前。执行顺序其实并不复杂。

    加入商业规则

    “商业规则”,是商业活动中的特殊规则。例如:我们去一家超市买东西,这家超市规定:凡是一次消费金额在2000元以上的顾客,可以获得一张会员卡。凭借这张会员卡,下次消费可以获得积分和享受9折优惠。“商业规则”主旨思想是在表达事与事之间,或者是物与物之间,再或者是事与物之间的关系,而不是事情本身或物质本身的完整性。再例如:一个用户在一个论坛进行新用户注册,该论坛系统规定,新注册的用户必须在4个小时之后才可以发送主题和回复主题。4个小时之内只能浏览主题。这也可以视为一种商业规则。但是,例如:电子邮件地址必须含有“@”字符;用户昵称必须是由中文汉字、英文字母、数字或下划线组成,这些都并不属于商业规则,这些应该被划作“实体规则”。它所描述的是物质本身的完整性。
    在TraceLWord7中,商业规则是由Rules项目来实现的。其具体的商业规则是:
     

    n            每天上午09时之后到11时之前可以留言,下午则是13时之后到17时之前可以留言
     

    n            如果当天留言个数小于 40,则可以继续留言
    这两个条件必须同时满足。更完整的代码,可以在CodePackage/TraceLWord7目录中找到——

    那么,商业规则层和中间业务层有什么区别吗?其实本质上没有太大的区别,只是所描述的功能不一样。一个是功能逻辑实现,另外一个则是商业逻辑实现。另外,中间业务层所描述的功能逻辑通常是不会改变的。但是商业逻辑却会因为季节、消费者心理、资金费用等诸多因素而一变再变。把易变的部分提取出来是很有必要的。
    在发送留言之前,调用“商业规则层”来验证当前行为是否有效?如果无效则会抛出一个异常。
    “三层结构”的缺点
     
    有些网友在读完这篇文章前作之后,对我提出了一些质疑,这提醒我文章至此还没有提及“三层结构”的缺点。“三层结构”这个词眼似乎一直都很热门,究其原因,或许是这种开发模式应用的比较普遍。但是“三层结构”却并不是百试百灵的“万灵药”,它也存在着缺点。下面就来说说它的缺点……

    “三层结构”开发模式的一个非常明显的缺点就是其执行速度不够快。当然这个“执行速度”是相对于非分层的应用程序来说的。从文中所给出的时序图来看,也明显的暴露了这一缺点。TraceLWord1和TraceLWord2没有分层,直接调用的ADO.NET所提供的类来获取数据。但是,TraceLWord6确要经过多次调用才能获取到数据。在子程序模块程序没有返回时,主程序模块只能处于等待状态。所以在执行速度上,留言板的版本越高,排名却越靠后。“三层结构”开发模式,不适用于对执行速度要求过于苛刻的系统,例如:在线订票,在线炒股等等……它比较擅长于商业规则容易变化的系统。
    “三层结构”开发模式,入门难度够高,难于理解和学习。这是对于初学程序设计的人来说的。以这种模式开发出来的软件,代码量通常要稍稍多一些。这往往会令初学者淹没在茫茫的代码之中。望之生畏,对其产生反感,也是可以理解的……
    其实,无论哪一种开发模式或方法,都是有利有弊的。不会存在一种“万用法”可以解决任何问题。所以“三层结构”这个词眼也不会是个例外!是否采用这个模式进行系统开发,要作出比较、权衡之后才可以。切忌滥用——
    结束语
    谈到这里,文章对“三层结构”的原理和用意已经作了完整的阐述。作为这篇文章的作者,在心喜之余也感到写作技术文章并不是件很轻松的事情,特别是第一次写作像这样长达40多页的文章。为了能使读者轻松阅读,每字每句都要斟酌再三,唯恐会引起歧义。在这里要特别感谢一直关注和支持彬月论坛的网友,他们对彬月论坛的喜爱以及对我的支持,是我写作的巨大动力。当然,在这里还要感谢自己的父母,在我辞去原来的工作在家中完成彬月论坛的日子里,他们给了我极大的支持和理解……
      希望这篇文章能将你带到梦想的地方——
      AfritXia,01.18/2005
    作  者:AfritXia
     
    September 22

    Enterprise Library 学习

    做企业应用,越来越发现模式的重要性。企业其实也就那么点事,很多功能都能重用的。因此在自己写了一个小的CRUD框架后,开始寻找各种应用模式。先找到Enterprise Library,发现可以应用的有Configuration Block, Data Access Block, Security Block, Logging Block and xception Block,呵呵,Enterprise Library总共也没几个Application Block。
     
    开始学习EL,无奈竟然连下载都下不来,live login服务又出问题了(下载这个还要登记一些信息),从link中找啊找,直接找到下载地址,Enterprise Library January 2006.exe
     
    另外,中文MSDN上有个EL的系列webcast,可以看看。
     
    关于CRUD和ORM,找到NHibernate,一直跟着MS走,不知道J2EE的世界,才知道人家在某些方面走的很前头了。还找到个.NET下的Grove,不过好像听说更新不行,但看主页上好像也没见作者不积极啊。先看看NHibernate再说。
     
    还看到个Spring.NET,也是从Java世界来的,没太看明白为什么要用这个东西,说是用来IOC什么的。以后在说,先记着名词。
     
    September 15

    Notes - Delivering Business Intelligence with Microsoft SQL Server 2005

    Chapter 1: Equipping the Organization for Effective Decision Making
    想知道怎么走,就必须先知道去哪里。
    1. 决定(Decision)是重要的。决定就是选择,选择该做什么不该做什么。在组织的每一层面,上至CEO,下至普通员工,都是决定的主体。只有在每一层面上的成功决策才是组织前进的保障。
    2. 什么是有效的决定(Effective Decision)。只有能让组织在一个适时的范围上更靠近统一目标的选择才是有效决定。
     1)必须要有一个目标
     2)必须要有一个明确的距离目标远近的测量标准
     3)必须要有机遇测量标准的信息及时提供给决策者
    3. 信息
     1)基础信息:作为决策的基础
     2)反馈信息:但做完决策后,用来衡量决策的有效性
    4. 有效决策的3各总要因素:
     1)有一个目标:需要是明确的,可度量的;而不是笼统的虚的
     2)具体的可测量的标准,去指引我们朝向正确目标前进
     3)及时的基础信息和反馈信息。信息需要及时,有明确的传播途径,和明确的处理方式。
    5. 商业智能(Business Interlligence)
     1)商业智能是及时的向合适的决策者准确的传递有用信息以做出有效决策的过程。其必须准确,能被决策者容易理解,而且需要及时,即有时间对当前行动做出反馈。
     2)SQL SERVER 2005. Intergraion Service, Online Analytical processing, Data Mining
    ************************
    Chapter 2: Making the Most of What You've Got—Using Business Intelligence
    从杂乱中寻找规律。
    1. 知道要找什么,从哪里去找?
     1)设计指向:报表形式,即我们想知道些什么事先已经设计好,而且事后也只能知道这些
     2)数据指向:从数据到数据,即最初并不知道需要什么,随着查看信息的由高到低,需要查看的信息随时变化。需要在线查询。
    2. 不知道要找什么
     1)数据挖掘(Data Mining):采用复杂的数学算法从具体数据中识别数据中的模式、联系和聚合。
    3. 各个层次上的BI
     1)最高层:
    长远目标。
    高度集成的测量数据:KPI(Key Performace Indicator): 高度集成的测量数据,用来快速传播测量数据的状态。通常他们反应的是组织中的最重要特征。
    高延迟性:延迟是指从事件发生到此事件产生的影响集成到BI的时间
     2)中间层
    短期目标。
    概括性数据,但可以查看具体数据。
    可接受短时延迟。
    3)最底层
    当天或当周目标。
    具体数据。
    最短延迟。
    4. 例子:Maximun Miniatures 公司
    1)现有状况:快速发展,竞争激烈,现有BI系统不满足要求
    2)现有系统:订单处理(大客户,SQL),在线销售(SQL),POS(自身零售,XML),生产自动化(Text),财务(Foxpro),工期控制(Access)
    ****************************
    Chapter 3: Searching for the Source—The Source of Business Intelligence
    提早做准备。
    1. 信息来自已有的业务数据
     1)Transaction Data业务数据:用来跟踪公司互动、业务的存储信息
     2)Online Tranction Processing:在线业务处理:但业务发生时,记录业务信息,以支持公司每天的操作。
    2. 直接使用业务数据有困难
     1)设计不同。OLTP设计来快速操作日常事务,记录最具体的数据。而BI使用聚集数据,设计来使查询快速
     2)OLTP是用来日常操作的,BI如果也采用OLTP的数据,会使OLTP本身性能下降。
     3)OLTP只是处理近阶段数据,远期数据会存档。而BI需要分析远期数据。
     4)一个公司会有很多不同的OLTP,而BI需要集中这些数据来分析
    3. BI使用Data Mart来存储数据
     1)Data Mart:存储在电子介质上的历史数据,不参与公司日常操作。相反,是用来做BI分析的。(有点类似Date Warehouse,但相对小一点)
     2)Data Mart是非实时数据。从OLTP定时转存数据到Data Mart的行为叫做Data Load。
     3)Data Cleansing: 数据清理,从业务数据中移除不合理数据的行为
     4)ETL(Extract, Transform, Load):从一个或多个OLTP系统中取出数据,转换并清理业务数据以使符合数据中心的要求,并插入到数据中心
    4. 数据中心的结构
     1)Measure:代表组织性能的某一类指标。这类信息通常用来支持或者评价决策。也叫做Fact。
     2)Dimension:用来展开聚集的信息的一个分类,用来查看聚集信息的组成部分。可以有任意多的Dimemsion。
     3)Star Schema 星型结构:用来存储Measure和Dimension的关系数据库结构。Measure存储在Fact表中,Dimension存储在Dimemsion表中。
    星型中心是Fact表,有一栏Measure和每个Dimension一栏。Dimension表有Dimension的ID和此ID的描述。Fact表的Row数是各个Dimension表Row数的总乘。
     4)Attribute属性:Dimension表中除了ID和描述外的其他属性,用来描述此Dimension的其他性质
     5)Hierarchies:由两级或多级的关联Dimension组成的结构。高级Dimension包含几个低级Dimension,例如Year包含Quarter。
     6)Star Schema with Hierarchies: Dimension表的ID由最低级的Dimension构成
     7)Snowflake Schema:Hierarchies由多个关联表组成。Fact表关联最低级Dimension表,最低级Dimension表再关联高级Diemension表。
     8)无论Star Schema还是Snowflake,都没有存储关于高级Dimension的信息,需要计算聚合来获得。因此,我们需要预先计算并存储。SSAS(SQL Server Analysis Services)就是做这个事情的。
    *******************************
    Chapter 4: One-Stop Shopping—The Unified Dimensional Model
    雪花是自然界最易碎的东西,但你看看他们如果粘在一起后变成怎样了。
    1. Online Analytical Processing(OLAP): 使用户能快速简单的获取数据用来分析。OLAP用Measure,Dimension,Hierarchies和Cube来展现数据。
    2. Cube:保存所有Dimension的任意排列的Measure值的结构。这些是最具体的,处于页级别的数据。Cube也可能保存一些聚集数据。
    3. Preprocessed aggregate: 事先计算聚集数据并存储。
    4. OLAP特性
     1)多维数据库(MultiDimensional Database):以Measure,Dimension,Hierarchies和Cube为结构,而不是Table,Row,columns和Relations的结构。
     2)Preprocessed Aggregate:可存储在多维数据库
     3)容易理解:OLTP以各种复杂的外键联系。OLAP反映实际的组织机构。
     4)OLAP就是为了BI存在,可以存储其他描述性数据
    5. OLAP的架构
     1)Relational OLAP:原始数据在关系数据库,预计算数据也在关系数据库。没延时,但取数据慢
     2)MultiDimensional OLAP:原始数据和预计算数据都在多维数据库。延迟严重,但取数据快。而且从OLTP数据拷贝到OLAP需要时间。
     3)Hybrid OLAP:原始数据在关系数据库,但预计算数据在多维数据库。没延迟,取非最低级数据快速。
    6. OLAP的缺点
     1)管理复杂
     2)需要数据中心
     3)有延迟
     4)数据只读,修改不能引用到OLTP
    7. 统一维度模型(Unified Dimensional Model,UDM)
     1)数据源:可以直接是OLTP数据,不需要大量的ETL操作,这样就使易于管理
     2)数据视图:可以建立符合需要的视图
     3)事先Cache(Proactive):不像平常的Cache要有用户需求才Cache,是预先就Cache等着用户来取
     4)定义于XML,可以看着源代码。
    ******************************
    Chapter 5: First Steps—Beginning the Development of Business Intelligence
    为其他人建造好的工具能使人极大的满足。
    1. Business Intelligence Development Studio
    2. SQL Server Management Studio
     1) MDX: Multidimensional Expression
     2) DMX: Data Mining Expression
     3) XMLA: XML for Analysis Services
    September 13

    Notes: Teach.Yourself.Microsoft.SQL.Server.2005.Express.in.24.Hours

    1. Normalization and Normal Forms
     1) First Normal Form: all columns in a table must be atomic
     2) Second Normal Form: all nonkey columns must be fully dependent on the primary key
     3) Third Normal Form: all nonkey columns must be mutually independent
    2. Denormalization: to enhance performance
    3. Integrity Rules
     1) referential integrity:
     a) Child rows cannot be added for parent rows that do not exist
     b) A primary key value cannot be modified if the value is used as a foreign key in a child table
     c) A parent row cannot be deleted if child rows are found with that foreign key value
     2) Entity integrity: the primary key value cannot be null
    4. primary key's criteria
     1) Short
     2) Stable
     3) Simple
    5. Constraints
     1) Primary Key Constraints
     2) Foreign Key Constraints
     3) Default Constraints
     4) Not Null Constraints
     5) Check Constraints
     6) Rules: not recommended by MS
     7) Unique Constraints
    6. Computed Columns: (Formula) property under the Computed Column Specification key of the column
    7. User-Defined Data Types
    8. Indexes:
     1) it's generally best to include too many indexes rather than too few
     2) Create indexes only for fields or combinations of fields by which the user will search, sort, or group. Do not create indexes for fields that contain highly repetitive data. A general rule is to provide indexes for all fields regularly used in searching and sorting, and as criteria for queries
    9. relationships types:
     1) one-to-many: foreign key
     2) one-to-one:
     a) The number of fields required for a table exceeds the number of fields allowed in a SQL Server table
     b) Certain fields that are included in a table need to be much more secure than other fields included in the same table
     c)Several fields in a table are required for only a subset of records in the table
     3) many-to-many: develop this type of relationship by adding a table called a junction table
    10. The right type of relationship between two tables ensures
     1) Data integrity
     2) Optimal performance
     3) Ease of use in designing system objects
    11. Database Diagrams
    12. Delete and Update Specifications in foreign key relation
    13. T-SQL: Transact-SQL,
    14: SELECT: FROM, WHERE, ORDER BY, DISTINCT, TOP (PERCENT); FORM XML RAW(AUTO, EXPLICIT)
    15: LIKE:
     1) %:wildcard for zero or more characters
     2) _:wildcard for a single character
    16: TRUNCATE: Delete all rows from a table while remaining the structure
    17: You can insert data into a view just as you can insert data into a table. The INSERT statement affects all tables underlying the view
    18: IsNumeric(), Round()
    19: LEFT, RIGHT, LEN, REPLACE, STUFF, SUBSTRING, LOWER, UPPER, LTRIM, and RTRIM
    20: GETDATE, MONTH, DAY, YEAR, DATEPART, DATENAME, DATEADD, and DATEDIFF
    21: ISNULL, NULLIF, COALESCE
    22: View's advantage
     1) Join data so that users can work with it easily
     2) Aggregate data so that users can work with it easily
     3) Customize data to users' needs
     4) Hide underlying column names from users
     5) Limit the columns and rows with which a user works
     6) Easily secure data
    23: Modifying Data in a View
     1) If a view joins multiple tables, you can insert and update data in only one table in the view.
     2) The results of a view that aggregates data are not updateable
     3) The results of a view that contains a UNION clause are not updateable
     4) The results of a view that contains a DISTINCT statement are not updateable
     5) Text and image columns cannot be updated via a view
     6) Data that you modify is not checked against the view criteria
    24: Indexed Views: When you create a unique clustered index, SQL server stores the data that exists at the time you create the view. SQL Server then reflects all modifications to table data within the stored view.
    25: Requirements for Indexed Views
     1) The view cannot reference other views
     2) The tables underlying the view must be in the same database as the view and have the same owner as the view
     3) You must set the ANSI_NULLS option to ON when you create the tables referenced by the view
     4) You must set the ANSI_NULLS and QUOTED_IDENTIFIER options to ON before creating the view
     5) You must create the view, and any functions underlying the view, with the SCHEMABINDING option. This means that you cannot modify or drop tables and other objects underlying the view without dropping the view first
     6) You cannot use * to designate all columns
     7) You cannot use the keyword UNION, DISTINCT, TOP, ORDER BY, COUNT(*), AVG, MAX, MIN, STDEV, STDEVP, VAR, or VARP
     8) You cannot repeat a column in a view
     9) You cannot include derived tables or subqueries
    26: Optimizing the Views
     1) Create indexes for any fields used in the criteria of views
     2) Create indexes for any fields that are included in the sorting or grouping of a view
     3) Create indexes for all columns used in joins
     4) Do not create indexes for columns that have very few unique values
    27: Stored procedures' benifit
     1) help you to separate the client application from the database's structure
     2) help you to simplify client coding
     3) process at the server (reduces required bandwidth).
     4) enable you to create reusable code
     5) enable you to perform error-handling at the server
     6) facilitate the security of data
     7) execute more quickly
     8) improve the application's stability
     9) reduce network locking
     10) When you build a stored procedure, a query plan is created. This query plan contains the most efficient method of executing the stored procedure given available indexes and so on.
    28: DECLARE @FirstName VarChar(35); SELECT @FirstName = 'Alexis'
    29: IF...ELSE; BEGIN...END; GOTO, RETURN; CASE; WHILE
    30: SET NOCOUNT; @@RowCount; @@tranCount; @@Identity; @@Error
    31: Parameters: Input, default value, output
    32: CREATE TABLE #TempEmployees(create in TempDB database)
    33: User-Defined Functions:Scalar Functions, Inline Table-Valued Functions, Multi-Statement Table-Valued Functions
    34: Trigger: CREATE TRIGGER TriggerName On TableName FOR [INSERT], [UPDATE], [DELETE] AS
    35: Downsides of Triggers: get buried in your database and are difficult to debug and troubleshoot. often lock data for relatively long periods of time, increasing the chance of concurrency problems.
    36: MS Data-access technology history: DAO->RDO->OleDB->ADO->ADO.NET
    37: the process of building and executing a .NET stored procedure:
     1) write the stored procedure in the .NET language
     2) declare the stored procedure within SQL Server
     3) execute it
    38: Authentication: SQL Server and Windows; Windows only
    39: Roles: Fixed server roles, Fixed database roles, User-defined database roles, Application roles
    40: Permissions: ALTER, CONTROL, DELETE, INSERT, SELECT, TAKE OWNERSHIP, UPDATE, and VIEW DEFINITION
     

     
    September 03

    NTS(JTS) Techinal Notes

    1.         most common object is Geometry objects. We can generate one from Well-Known Text(WKT) string.

    Geometry g1 = new WKTReader().read("LINESTRING (0 0, 10 10, 20 20)");

    Or we can use GeometryFactory

    Coordinate[] coordinates = new Coordinate[] {

    new Coordinate(0, 0), new Coordinate(10, 10), new Coordinate(20, 20) };

    Geometry g1 = new GeometryFactory().createLineString(coordinates);

    Geometry is the most basic object and we can do many things on it. Such as find the intersection of two Geometries

    Geometry g3 = g1.intersection(g2);

    2.         Spatial Relationships. JTS follows the Dimensionally-Extended 9 Intersection Matrix model(DE-9IM) specified by the OGC. Relationships include equals, disjoint, intersects, touches(one boundary point in common, but no interior points), crosses, within, contains, overlaps

    3.         overlay operations, include Intersection, Union, Difference, SymDifference, Buffer, convexHull

    4.         Buffer: the area containing all points within a given distance of a Geometry. In mathematical terms, this is called computing the Minkowski sum of the Geometry with a disc of radius equal to the buffer distance. Finding positive and negative buffers is sometimes referred to as the operations of erosion and dilation.

    Geometry g = . . .

    Geometry buffer = g.buffer(100.0);

    Buffer polygons can have different line end cap styles, include CAP_ROUND, CAP_BUTT, CAP_SQUARE

    Geometry g = . . .

    BufferOp bufOp = new BufferOp(g);

    bufOp.setEndCapStyle(BufferOp.CAP_BUTT);

    Geometry buffer = bufOp.getResultGeometry(distance);

    5.         Polygonization: forming polygons from linework which encloses areas.

    Collection lines = new ArrayList();

    lines.add(read("LINESTRING (0 0 , 10 10)")); // isolated edge

    lines.add(read("LINESTRING (185 221, 100 100)")); //dangling edge

    lines.add(read("LINESTRING (185 221, 88 275, 180 316)"));

    lines.add(read("LINESTRING (185 221, 292 281, 180 316)"));

    lines.add(read("LINESTRING (189 98, 83 187, 185 221)"));

    lines.add(read("LINESTRING (189 98, 325 168, 185 221)"));

    polygonizer.add(lines);

    Collection polys = polygonizer.getPolygons();

    Collection dangles = polygonizer.getDangles();

    Collection cuts = polygonizer.getCutEdges();

    6.         LineMerger: sew these small LineStrings together,

    LineMerger lineMerger = new LineMerger();

    Collection lineStrings = . . .

    lineMerger.add(lineStrings);

    Collection mergedLineStrings = lineMerger.getMergedLineStrings();

    7.         Using Custom Coordinate Sequences: implementing the CoordinateSequence and CoordinateSequenceFactory interfaces.

    8.         node process: splits LineStrings that cross into smaller LineStrings that meet at a point

    Collection lineStrings = . . .

    Geometry nodedLineStrings = (LineString) lineStrings.get(0);

    for (int i = 1; i < lineStrings.size(); i++) {

    nodedLineStrings = nodedLineStrings.union((LineString)lineStrings.get(i));

    }

    9.         Terminology: Coordinate, Exact Computation, Node, Noding, Non-coordinate, Numerical Stability, Point, Proper intersection, Robust Computation, SFS, Unit of Resolution, Vertex

    10.     Precision Model: Fixed, Floating, Exact. Constructed Points, Dimensional Collapse, Robustness, Numerical Stability, Computational Performance(Monotone Chains)

    11.     Spatial Model:

    a)        Definition: Geometry, Empty Geometry, GeometryCollection, Curve, MultiCurve, LineString, LinearRing, Polygon, MultiPolygon

    b)        Feature Classes: Geometry, GeometryCollection, Point, MultiPoint, Curve, Line, LinearRing, MultiCurve, MultiLineString, Surface, Polygon, MultiSurface, MultiPolygon,

    c)         Support Classes: Coordinate, CoordinateSequence, Envelope, IntersectionMatrix, GeometryFactory, CoordinateFilter, GeometryFilter

    12.     Basic Geometric Algorithms and Structures: Point-Line Orientation Test, Line Intersection Test, Line Intersection Computation, Point-in-Ring Test, Ring Orientation Test

    13.     Topological Computation: Labels, Intersection Matrix, Relate Algorithm, Overlay Algorithm

    14.     Binary Predicates: Equals, Disjoint, Intersects, Cross, Within, Contains, Overlaps

    15.     Other Methods: Boundary, IsClosed, IsSimple, IsValid

    16.     Geometry Object model: P:zero-dimension geometry; L: one-dimension; A: two-dimension

    Geometry: Basic: Dimension(), GeometryType(), SRID(), Envelope(), AsText(), AsBinary(), IsEmpty(), IsSimple(), Boundary(); Relations: Equals(), Disjoint(), Intersects(), Touches(), Crosses(), Within(), Contains(), Overlaps, Relate(); Spatial Analysis: Distance(), Buffer(), ConvexHull(), Intersection(), Union(), Difference(), SymDifference()

    GeometryCollection:

    Curve: A Curve is a one-dimensional geometric object usually stored as a sequence of points, with the subtype of Curve specifying the form of the interpolation between points. This specification defines only one subclass of Curve, LineString, which uses linear interpolation between points.

    LineString: a Curve with linear interpolation between points. Each consecutive pair of points defines a line segment.

    Line: a LineString with exactly 2 points.

    LinearRing: a LineString that is both closed and simple.

    MultiCurve: a one-dimensional GeometryCollection whose elements are Curves

    MultiLineString: a MultiCurve whose elements are LineStrings.

    Surface: a two-dimensional geometric object.

    Polygon: a planar Surface, defined by 1 exterior boundary and 0 or more interior boundaries. Each interior boundary defines a hole in the Polygon

    MultiSurface: a two-dimensional geometric collection whose elements are surfaces.

    MultiPolygon: a MultiSurface whose elements are Polygons.

     

    Samples:

    1.        Geometry g1 = new WKTReader().Read("LINESTRING (0 0, 10 10, 20 20)");

    Coordinate[] coordinates = new Coordinate[] { new Coordinate(0, 0), new Coordinate(10, 10), new Coordinate(20, 20) };

    // use the default factory, which gives full double-precision

    Geometry g2 = new GeometryFactory().CreateLineString(coordinates);

     

    2.        GeometryFactory fact = new GeometryFactory(new PrecisionModel (PrecisionModels.FloatingSingle));

     

    3.          Linear Reference System: Linear referencing is a natural and convenient means to associate attributes or events to locations or portions of a linear feature. The major advantage of linear referencing is its capability of locating attributes and events along a linear feature with only one parameter (usually known as measure) instead of two (such as latitude/longitude or x/y in Cartesian space). LengthIndexedLine support it.

     

    4.          use EnhancedPrecisionOp to reduce the likelihood of robustness problems.

     

    5.          GMLWriter(write)->XMLReader(load)->XMLDocument(innerXml)->XMLString->GMLReader(read)

    ShapefileReader(readAll)->GeometryCollection->ShapefileWriter(write)

    DbaseFileReader(getHeader, getEnumerator)

    ShapefileDataReader, ShapefileDataWriter

    GDBReader, GDBWriter

    Stream->WKBReader(read)->Geometry->WKBWriter(write)->byte[]

    String->WKTReader(read)->Geometry->WKTWriter(write)->string

    Serialize: BinaryFormatter.Serialize(Stream), BinaryFormatter.Deserialize(Stream)

    6.          identifying the location of self-intersections(…); using a zero-width buffer to compute unions of geometrys(…)

    7.          Distance and closest points between geometries: DistanceOp.Distance, DistanceOp.ClosestPoints

    8.          LineMerger class to sew together a set of fully noded linestrings. LineMerger.MergedLineStrings

    9.          Polygonizer class to polygonize a set of fully noded linestrings. Polygonizer.Polygons

    10.       coordinatesystem transform(……..)

    11.       line sequencer

    12.       CoordinateArrays, Normalize, Reverse

    September 01

    Windows Workflow Foundation Hnads-On Lab Notes - Lab 7, 8, 9, 10

    Lab 7:
    Exercise 1:
    1) the ConditionedActivityGroup(CAG) Activity
     a) The ConditionedActivityGroup activity allows you to provide condition-driven execution behavior to a
    collection of activities.
    Exercise 2:
    1) the Replicator Activity
     a) launch multiple copies of other activities
     b) Replicator events: Initialized, ChildInitialized, Childcompleted, Completed
     c) Replicator.InitialChildData(IList, such as ArrayList); Replicator.ExecutionType
    Exercise 3:
    1) the Policy Activity
     a) encapsulates a forward chaining rules engine
     b) PoliceActivity.RuleSetReference
     c) Ruleset is a set of rules
     d) rule: IF (C1) THEN (T1) ELSE (E1)

    Lab 8:
    Exercise 1:
    1) Atomic Transactions
     a) ACID (Atomicity, Consistency, Isolation and Durability)
     b) encapsulate activities in TransactionScope
    2) Throw activity
     
    Exercise 2:
    1) Compensation activity
     a)  the process of logically undoing the completed transactions (either Atomic or Long Running) in case of
    any business exceptions.
     b) add a compensateActivity in Exception view and set TargetActivityName property
     c) in compensatableTransactionScope's compensate view, do what you want
    Lab 9:
    Exercise 1:
    1) Make dynamic changes to the workflow while it is running(within the workflow)
     a) WorkflowChanges changes = new WorkflowChanges(this)
     b) changes.TransientWorkflow
     c) this.ApplyWorkflowChanges(changes)
    Exercise 2:
    1) WorkflowChange from the Host on an Executing Workflow
     a) workflowInstance.Suspend();
     b) Activity workflowInstanceDefinition = workflowInstance.GetWorkflowDefinition();
        WorkflowChanges workflowChangesToMake = new WorkflowChanges(workflowInstanceDefinition);
     c) changes.TransientWorkflow
     d) workflowInstance.ApplyWorkflowChanges(workflowChangesToMake)
     e) workflowInstance.Resume()
    Exercise 3:
    1)  Changing Conditions in an IfElse Activity of a Running Workflow
     a) RuleDefinitions ruleDefinitions = (RuleDefinitions)transient.GetValue
    (RuleDefinitions.RuleDefinitionsProperty);
     b) RuleConditionCollection conditions = ruleDefinitions.Conditions;
     c) RuleExpressionCondition condition1 = (RuleExpressionCondition)conditions["Condition1"];   
     d) (condition1.Expression as CodeBinaryOperatorExpression).Right = new CodePrimitiveExpression(newAmount);
    Lab 10: (don't know much)
    Exercise 1:
    1) Displaying a Workflow in a Windows Forms Application
     a) IMenuCommandService
     b) IToolboxService
     c) ITypeProvider
    Exercise 2:
    1) Interacting Programmatically with the Workflow Designer in a Windows Forms Application
     a) workflowView.Zoom
    Exercise 3:
    1) Adding Activities to the Workflow Designer in a Windows Forms Application
     a) ToolBox
     b) ISelectionService
    Exercise 4:
    1) Opening, Saving, Compiling and Running Workflows in the Workflow Designer
     a) Open: WorkflowMarkupSerializer
     b) Save: WorkflowMarkupSerializer
     c) Compile: WorkflowCompiler, WorkflowCompilerParameters
     d) Run: workflowRuntime.CreateWorkflow(AppDomain.CurrentDomain.CreateInstanceAndUnwrap(inMemoryAssembly.FullName, typeName).GetType())

    Windows Workflow Foundation Hnads-On Lab Notes - Lab 4, 5,6

    Lab 4:
    Exercise 1:
    1) Create the State Machine Workflow
     a) offer a flexible workflow creation style where the process is modeled as a state machine. in a State
    Machine Workflow the activities execute based on external events. The state machine responds to an external
    event, does the required work and then moves to the next state.
     b) ExternalDataExchangeService how to work:
    Workflow W1, Local service L1, ExternalDataExchangeService EX1, Host application H1, Event in L1 E1;
    H1 create W1, W1 add EX1 through W1.AddService(), EX1 add L1 throuth EX1.AddService(). in W1, there is an
    activity HandleExternalEvent which handle the event E1(some like delegate and event handle in normal
    application);
    when H1 want to notify W1, H1 raise E1 through L1(H1 know L1 instance because host create localservice).
    then W1 know the event is raised and HandleExternalEvent is called.
    L1 in a dll which reside in W1, H1 know the L1 address and call the same L1.
    2) execute method in another thread context
     private delegate void DelegateAAA(Parameters p1);
     private void MethodAAA(Parameters p2)
     {
      if InvokeRequired()
      {
       DelegateAAA delegateaaa = new DelegateAAA(MethodAAA);
       Invoke(delegateaaa, p2)
      }
      else
      {
       // do something
      }
     }
    Exercise 2:
    1)  Use the StateMachineTracking Service (don't know now)

    Lab 5:
    Exercise 1:
    1) Communicating with a Local Service
     a) DoSomething method / SomethingDone event pattern
     b) The ***Args class defines the message passed between the service and the workflow and stores the
    instance id of the workflow.  The I***Service is an interface that defines how the service and workflow
    communicate.  The workflow calls the service's method DoSomething; the service raises the events into the
    workflow to indicate SomethingDone.
    Exercise 2:
    1) Correlating Communication
     a) Correlation could also be viewed as a channel between the host application and the workflow that is
    used to exchange messages with a unique ID.
     b) decorating the interface with the correlation key information and adding a correlation reference object
     c) attribute: CorrelationInitializer, CorrelationParameter, CorrelationAlias
    Exercise 3:
    1) Roles and Security in Workflow
     a) the execution of activities based on the membership of a user in a given role
     b) using System.Web.Security.Roles which store user and role information in sql table
     c) in workflow, create a variable of type WorkflowRoleCollection
     d) in HandleExternalEventActivity's Roles property, set it to variable of type WorkflowRoleCollection. In
    this way, this activity can only be execute by the roles in WorkflowRoleCollection
     e) if auth fails, there is an exception of type System.Workflow.Activities.WorkflowAuthorizationException.
    Exercise 4:
    1) Deferring a Long Running Operation to the Host. the long running process was carried out by the host
    application while the workflow was idled. We could, of course, add a PersistenceService to our Host and
    allow the runtime to persist out the workflow while the long running process was taking place

    Lab 6:
    Exercise 1:
    1) Consuming a web service from a workflow
     a) using InvokeWebServiceActibity
    Exercise 2:
    1) Exposing Web Services:
     a) create a custom activity
     b) publish it as web service. a new project of webservice is created automatically.
     c) WebServiceInputActivity and WebServiceOutputActivity
     d) webservice has a interface which don't need implemented and only exposed to outside.
    WebServiceInputActivity is it's incoming parameters and WebServiceOutputActivity is it's outcoming
    parameters or return value.

    Windows Workflow Foundation Hnads-On Lab Notes - Lab 2, 3

    Lab 2:
    Exercise 1:
    1) Create a Composite Activity
     a) inherit from SequenceActivity
    Exercise 2:
    1) create a Basic Activity
     a) inherit from System.Workflow.ComponentModel.Activity
     b) use DependencyProperty property to describe the property of custom activity's property
     c) override Execute method.
    Exercise 3:
    1) custom ActivityValidator
     a) custom activity has following aspects: Designer, Code generator, Validator, Toolbox item, Executor,
    Serializer, Deployer
     a) inherit from ActivityValidator and override ValidateProperties() method
     b) used in custom activity's attribute with ActivityValidator()
    Exercise 4:
    1) custom designer
     a) inherit from ActivityDesigner and override OnPaint(), Initalize(), OnLayoutSize() method

    Lab 3:
    Exercise 1:
    1) add service to workflow runtime
     a) using WorkflowRuntime.AddService(): Tracking service(SqlTrackingService)
     b) handle workflow runtime events: WorkflowCompleted, WorkflowTerminated, Started, Stopped,
    WorkflowResumed, WorkflowSuspended
     c) using WorkflowRuntime.GetAllServices() to get all the service in workflow runtime
     d) config runtime services in app.config
    Exercise 2:
    1) add SqlTrackingService to catch events and store that event information in a SQL Server database.
     a) events include Workflow Events, Activity Level Events, User Tracked events
     b) query sql tracking events with SqlTrackingQuery, SqlTrackingWorkflowInstance, WorkflowTrackingRecord
     c) create tracking table in sql database: create datatable, execute Tracking_Schema.sql, execute
    Tracking_Logic.sql.
    2) Add SQLPersistenceService to unloading an idle instance and reload an active instance
     a) add SharedConnectionWorkflowCommitWorkBatchService service to use an identical database connection
    string with SQLPersistenceService service : avoid using the DTC
     b) ThreadPool.QueueUserWorkItem to add method with call workflowInstance.unload()
     c) create persistence table in sql database: create datatable, execute SqlPersistenceService_Schema.sql,
    execute SqlPersistenceService_Logic.sql.
    3) Add DefaultWorkflowSchedulerService to decides how and when to load and persist running workflows
     a) DefaultWorkflowSchedulerService(int maxSimultaneousWorkflows) to allow maxSimultaneousWorkflows
    workflow instance execute parallelly
    Exercise 3: Building a Custom TrackingService:
    1) build a TrackingChannel class
     a) receives the various tracking records sent by the runtime.
     b) override TrackingChannel.Send() method
    2) build a TrackingService class:
     a) provides the runtime with tracking profiles based on specific parameters and conditions. It is also
    responsible for providing a TrackingChannel that receives the data sent by the runtime.
     b) override GetProfile(Guid workflowInstanceId), GetProfile(Type workflowType, Version profileVersionId),
    GetTrackingChannel(TrackingParameters parameters), TryGetProfile(Type workflowType, out TrackingProfile
    profile), TryReloadProfile(Type workflowType, Guid workflowInstanceId, out TrackingProfile profile)
     c) create a TrackingProfile: activity status events, workflow status events, user track points
    August 31

    Windows Workflow Foundation Hnads-On Lab Notes - Lab 1

    Exercise 1:
    1) create a Sequential workflow from template
    2) basic code activity which contains code only
    3) create workflow runtime, workflow instance use workflow runtime events
     a) WorkflowRuntime workflowRuntime = new WorkflowRuntime();
     b*) workflowRuntime.StartRuntime()
     c) WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(HelloWorldWorkflow.Workflow1));  
     d) instance.Start();
     e*) workflowRuntime.StopRuntime();
    Exercise 2:
    1) Receiving Data into the Workflow using Parameters:
     a) Create property in workflow
     b) create dictionary with key=property name
     c) CreateWorkflow(type, dictionary parameters)
    Exercise 3:
    1) local services communications: A local service implementation is a class that implements an interface
    that is decorated with ExternalDataExchangeAttribute. Local services added in this way are used by
    CallExternalMethodActivity and HandleExternalEventActivity.
     a) use interface with ExternalDataExchange attribute to supply external data exchange service.
     b) add ExternalDataExchangeService to workflow runtime
     c) add local service to ExternalDataExchangeService.
    2) Dependency properties:
     a) Binds an activity's property to a property, field, indexer, method, event, or another activity's
    property
    3) PolicyActivity: if/else in rules
     a) RuleSet: a set of rules
    4) custom activity:
     a) ActivityDesigner
     b) ActivityDesignerTheme
    Exercise 4:
    1) ListenActivity:
     a) contain at least 2 EventDrivenActivity
     b) The first child of an EventDrivenActivity activity must be an activity that inherits from
    IEventActivity, such as HandleExternalEventActivity and DelayActivity
    2) If/Else Activity
     a) Code condition
     b) Declarative Rule Condition
    August 26

    关于WWF的几篇文章

    1. An introduction to Windows Workflow Foundation http://www.asptoday.com/content.aspx?id=a0362
    2. Using Windows Workflow Foundation with ASP.NET 2.0 http://www.asptoday.com/Content.aspx?id=2427
    3. Windows Workflow Foundation: Everything About Re-Hosting the Workflow Designer http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnlong/html/WFDsgnRehst.asp
    6.  Transcript: Windows Workflow Foundation Runtime Hosting and Services http://msdn.microsoft.com/msdntv/transcripts/20060323WWFIHTranscript.aspx
    7. WinFX 工作流:使用 Windows Workflow Foundation 的声明性模型简化开 http://www.microsoft.com/china/MSDN/library/Windev/WindowsVista/WindowsWorkflowFoundation.mspx?mfr=true
    9. Windows Workflow Foundation:创建自定义复合活动 http://www.microsoft.com/china/MSDN/library/Windev/WindowsVista/parallelif.mspx?mfr=true
    12. Microsoft Windows Workflow Foundation 入门:开发人员演练 http://www.microsoft.com/china/MSDN/library/Windev/WindowsVista/WWFGetStart.mspx?mfr=true
     
    Website:

    Windows Workflow Foundation 构造块

    From Microsoft Windows Workflow Foundation 入门:开发人员演练http://www.microsoft.com/china/MSDN/library/Windev/WindowsVista/WWFGetStart.mspx?mfr=true

    表 2. Windows Workflow Foundation 构造块

    活动 说明

    Code

    使您能够向工作流中添加 Microsoft Visual Basic .NET 或 C# 代码以执行自定义操作。但是,这些代码不应该用对 Web 服务等外部资源的依赖性来阻塞工作流。

    Compensate

    使您能够在发生错误时调用代码来撤消或者补偿已经由工作流执行的操作。通常,对于现在已被取消的操作,您可能希望向先前已经获得成功通知的用户发送电子邮件。

    ConditionedActivityGroup (CAG)

    使您的工作流能够基于特定于每个活动的准则有条件地执行一组子活动,直到针对 CAG 整体满足完成条件。子活动相互独立并可能并行执行。

    Delay

    使您能够控制工作流的定时以及将延迟内置到工作流。您可以在 Delay 活动上提供超时,以便工作流在恢复执行之前暂停。

    EventDriven

    代表一系列其执行由事件触发的活动。第一个子活动必须能够等待外部事件。可行的首要子活动是 EventSink 和 Delay。在这种情况下,Delay 用作超时。

    EventSink

    在向 WorkflowRuntime 注册的数据交换服务引发指定事件时,使工作流能够从该服务接收数据。

    ExceptionHandler

    使您能够处理指定类型的异常。ExceptionHandler 活动是其他活动的包装,在指定的异常发生时,这些活动实际执行所需的任何工作。可根据情况指定一个用于存储异常的本地变量,并且使其可以在代码隐藏中使用。

    IfElse

    使您的工作流能够有条件地执行多个可供选择的分支之一。可在每个分支上放置一个条件,而条件为真的第一个分支将执行。无需在最后一个分支上放置条件,因为它被视为“else”分支。

    InvokeMethod

    使您的工作流能够调用接口上的方法,以便将消息从工作流发送到向 WorkflowRuntime 注册的数据交换服务。

    InvokeWebService

    使您的工作流能够调用 Web 服务方法。您需要指定要使用的代理类(使用 WSDL),以及您想要调用的方法的名称。同步和异步调用都受到支持。

    InvokeWorkflow

    使您的工作流能够调用或启动另一个工作流(可达到任意深度)。例如,被调用的工作流可以调用第三个工作流,该工作流又可以调用第四个工作流,等等。递归调用不受支持。受支持的调用模型是发后不理。

    Listen

    使工作流能够等待(可能存在的)多个事件之一,或者在指定的超时间隔之后停止等待,并且基于结果分支。可向每个分支中添加一个或多个由事件驱动的活动。只有第一个满足条件的分支被执行;其他分支都不会运行。

    Parallel

    使您的工作流能够相互独立地执行两个或更多个操作。该活动在继续执行之前会等待这些操作终止。

    Policy

    使您能够表示或执行规则集合。该活动不在工具箱中;要访问它的功能,必须创建自定义活动并使用派生。

    Replicator

    使您的工作流能够创建给定活动的任意多个实例,并且顺序或同时执行它们。

    SelectData

    使您的工作流能够通过在外部数据源对象上定义的方法查询外部数据。当触发 SelectData 活动时,关联的方法将在宿主线程内部执行。该方法返回的值被传递给工作流。

    Sequence

    使您能够协调一组子活动的连续执行。该序列在最后一个子活动完成之后完成。

    SetState

    使您的状态机工作流能够指定向新状态的转换。

    State

    表示状态机工作流中的状态。

    StateInitialization

    在 State 活动中,用作在状态转换时执行的子活动的容器。

    Suspend

    挂起工作流的操作,以便能够在发生某个错误条件时进行干预。当工作流实例挂起时,将记录错误。可指定一个消息字符串来帮助管理员诊断发生了什么事情。与当前实例关联的所有状态信息都被保存,并且这些信息会在管理员继续执行时恢复。

    Terminate

    使您能够在发生任何异常情况时立即结束工作流的操作。如果是在 Parallel 活动内部调用,则所有分支都被突然终止,而无论它们的当前状态如何。当工作流终止时,会记录错误,并提供一个消息以帮助管理员弄清楚发生了什么事情。

    Throw

    使您能够引发指定类型的异常。使用该活动等效于在用户代码中引发异常的代码处理程序。该活动是引发 .NET 异常的声明性方式。

    TransactionalContext

    事务上下文 是用于对活动进行分组的块。该活动主要用于事务性执行、补偿和异常处理,可以根据情况进行同步。通过同步事务性上下文,可确保对活动中共享数据的任何访问都将正确地序列化。

    UpdateData

    使您的工作流能够通过在外部数据源对象上定义的方法更新数据存储区。当 UpdateData 活动被触发时,关联的方法将在宿主线程内部执行。

    WaitForData

    使您的工作流能够从外部数据源对象接收信息。当传入的数据修改绑定数据源的状态时,该活动被触发。传入的数据是通过绑定数据源服务接收的。

    WaitForQuery

    使外部应用程序能够在您的工作流中查询数据。该活动将在从宿主收到查询之前一直等待。来自外部应用程序的查询使用绑定数据源服务上的方法提交给工作流。

    WebServiceReceive

    使作为 Web 服务本身公开的工作流能够接收 Web 服务请求。

    WebServiceResponse

    使作为 Web 服务本身公开的工作流能够响应 Web 服务请求。

    While

    使您的工作流能够在一个条件被满足时执行一个或多个活动。在每次迭代之前,都评估该条件。如果为真,则所有子活动都会执行;否则,该活动完成。可指定声明性条件或代码条件。

    July 24

    Presenting Windows Workflow Foundation Note 1

    1. Introduction
     1) The Windows Workflow Foundation supports development of both sequential and state-based workflow
    involving both human and system interaction.
     2) Key Scenarios that can be developed with Windows Workflow Foundation include: Document management,
    Line of business application, Website page flow, IT management, Decision making logic, Consumer.
     3) The Wonder of Flowcharts. There is some issue in pure coding approach:
       a) Code written by one developer is often inherently hard to understand by another
       b) Once compiled and executed, code is inherently opaque
       c) Code is often grouped into procedures or objects
     4) Some of the key goals of Windows Workflow Foundation technology are to:
       a) Provide a singular engine for workflow execution for all applications built on the Windows platform
       b) Support a wide range of scenarios from a highly people-centric workflow to the highly structured
    application-centric workflow and a variety of blended rules based on conditional scenario
       c) Bring model-driven workflow development to the entire WinFX development community such that every
    developer who today is familiar with the .NET Framework application can be immediately productive,
    building workflow-enabled applications without learning a second parallel set of technologies.
       d) Enable reuseable workflow component development through strong extensibility points and ensure both
    developers and ISVs can deeply embed this technology in their applications
     5) Windows Workflow Foundation Engine Architecture
       a) host process
       b) hosting layer: provides interfaces between Windows Workflow Foundation and a particular host for
    the following key services: Communication, Persistence, Tracking, Timer, Threading, and Transaction
       c) runtime layer: Execution, Tracking, State management, Scheduler, Rules
       d) workflow model layer: Sequential workflow model, State machine workflow model
    July 23

    Windows Workflow Foundation

    好久没写技术性blog了,一直在写代码,没啥新东西。写到一定程度,又有新想法了,就像代码写到复杂,就需要重构了。在实际开发中,技术其实并不是最重要的,就现有成熟的技术肯定能满足要求;最重要的就是要熟悉实际业务,业务不清楚,需求就没法明确。而无明确需求的开发基本就是浪费时间。
     
    但业务熟悉了,在开发中,却发现很多东西应该都有模式的。自己慢慢在写适用于自己程序的模式,但总归觉得不是特别好,自己能力总归有限。于是开始想到一个词,Workflow,这也是从用内容管理网站Plone中学到的一个词。google之,从基本什么都不懂,开始找好的框架。找到BizTalk,但觉得实在太庞大(盗版不盗版我倒不在意,呵呵)。又发现Vista下的新框架,Windows Workflow Foundation(WWF),觉得工作流在实际的Business中真的很有用,不仅可以方便操作,更重要的是可以规范流程,即处理业务的主动性从人转移到了程序。人只是被动的执行程序给你的任务。但这个对绝大多数工作人员都是乐意见到的事情。
     
    在想到Workflow前就想实现的功能,就是每个Role都有自己的Tasklist,业务流程有一个主服务在跑,每个人可以通过终端连接到服务(间接实现了不仅从程序而且可从网络操作)。每天一上班,打开电脑,就是主服务给你的任务。每天一下班,可以看看今天看了哪些工作,还有哪些新增加的工作。现在粗粗看WWF,觉得真的很适合。
     
    如果真能让程序和业务结合的这么简单而紧密,确实挺激动人心的。
     
    继续学习,看书先。下了本MS推荐的Presentation Windows Workflow Foundation。
     
    May 25

    VSTS2005

    概述:
     
     
    1. 构建 IT 解决方案的挑战
     1) 信息交流
     2) 工具捆绑
     3) 不合适的过程
     4) 另人失望的投资回报 (ROI)
    2. 改进软件开发生命周期
     SDLC: 软件开发生命周期(Software Development Life Cycle)
    3. 集成
     1) 用户界面集成
     2) 数据集成
     3) 过程集成
    4. 扩展性
     1) 用户界面扩展性
     2) 数据扩展性
     3) 过程扩展性: 方法论模板
     
    VSTS著名结构图
     

     

    构建健壮而可靠的软件

     From http://www.microsoft.com/china/msdn/library/langtool/vsts/dnvsentvstsdev.mspx

    1. 我们的解决方案
      1) 代码分析工具: PREfast(C/C++), FxCop
      2) 性能工具: 采样, 使用仪器

     

    企业级源代码管理和工作项跟踪

     From http://www.microsoft.com/china/msdn/library/langtool/vsts/dnvsentvststeam.mspx

    1. 软件配置管理 (SCM)
      1) 控制对源文件的访问
      2) 创建和管理工作项
      3) 生成产品版本
      4) 管理产品版本

    2. Microsoft 的 SCM 方法
      1) 工作项跟踪
      2) 源代码管理
      3) 策略支持
      4) 通知和报告生成功能

    3. 集成工作项跟踪和源代码管理

    4. Checkin 策略

    5. 生成报告

    6. 工作项跟踪功能

    7. 通过 Web 接口跟踪工作项

    8. 源代码管理: 可伸缩性, 完整性和可靠性, 速度

     

     

    Microsoft Solutions Framework(MSF)

     From http://www.microsoft.com/china/msdn/library/langtool/vsts/dnvsentvstsmsf.mspx?mfr=true

    1. MSF 的远景目标是为软件专业人员提供由软件专业人员开发的高效、集成和可扩展的流程指导。

    2. 传统的方法
     1) 灵活流程模型(Agile)
     2) 正式流程模型

    3. 我们的解决方案
     1) 为维护而设计的内容
     2) 一个清楚的元模型
     3) 一个清楚的插件模型
     4) 许多贯穿整个内容的导航路径。

    4. 扩展可能性
     1) 流程指导
     2) 迭代结构
     3) 进入条件和退出条件视图
     4) 工作项类型定义和规则(活动和工作产品)
     5) 工作项查询
     6) 源代码签入策略
     7) 角色群集和安全组
     8) 文档模板(Excel 和 Word)
     9) Microsoft 项目模板
     10) 报告
     11) 项目门户/SharePoint 站点模板

     

     

    通过更完善的测试实现更好的软件

     From http://www.microsoft.com/china/msdn/library/langtool/vsts/dnvsentvststest.mspx?mfr=true

    1. 我们的解决方案
     1) 支持的测试类型: 单元测试, Web 测试, 通用测试, 加载测试, 手动测试
     2) 扩展可能性: 通用测试, 测试类型加载项

    May 10

    A wondonerful article about BindingSource and DataBinding in .NET 2.0

    Tackle Complex Data Binding with Windows Forms 2.0
       
    By: Brian Noyes


    Here

    April 26

    Team Foundation Server 安装完全攻略

    其实TFS随带的帮助已经写的很清楚了,如果干净机器照着步骤做一般不会有问题。这里只是整理一下。
    按照Single Server模式安装。
     
    1. Logical Architecture: 3层,Data Tier(SQL 2005), Application Tier(IIS), Client Tier(VSTS)
    2. Physical Architecture: Single-Server(SQL and IIS in one server), Dual-Server(SQL and IIS
    in different servers), Team Foundation Build(build automation), Team Foundation Server
    Proxy(version control files cache) and Team Explorer(in client)
    3. Required Accounts:TFSSETUP(安装帐户,可用Administrator), TFSSERVICE(TFS服务帐户),
    TFSREPORTS(TFS Report服务帐户), TFSPROXY(TFS Proxy服务帐户)。 没用TFSPROXY,TFSSERVICE和
    TFSREPORTS分配了administrator权限。
    4. Required Ports: SQL Server, SharePoint and TFS需要端口。这个很重要,如果有其他程序占用相
    同端口要改掉。
    5. TFS Security Groups: Team Foundation Administrators, Service Accounts, Team Foundation
    Valid Users。 安装的时候不用管,安装完后安装时用的帐号自动进入TFS Administrators,此时再给其
    他人分配权限。关于权限的问题需综合考虑Windows, SharePoint, SQL Server和TFS,比较复杂。这里为
    了简单全部用Administrator权限。
    6. 安装Prerequisites:
      1) IIS 6.0 with ASP.NET and without FrontPage Server 2002 Extensions.
      2) SQL Server 2005 with SP1(除了Integrated Service, Replication in Database Service其他都
    要装)
      3) .NET Framework 2.0 with hotfix KB913393(in cd)
      4) SharePoint with SP2(注意语言要和TFS的语言一致,不如不一致要下Language Pack)
      5) 开防火墙
      6) 去微软网站自动更新
    安装这些对以后的安装会有很大影响,所以最好是干净机器。如果已经装了某些,要确认是否符合要求。
    SQL Server最关键的是Report Service,如果以前配置过要重新用管理工具配置一下符合实际要求。
    SharePoint要用Server Farm安装方式不要用Type方式。安装完后出现网页也不要配置,TFS会自动配置。
    IIS里最好也要干净的。
    7. 如果Prerequisites安装没问题,安装TFS应该也没问题。列出几个碰到的问题:
      1) SharePoint已配置过:安装Sharepoint一定不要选自动配置或者安装完手动配置,否则只有重装
    Sharepoint
      2) SQL Server Report Service配置不对:在Report Service管理台中重新配置,符合具体IIS配置情
    况。配置好后可以在IIS中查看Report和ReportService两个虚拟路径下的网页。
      3) SQL Server Database配置不对:装之前数据库中删掉出了系统、Report、ReportTemp的其他数据库

      4) 帐号权限不对不能执行某些任务:把TFSSERVICE和TFSREPORT加入到administrator组。
    8. 安装TFS build。没问题
    9. 安装TFS Proxy。不用这个。
    10. 安装Team Explorer:
      1) Install Office Excel 2003(install .NET Programmability Support)
      2) Install Project Professional 2003(install .NET Programmability Support)
      3) Install VSTS
      4) Install Team Explorer
    11. 用Team Explorer连入TFS: Tools->Connect to Team Foundation Server
    用到这里应该简单不用具体说了吧。

    TFS终于能用了

    经过10+次的安装,终于把Team Foundation Server搞定了,客户端的VS能顺利连上并建立新项目。昨天虽然安装成功但还是不能用,今天稍微有点轻车熟路的又装了几遍,终于能用了。明天再来写具体安装步骤。界面不错。附上一张网页。强烈期待中文版!!
    April 25

    Installation and Administration of Team Foundation Server

    Installation and Administration of Team Foundation Server


        (from http://msdn.microsoft.com/vstudio/teamsystem/team/quickstarts/install/default.aspx)

    1. Team Foundation Server Installation Guide

        参看上一篇文章。注意,一定要看安装文档,一步一步按照帮助来。

    2. Installing the Team Explorer on client machines

        安装TFS光盘上的Team Explorer(不会连VSTS都没安装吧)。

    3. Customizing Team Foundation Server

        don't know now

    4. Setting up a Nightly Build

    The Nightly Build process is broken into five main phases:

    • Retrieving from Source Control.
    • Building the product (Compile).
    • Execution of Unit Tests.
    • Sharing the result of the build.
    • Reporting the build results and metrics.
    for first three steps, I would prefer to use NAnt and NUnit.