设计体系结构

通常网站由多个单独的模块组合而成,然而,所有的模块都有以下共同的“设计问题”需要解决:

  • 从业务逻辑代码和表示层代码(用户界面)中分离出数据访问代码,增强网站的可维护性和可扩展性。这称为多层设计。
  • 使数据访问架构独立,以支持不同的底层数据库---即底层数据库发生改变时不需要对业务对象层进行修改。(每一层的相对独立变动,不影响其他层的改变)。这称为层去耦。
  • 设计业务对象架构,以面向对象的方式来暴露从数据访问层取得的数据。这个过程就是将关系型数据映射到oop类上。(ORM[Object Relational Mapper]对象关系映射)
  • 支持业务对象缓存,以存储从数据库中取得的数据,从而提高性能。
  • 对异常和其他重要的事件(如删除记录)进行处理和记录,方便错误诊断和提供审计追踪。
  • 网站和模块的配置信息保存到容易读写的地方。(Web.config)
  • 将许多用户界面控件同从业务逻辑层获取的数据进行绑定。

用户界面层主要用于展示数据;业务逻辑层(BLL)主要用于实现业务规则和操作数据;数据层只用于保存数据。

 

设计分层结构

  1. 数据存储层:保存数据的地方。(关系型数据库/XML文件/文本/Access等)
  2. DAL层:对数据存储中的原始数据进行操作的代码。
  3. BLL层:获取DAL层检索到的数据,并用更抽象、直观的方式显示到客户端,达到隐藏底层的细节(数据库结构),并可以增加逻辑验证以保证输入的安全性和一致性。
  4. 显示层(用户界面):定义用户在屏幕上可以看到的内容,包括格式化的数据和系统导航菜单。

 

设计数据访问层(DAL)

  是对数据库执行查询、更新、插入和删除操作的代码,是最接近数据库的代码,必须了解数据库的所有细节。

  1.用提供程序模型设计模式来支持多种数据库

      不能直接写DAL类,应当先写一个抽象基类(IDAL),其中定义(CRUD方法的签名),必要的话(实现一些辅助方法)。真正的数据访问代码是在继承于基类的次类(称为提供程序[provider])中[如SQLDAL]。通常专门支持某一类型的数据库[SQLServer数据库]。

   

2. 使用强类型的DataSet还是自定义实体(Entity)

    DataSet/DataTable:

      优点:智能感知/内置排序和过滤/IDE完美集成/binary formatter[二进制序列化]/支持不同并发策略

      缺点:性能和扩展的局限性(要完整的DataTable,增加内存开销)/数据的表示形式模糊,数据库结构发生一点改变(字段变化),必须重建类型化的DataSet/添加自定义业务和验证逻辑很困难

 

     自定义实体对象是一个类:用面向对象的方式将从数据库中获取的数据封装,使得数据库结构和其他细节抽象化。

    第一种情况:实体类简单,只是与数据库的表(视图)一一对应,无增/删/改/查的方法。

    第二种情况:还增加了对其父对象或子对象进行引用的其他属性,还要有操作数据的实例方法。(这种类称为域对象)

    优点:更容易让UI人员使用,容易维护,只载入需要的数据(更快/更少内存),可自定义序列化方法,自定义验证逻辑。

    在Web应用程序中,特别是在BLL和UI之间,作者更倾向于使用自定义对象而不是DataSet

 

3.使用存储过程还是用SQL文本查询

  在SQL文本查询中,只要使用带参数的SQL语句(避免SQL注入攻击)查询和存储过程(‘预编译’)的性能是差不多的。

  存储过程更好地进行数据安全访问控制(可以给用户指定权限)---比如限制访问某些表的字段。不过,很少用到行级安全的时候。

  存储过程通常包含了一批语句,调用时只需要传名称,而不是很多字符组成的SQL语句,可使网络通信量最小化。

  存储过程提供了深一层的代码。在DAL层中可以没有SQL代码。方便对DAL部署和维护。

  SqlCommand执行SQL文本查询的最大优势就是灵活。(UI中的高级查询和过滤功能/动态查询)

  总结:通常情况使用存储过程来获取和处理数据库中的数据,除非对存储过程来说查询太动态和复杂,那就使用SQL文本查询

 

4.数据访问类的基类

抽象类:从继承树的垂直方向给我们提供复用功能

接口:   从树的交叉方向给我们提供这些功能的替代(称为多态性)

只有很好地掌握了这些概念才能为系统构建出最好的体系结构。

所有抽象基类提供程序本身都是从一个基类(DataAccess)继承的。

DataAccess:提供一些属性,从web.config读取自定义配置进行封装,对DbCommand对象(SqlCommand、OledbCommand)的基本方法(ExecuteNonQuery、ExecuteReader和ExecuteScalar)进行封装。

  

设计业务逻辑层

在开始阶段,使用面向对象和强类型化的数据表示方式比用DataSet来传递数据需要更多的开发时间,需要更多有才能和经验的开发人员来进行设计,但在以后会得到更好的可维护性和更高的可靠性。

只要拥有了一个设计良好的、完全的、使用域对象的BLL层,开发用户界面将很容易,以前花费的时间在UI开发时得到弥补。

下面的代码片段展示了UI开发人员怎样获取一个填充了一些数据的Customer对象,对它的一些字段进行更新,并且保存到数据库。

 Customer cust=Customer.GetCustomerByID(2);

cust.FirstName=”Marco”;cust.LastName=”Bellinaso”;

cust.Update();

使用UpdateCustomer静态方法会更简单一些,因为不需要创建类实例:

Customer.UpdateCustomer(2,”Marco”,”Bellinaso”);

  • 如何避免对同样的、没改变的数据进行又一次查询?
  • 如何对多个子操作组成的方法进行事务处理?
  • 如何把需要关注的事件记录到日志中?

1.缓存数据以提高性能

   aspnet_regsql.exe创建SQL依赖所需要的表、触发器和存储过程。

aspnet_regseq.exe –E –s .\sqlexpress –d aspnetdb –ed

注意事项:确保[SQL Server配置管理器中的SQL Server 服务开启]

图示参考:

运行isqlw.exe,登录进去后,看不见mydb.mdf,所以先附加一个映射的逻辑数据库名称,命令如下:

sp_attach_db DBlogicName,"F:\netStudy\三层体系\三层体系\App_Data\mydb.mdf",这样,再次刷新数据库,看到了DBlogicName 。

运行控制台命令:

aspnet_regsql.exe –E –S .\sqlexpress –d DBlogicName –ed

DBlogicName数据库启用了缓存依赖,生成了相应的表和存储过程:

下一步是对一个特点的表添加支持,意味为该表创建一个触发器,并在AspNet_CacheTablesForChangeNotification表中新增一条记录

aspnet_regsql.exe –E –S .\sqlexpress –d DBlogicName –t Customers –et

最后不要忘记分离数据库: sp_detach_db "DBlogicName”

最后在web.config中进行轮询设置。

配置好后,可以写代码来进行数据缓存了。

System.Web.Caching.SqlCacheDependency dep=new SqlCacheDependency(“SiteDB-cache”,"Customers”);

Cache.Insert(“Customers”,Customer.GetCustomers(),dep);

这种缓存机制还可以用于ASP.NET的输出缓存功能(Output Caching) 功能,即将页面生成的HTML缓存起来。

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="三层体系._Default" %>
<%@ OutputCache Duration="3600" VaryByParam="none" SqlDependency="SiteDB-Cache:customers" %>

使用该指令,网页的输出将被缓存最多一个小时,如果Customers表中的数据发生改变,这时间更短

网页中看效果:

<form id="form1" runat="server">
    <div>
      <%=DateTime.Now  %>
        <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" 
            DataSourceID="SqlDataSource1" EmptyDataText="没有可显示的数据记录。">
            <Columns>
                <asp:BoundField DataField="id" HeaderText="id" SortExpression="id" />
                <asp:BoundField DataField="name" HeaderText="name" SortExpression="name" />
            </Columns>
        </asp:GridView>
        <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
            ConnectionString="<%$ ConnectionStrings:SiteDB %>" 
            ProviderName="<%$ ConnectionStrings:SiteDB.ProviderName %>" 
            SelectCommand="SELECT [id], [name] FROM [customers]"></asp:SqlDataSource>
    </div>
    <asp:Button ID="Button1" runat="server" Text="Button" οnclick="Button1_Click" />
    </form>
protected void Button1_Click(object sender, EventArgs e)
        {
            GridView1.DataBind();
        }

 

等待几分钟后,点击按钮,可以看到页面【时间】没有变化,已经正确缓存了。

我们来改动数据,再点击按钮看看效果:

可以看到,数据改动后,重新缓存了。缺陷:当该表的任何数据改变,都会使缓存失效。为表级别的缓存依赖跟踪。

但SQL Server2005增加了行级别的缓存依赖跟踪。

SQL Server2005特定的SQL 依赖支持

        private string connStr = ConfigurationManager.ConnectionStrings["SiteDB"].ConnectionString;
        protected void Page_Load(object sender, EventArgs e)
        {
            connStr = connStr.Replace("|DataDirectory|", Server.MapPath("~/app_data"));
            SqlDependency.Start(connStr);//应放在global.asax中的Application_Start全局事件处理程序中
            using (SqlConnection conn = new SqlConnection(connStr))
            {
                conn.Open();
                SqlCommand cmd = new SqlCommand("select name from dbo.customers", conn);
                SqlDependency dep = new SqlDependency(cmd);
                dep.OnChange += new OnChangeEventHandler(dep_OnChange);
                SqlDataReader reader = cmd.ExecuteReader();
                while (reader.Read())
                {
                    Trace.Write(reader["name"].ToString());
                    Response.Write(reader["name"].ToString() + "<br/>");
                }

            }
        }

        void dep_OnChange(object sender, SqlNotificationEventArgs e)
        {
            StreamWriter sw= File.AppendText("c:\\changeData.txt");
            sw.Write("\n Customers表数据已经更改,请重新获取最新数据!" + DateTime.Now.ToString());
            sw.Close();
        }

 这种技术的限制很严格

不能用“*”,必须列出所有字段,引用表需要全名(dbo.customers)/不能用聚合函数/不能使用排序和windowing函数/不能用视图或临时表/查询不能返回text、next或image(blob类型)的字段/不能用DISTINCT、HAVING、CONTAINS和FREETEXT。/不能包括子查询、join/存储过程不能用SET NOCOUNT ON语句

事务管理

新的System.Transactions名称空间

在DAL中由于共享同一个连接,那么将会创建一个轻量级的事务。通常事务在BLL运行,并且必须封装调用几个其它的方法,那些方法可能在内部创建了单独的针对不同数据库的连接。可能会在底层创建了一个分布式的COM+事务。

using (TransactionScope scope = new TransactionScope())
            {
                MyBizObject1 obj1 = new MyBizObject1();
                obj1.DoSomething();

                MyBizObject2 obj2 = new MyBizObject2();
                obj2.DoSomethingElse();

                scope.Complete();
            }

作者不推荐在共享的Web主机上使用COM+、SWC或者System.Transcation,因为不能控制服务器。

 

配置健康监视系统

      该系统以提供程序模型设计模式为基础:意味从一个通用的抽象类继承出几个类,并提供具体的实现。内置的提供程序能把日志保存到SQL Sever数据库、Windows Event Log、WMI Log 或电子邮件中。也能通过继承System.Web.Management.WebEventProvider基础提供程序类来创建自己的日志提供程序。

存储在web.config文件中的配置格式如下:(可为日志事件定义规则、注册自定义事件、注册并选择用于日志事件的提供程序类)

<healthMonitoring>
        <eventMappings>...</eventMappings> --手动注册需要记入日志的内置类和自定义类。为注册的事件类指定一个名称
        <providers>...</providers> -- 注册提供程序
        <rules>...</rules> --设置要将事件记入日志、该事件使用什么提供程序。以及多长时间记录一次。
        <profiles>...</profiles> --可被运用到多个规则中<rules>
        <bufferModes>...</bufferModes> --可能的应用于提供程序的缓冲信息
</healthMonitoring>
 为SQL Server提供程序建立数据库
  aspnet_regsql.exe –E –S .\sqlexpress –d aspnetdb –A w
  数据通过aspnet_WebEvent_LogEvent存储过程保存在一个名为aspnet_WebEvent_Events的表中。

所有业务类的基类

  许多业务对象需要访问很多共享信息,如当前用户名、IP地址以及对当期环境中Cache对象的应用。这些信息可以放在一个基类中,让其他所有域对象从它继承。该基类还包含一些辅助方法,如通过替换特殊字符来对HTML输入进行编码,以及清除缓存中的项目。

保持基类短小,让它只包含子类所需的通用功能是个好的设计习惯,以后如何需要一个通用的属性或方法,在架构中拥有一个基类会很方便。

 

用户界面(省略)

转载于:https://www.cnblogs.com/netxiaochong/archive/2011/12/19/2293373.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值