[转载]企业应用下的业务组件开发实践

mikel阅读(1213)

[转载]企业应用下的业务组件开发实践 – Anders小明的Blog – 博客园.

作者:  Anders小明


什么是企业应用下的业务组件

首先,这是一个组件,这 意味着它需要在容器里运行,因此不包括任何中间件服务,同时以一定结构(文件结构或者压缩格式)组成,被容器识别;其次,这是一个业务组件,即提供的是应 用服务,而非技术服务;第三,这是企业应用,在业务上包括功能和服务(Service,当前最时髦的说法,你可以理解为API),技术上(以J2EE来讲)包括:UI资 源(JSFJSPJSCSS等)、应用程序(Java)资源和配置文件、数据库表定义、初始化数据和存储过程。

为什么要企业应用下的业务组件

组件技术从提出到现在已 经有20多年了,为什么要提企业应用业务组件?因为现有的组件技术不支持 企业应用环境下的组件要求,J2EEEJB不支持,.NETDLL也不支持。

如前所述,一个企业应用 通常包括了交互界面、应用代码以及数据库结构,而不论是EJB还是DLL只支持应用代码,都不包括交互界面和数据库结构。

如果说EJB不是,那么J2EEEAR或者WAR是否算 是一个组件?答案也不是,EAR或者WAR部署的是一个企业应用,请注意EJB规范中明确说:The Enterprise JavaBeans architecture is a architecture for the development and deployment of component-based (distributed) business applications(EJB 2.x3.x唯一的区别 是2.xdistributed),它们有自己的应用域,彼此相互隔离(简单的看,它们有各自独立的会话管理)。.NET也是有自己的应用域概念。

更进一步,基于应用的部 署导致了三个隔离问题:交互(界面)隔离程序访问隔离数据隔 离(请注意这三个问题分别对应了企业应用业务组件的三个技术内容)。交互隔离导致了企业用户必须访问不同界面,代码访问隔离导致了点对 点的集成以及诸如性能、事务和异步处理等各种非功能性问题,而数据隔离导致了数据有效性、一致性等等问题。所有这些都进而导致了维护的问题。

为了解决这些问题,大厂 商们都提出了各种解决方案:Portal来解决交互隔离问题,通过ESB来解决代码访问隔离问题,以及通过所谓的信息服务(Information Service)来解决数据隔离问题。

那么OSGi技术或者SCA技 术能否满足要求?答案是目前不能,OSGi最初开发的目的就不是为了企业 应用,只是这几年开始成熟,并向企业应用方向发展。09年推出的企业版 (草案)刚刚提出针对程序访问问题的方案,如远程服务、事务管理等;交互隔离问题上规范并没有提出相应方案,只有EclipseEquinox提 出了界面的扩展点机制,但这也不能解决B/S环境的问题;而数据隔离问题 就没有任何方案。SCA从一开始就是面向企业应用,不过不解决交互隔离和 数据隔离问题。

此外,对于行业ISV来说,除企业用户面临的这些种种问题,还面临着其它问题。企业用户毕竟只是面对自己的需求,行业ISV却面临着多个企业用户的需求,面临定制化带来的维护问题,特别是业务和技术的隔离问题(即如何保持 构建业务组件的所使用技术的平稳升级)。

组件的容器

既然要企业应用下的业务 组件,而现有的组件技术又无法支撑,那么就需要一个新的组件容器了(当然,作为一个普通开发人员,我们无法新建一个公开标准的组件体系,也独立维护一个私 有的)。新的组件容器完全使用现有的中间件技术,并加上一些新的内容,包括如下:

  1. 组 件框架,识别组件,以及组件(文件)结构和各个技术工件。
  2. 技术框架,提供业务无关的技术支持,以便于技术的平稳升级切换。
  3. 运行容器,采用现有中间件技术,包括Tomcat、应用服务器和数据库服务等;
  4. 工具,包括打包以及部署工具等。

关于数据隔离问题,在EIP中提到了各种解决方案,这里采用的共享数据库方式,即各个组件都共用一个数据库,各个组件只提供数 据库定义和初时数据(如同EJB/OSGi一样,运行时环境由容器提 供)。

组件的关系

组件的关系分为两种:依 赖和联动。依赖关系在已有的组件技术上已经广为认知,而联动则是新创造的(肯定不是第一个创建的,只不过不同人有不同的叫法)。

联动和依赖的区别是:如 果有组件B和组件A联动,则组件B可以 在没有组件A的情况下运行,并提供相应功能。

针对三种不同技术工件 (即三个隔离问题)呈现不同特点,如下:

1. UI资源(交互隔 离问题),依赖是指UI资源的嵌入、引用和替换,联动是指UI资源的新增。

2. 应用程序(程序访 问隔离),依赖是指API/模型依赖,联动是指消息(传统消息和JMS消息)以及SPI实 现。其中,无论是依赖或者联动都涉及到相应的非功能性需求,包括:异步、事务控制和服务时限等。

3. 数据库资源(数据 隔离),依赖是指外键关联和级联操作,无明显的联动关系。

这里,需要关注应用程序 的依赖和联动

1. SPIAPI存在业务不匹配问题。

虽然组件A依赖组件B,但是 不代表组件B提供的服务完全匹配组件A的要求。有时组件A所 需要的数据需要组件B的多个API组成,为了开发方便或者组件A所需要的性能问题,可能会在组件B新写一个接口给组件A使用,注意该接口不是组件BAPI,该接口仅适用于组件A

2. 尽可能的使用SPI集成方式

SPI集成方式是相对于API集成方式,API集 成方式就是,组件B直接调用其它组件的API及其模型。SPI集 成方式(类似于依赖倒置),组件B定义其所需要的接口及其模型,由组件A或者胶水层代码来实现。

这个点对于行业ISV尤为明显。对于企业用户来说,依赖是明确的,组件A依赖/联动于组件B,但对于行业ISV, 则面临着定制化问题,虽然组件A依赖/联动于组件B,但是 在某个定制化项目中,由于客户已有系统C,而需要组件A依赖/联动于客户 已有系统C。此时采用SPI方式。

在开源世界里采用SPI方式更是广泛。很多框架为了兼容(同一功能)不同实现的类库,都是先定义框架所需接口,并同时提供 不同类库的胶水代码。

不论是EJB/OSGi/SCA都没有对SPI集 成方式的支持。

3. 依赖和联动的非功 能性需求。

事实上,非功能性需求都 是在集成时才存在的。以事务管理为例,除了及其少数的例子外,大部分事务只能在处理流程才被决定(注意,EJB在这方面着是定义在API上的,这样的设计是不适应需求的),而组件AAPI在用例1中需要被 异步调用,而在用例2中需要被同步调用是常见的。即便是OSGi规范,也在这方面没有任何处理。

组件的定制化

定制化问题只针对于行业ISV有效,对于企业用户来说,除非是那种跨国企业在面临不同国度的业务模式、法律监管和会计制度等差 异,存在定制化需要,即使如此,ISV和企业用户对于同一问题的解决方式 也是不同的。

既然我们已经将原有的应 用采用组件化方式开发,那么应用的定制化问题就转化为组件的定制化问题。同样,应用的定制化手段也就转化为组件的定制化手段。

组件框架

罗罗嗦嗦的说了半天,有人就说了:这不就是把UIJava和数据 库三个东东一打包,然后说这就是一个企业应用下的业务组件,有啥新意呢,不就是模块化开发嘛,一直一来大家都是就是这么搞的嘛,何必搞个怪名词来忽悠。

是的,就是把UIJava和数据库 三个东东整合在一起,组件容器说提到的技术框架很多的开发队伍都有一套,运行容器更是有无数开源商业的,打包部署工具更是写了无数。

这确确实实就是我们常说 的模块化开发。但是模块化开发不同于组件开发,模块化开发只是在逻辑上做了切分,物理上(开发出的系统代码)通常并没有真正意义上的隔离,一切都只是在文 档中。

我们需要一点干货,只有 实实在在的组件框架才能组件化开发真正落地的(如同OSGi框架那样): 我们需要一个类似于Equinox的界面扩展框架来支持UI资源的依赖和联动;我们需要一个集成框架来支持应用程序的依赖和联动,解决所面临的种种问题(业务 不匹配、SPI集成以及各种非功能性需求);我们需要一个打包部署工具 (类似Spring DM)提供部署UI资源、应用程序和数据库定义资源(Spring DM提供了基于Web资 源的部署能力)。

其它问题

对于采用J2EEB/S环境的组 件应用还面临一个问题,即现有Servlet规范只允许一个web.xml,不支持组件各自定义私有的FilterServlet, 不过这个问题不是很严重,在现有技术框架已经支持一份简单的web.xml, 而新的Servlet规范已经允许多个web.xml

分布式部署以及集群部署 问题,这其实不是个问题。基于应用的我们有很多手段和技术,那么基于组件的也一样有办法。

[转载]ASP.NET的SEO:目录

mikel阅读(1153)

[转载]ASP.NET的SEO:目录 – 自由飞 – 博客园.

ASP.NET 的SEO:基础知识

ASP.NET 的SEO:Global.asax和HttpModule中的RewritePath()方法——友好的URL

ASP.NETSEO:正则表达式

ASP.NET 的SEO:服务器控件背后——SEO友好的Html和JavaScript

ASP.NET的SEO:使用.ashx文件——排除重复内容

ASP.NET 的SEO:HTTP报头状态码—内容重定向

ASP.NET 的SEO:Linq to XML—网站地图和RSS Feed

ASP.NETSEOSEO Hack

这 个系列可以算是我的一个读书笔记—WROX红皮书系列之《搜索引擎优化高级编程》(Professional Search Engine Optimization with ASP.NET:A Developer’s Guide to SEO)。我觉得蛮不错的,第一是比较系统和权威;第二是不同于一般的SEO的理论介绍,它着重于asp.net技术的实现!推荐一下。另外,因为是老外 的书,所以所谓的搜索引擎,其实没有包括百度,主要针对的是Google和Yahoo。但博客中也有很多知识只是我自己的理解,欢迎大家指正讨论。

SEO, 搜索引擎优化,简单的理解,就是一种让网站能尽可能的被搜索引擎收录而且排名靠前的技术。可能很多程序员并不是很看重或者了解,但对于无数的草根站长(包 括垃圾站长,呵呵)和众多希望进行网络营销的中小企业而言,SEO是简直是一个入门的基本功。但关于SEO的基础知识,我也就点到即止,因为类似的文章网 上太多了。而且是在博客园里面,我会把重点放在技术层面上。

所涉及的技术其实比较“底层”,对于直接学习 ASP.NET2.0甚至是3.5的同学来说,应该还是很有帮助的,如:
1. 应用程序生命周期事件,如Global.asax和HttpModule;

3. 正则表达式;(以上都关系到URLRewrite)

4. Http报头信息中的状态码:404、301、500等;(主要用于站点重定向)

5. XML文件生成;(关系到网站地图,RSS)

6. HttpHandler;(动态的生产验证码、Robert.txt文件,用于排除重复内容)

[转载]ASP.NET的SEO:Linq to XML---网站地图和RSS Feed

mikel阅读(1113)

[转载]ASP.NET的SEO:Linq to XML—网站地图和RSS Feed – 自由飞 – 博客园.

本 系列目录

网站地图的作用是让搜索引擎尽快的,更多的收录网站的各个网页。

这里我们首先要明白一个基本的 原理,搜索引擎的爬行方式。整个互联网就像一张纵横交错的“网”:网的各个节点就是各个网页,而各个网页之间通过url相互连接。蜘蛛可以从一个网页出 发,通过该网页上的url,爬到另一个网页;再通过另一个网页上的url,再爬到更多的网页……,以此类推。但如果是一个新发布的网站,可能就没有其他 url指向它,那么它就永远不会被“爬到”(收录)。为了解决这个问题,新站可以自己主动向搜索引擎提交url,申请蜘蛛前来抓取(Google申请网 址:),但申请时一般只会提交一个主页的url。

为了让所有的url(尤其是动态生成的)都能被蜘蛛快捷便利的检索到,我们就需要提供一 个全面完整、架构清晰和更新及时的网站地图。(网站地图的更多信 息)。

和处理重复内容的robots.txt文件,我们通过.ashx文件来生成一个基于sitemaps.org的 xml格式的网站地图。网站地图生成之后,我们就可以向Google等搜索引擎提交。大量的文章证实,提交网站地图将极大的提高网站的收录速度和深度。其 他几乎所有的SEO方法,都有可能效果难以证实、失效甚至带来副作用,但提交网站地图除外!

Linq to XML为我们带来了近乎完美的操作体验。

WebSite

<%@ WebHandler Language=C# Class=website %>

using System;
using System.Web;
using System.Xml;
using System.Xml.Linq;
using System.Linq;

public class website : IHttpHandler {

public void ProcessRequest (HttpContext context) {

context.Response.ContentType = text/xml;

//文件的声明信息, 第第三个参数standalone的值yes 表示这个 XML 文档是自包含的(self-contained)而不依赖于外部所定义的一 个 DTD.
XDeclaration declaration = new XDeclaration(1.0, UTF-8, yes);
context.Response.Write(declaration);

//XML文件的命名空间
XNamespace ns = http://www.google.com/schemas/sitemap/0.84;
XElement siteMap = new XElement(ns + urlset);

string fixedUrl = http://www.freeflying.com/article;
string wholeUrl = string.Empty;

//循环取出数据,转换成XML节点
foreach (var item in Articles.GetArticles())
{
XElement url = new XElement(url);

wholeUrl = string.Format({0}?id={1}&catelog={2},fixedUrl,item.ID,item.Catelog);
XElement loc = new XElement(loc, wholeUrl);
XElement lastmod = new XElement(lastmod, item.LastMod.AddDays(23).ToShortDateString());
XElement changefreq = new XElement(changefreq, item.Frequency);
XElement priority = new XElement(priority, item.Weight);

url.Add(loc, lastmod, changefreq, priority);

siteMap.Add(url);
}

//最后输出整个xml文件
context.Response.Write(siteMap);
}

public bool IsReusable {
get {
return false;
}
}

}

同 样还将使用到xml技术的还有RSS

RSS

<%@ WebHandler Language=C# Class=rss %>

using System;
using System.Web;
using System.Xml;
using System.Xml.Linq;

public class rss : IHttpHandler {

public void ProcessRequest (HttpContext context) {
context.Response.ContentType = text/xml;

context.Response.Write(<?xml version=\1.0\ encoding=\UTF8\ ?>);

XElement rssFeed = new XElement(rss, new XAttribute(version,2.0));

string fixedUrl = http://www.freeflying.com/article;
string wholeUrl = string.Empty;

XElement channel = new XElement(channel,
new XElement(title, freeflying),
new XElement(link, fixedUrl),
new XElement(description,the website for dream flying freely),
new XElement(pubDate,DateTime.Now.ToString())
);

foreach (var article in Articles.GetArticles())
{
XElement item = new XElement(item);

XElement title = new XElement(title, article.Title);

wholeUrl = string.Format({0}?id={1}&catelog={2}, fixedUrl, article.ID, article.Catelog);
XElement link = new XElement(link, wholeUrl);

XElement description = new XElement(description, article.Description);

XElement pubDate = new XElement(pubDate, article.LastMod.ToString());

item.Add(title,link,description,pubDate);

channel.Add(item);
}

rssFeed.Add(channel);

context.Response.Write(rssFeed);

}

public bool IsReusable {
get {
return false;
}
}

}

模拟数据

using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Web.UI.MobileControls;
using System.Collections.Generic;

/// <summary>
/// Summary description for Articles
/// </summary>
public class Articles
{
public Articles()
{
//
// TODO: Add constructor logic here
//
}

public static List<Article> GetArticles()
{
return new List<Article>(){
new Article(234, blog, DateTime.Now.AddDays(23), Freq.none, 0.8, ASP.NET SEO, articles about SEO in ASP.NET),
new Article(267, blog, DateTime.Now.AddDays(245), Freq.daily, 0.6, ado.net pro,about the dataset usage),
new Article(653, news, DateTime.Now.AddDays(45), Freq.daily, 1,CLR via C#,notebook about this book)
};
}

}

public class Article
{
public int ID;
public string Catelog;
public DateTime LastMod;
public double Weight;
public Freq Frequency;
public string Title;
public string Description;

public Article(int id, string catelog, DateTime lastMod, Freq frequency, double weight, string title, string description)
{
ID = id;
Catelog = catelog;
LastMod = lastMod;
Weight = weight;
Frequency = frequency;
Title = title;
Description = description;
}
}

public enum Freq
{
none = 1,
daily = 2,
weekly = 3,
}

[原创]nvelocity模板调用.NET类的方法代码

mikel阅读(1482)

最近利用NVelocity+MVC进行开发过程中,发现NVelocity没有四舍五入的函数,利用js进行页面处理又太慢,如果可以调用.NET类的方法就太完美了。

于是Google了一下,发现篇Velocity调用Java代码的文章:

velocity模板开发中减法 加法

既然NVeclocity是从Velocity来的,自然少不了这个功能了,于是将ASP.NET的代码修改如下:

public class MatchTool

{

/// <summary>

/// 四舍五入

/// </summary>

/// <param name="d"></param>

/// <param name="decimals"></param>

/// <returns></returns>

public Decimal Round(decimal d, int decimals)

{

return System.Math.Round(d,decimals);

}

}

//添加C#的Math数字计算实例给模板用于四舍五入

AddContext("MathTool",new MatchTool());

由于C#的System.Math是静态类型,不能实例化赋值给模板标签$MathTool,于是给包装了一下,实例化了个MathTool类封装了Round方法,于是将它的实例赋值给模板标签MathTool

模板代码调用代码如下:

$MathTool.Round($order.UnitPrice,2)

另外NVelocity也可以调用.NET的String类型的方法,例如String类型的CutString方法

代码如下:

$Str.Cutstring(0,20) 截取20个字符

[转载]项目开发总结:前端开发部分总结[兼容性、DOM操作、跨域等]

mikel阅读(990)

[转载]项目开发总结:前端开发部分总结[兼容性、DOM操作、跨域等]

项目背景:.Net 3.5+MySQL+JQuery+WebService

在公司做这个项目已经6个多月了,总结一些问题,也算 是抛砖引玉吧,希望园子里更多的朋友一起分享一些技巧。

1、 WebService方法返回值不能为void。

当 WebService方法返回值为void时,FF和Chrome会持续等待,认为这个请求没有结束,而在IE中一切是正常的。

2、 当input的type=”button”时或者使用button时,点击后会触发form的submit。

当时查找页面刷新的问题找了很 久,最后才发现是button会自动触发form的submit,导致当前页面的刷新。

3、js设置document.domain只能从 二级域名设置到顶级域名。

比如访问moozi.cnblogs.com,可以在这个页面中写 document.domain=’cnblogs.com’;如果把域设置为cnblogs.com之后,无法再使用 document.domain=’moozi.cnblogs.com’;。

4、使用DOM片段创建iframe,当跨域时,不能用js 写iframe的域。

比如你在moozi.cnblogs.com/test.html页面用js把域设置为 document.domain=’cnblogs.com’,而此时你再用var ifrm=createElement(‘iframe’) ,这时将不能用js设置iframe的域也为’cnblogs.com’,只能让ifrm.src=’test2.htm’,并在test2.htm中设 置域document.domain=’cnblogs.com’。

5、当你修改过当前域的时候,CKEditor是正常的,而 tinyMCE需要重新设置域。

CKEditor能自动识别当前域,并不出错。tinyMCE需要人为的设置。同时,如果你是用 CKFinder上传文件,而需要修改它的输入代码,不然会因为域不同而出现没有权限的错误提示。

6、使用tinyMCE,尽管你的初始化 代码只有一句,但是可能出现多次初如化。发生原因未知,在不确定的情况下会出现此BUG。

7、开发js组件的时候,要时刻注意闭包 和this的指代,这方面之前吃了不少亏。

8、将字符串转换成JQuery对象时,要注意字符串html代码的闭合标签。

比 如:var html='<span>test</span>&nbsp;’var jqEl=$(html);此时,jqEl会把html里的’&nbsp;’丢掉。

9、JQuery1.2.6和 jQuery1.3在细节的处理上有一些区别。

jQuery1.2.6处理hasClass(‘.className’)会出错,而 jQuery1.3不会。

10、尽量少用jQuery的each,多用js原生的代码,性能较高。

———————-

先 写到这里吧…继续更新

[转载]Asp.net MVC并不仅仅只是Linq to SQL

mikel阅读(1601)

[转载]【译】Asp.net MVC并不仅仅只是Linq to SQL – CareySon – 博客园.

很多ASP.NET的教程中的示例代码使用的数据访问方法是Linq to SQL或是Entity Framework。我在www.asp.net的论坛上看到很多关于讨论是否有其他替代的数据库访问方式,回答 是:当然有。这篇文章就讲述了使用Ado.Net作为数据访问层来实现一个典型的增删查改程序。

由于是以练习作为目的,那我就不妨借用Spaanjaar’s 的N层构架文章(Building Layered Web Applications with Microsoft ASP.NET 2.0.)的构架方式。我强烈推荐你阅读他 的系列文章,如果嫌太长起码也得看完前两部分,这样就能对N-Layer构架有个基本的认识。N-Layer构架的三个关键层分别为:业务对象层,业务逻 辑层和数据访问层。而其数据访问层会几乎不加改变的包含在本文的MVC项目中,Spaanjaar的文件详细描述了各个层是如何组织的。这篇文章仅仅讲述 各个层所扮演的角色,但是不会深入到代码的细节中。

首先,我们来看Imar提供的程序,这是一个具有典型增删查改的程序,这个程序允许用户管理联系人,包括联系人的地址,电话,email。它能增,删, 查,改任何实体。

程序内包括的实体有:ContactPersons, PhoneNumbers, Addresses EmailAddresses.他们都隶属于程序的业务对象(BO)层。上述的每一个类都包含可以获取或者赋值的属性,但并不包含任何方法。而所有方法存 放于业务逻辑层(BLL)中的对应类中。在业务对象层和业务逻辑层的实体和实体manger是一对一的关系,在业务逻辑层中类包含的方法都会返回业务对象 层(BO)的实例,或是实例集合,或者保存实例(更新或是添加),或是删除实例。业务逻辑层(BLL)中也可以包含一些业务规则验证,安全性检查的代码。 但在本篇文章为了简便起见,就不添加这些了。如果你对业务规则和安全性有兴趣的话,可以去看Imar文章的6 part series

最后一层是数据访问层(DAL),同样,DAL层的类也和业务逻辑层(BLL)内的类有着一对一的关系,在BLL层的类中会调用相关DAL层中的方法。而 在这些层中,只有DAL层需要知道利用什么技术(linq,entity framework..)保存业务实体。在本例中,使用SQL Server Express数据库和Ado.net。而这样分层的思想是如果你需要更换数据源(XML,oracle,更或者是Web Service甚至是Linq to Sql或者其他ORM框架),因为DAL层给BLL层暴漏的方法的签名是一致的,所以只需要更换DAL层即可。而为了保证所有DAL的实现有着同样的签 名,则利用接口即可。但我想或许是未来帖子中讨论的话题了吧。

MVC构架

已经有很多优秀的文章中已经探讨了MVC程序的构架,所以本篇文章就不再累述相关细节了。如果想要了解更多,我推荐访问Asp.net MVC官方站点.简单二代说,M代表Model,也是包含 BO,BLL,DAL的地方,V代表View,也是UI相关开发的部分,或者说是用户看到的部分,C是Controller的简写,也是控制用户请求与程 序回复的部分。如果用户点击了一个指向特定地址的按钮,请求会和Controller的Action(类的方法)进行匹配,而Action负责处理请求, 并返回响应。通常情况下是一个新的View,或者是更新现有的View。

下面用Visual Studio创建一个MVC应用程序并删除默认的View和Controller,然后将Imar的程序中的Bo,Bll和DAL复制到这个MVC程序的 Model内,我还复制了响应的数据库文件和Style.css。

我还做了 一些其他的修改,把数据库连接字符串添加到Web.Config中。除此之外,我还将复制过来的代码的命名空间做了响应的调整并把DAL层的代码升级到了 3.0.虽然这并不是必须的。做完这些,我按Ctrl+Shift+F5来测试是否编译成功。——————

Controller

我 添加了4个Controller(Visual Studio附带的默认Controller已经被删除),和四个实体类相匹配。它们分别为:ContactController, PhoneController, AddressController and EmailController。

每个 Controller都含有四个Action:List, Add, Edit and Delete。首先需要在Global.exe中为这些Action注册这些路由。

public static void RegisterRoutes(RouteCollection routes)
{
  routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

  routes.MapRoute(
      "Default",                                              
      "{controller}/{action}/{id}",                           
      new { controller = "Contact", action = "List", id = " " }  
  );
}

默认的View会显示所有联系人,BLL层中的ContactPersonManager类内的GetList()方法会获取所有联系人的数 据,响应的List() Action代码如下:

public ActionResult List()
{
  var model = ContactPersonManager.GetList();
  return View(model);
}

强类型View

整个实例代码中,我都会使用强类型的View,因为这样不仅可以使用intellisense(智能提示)的好处,还不必依赖于使用String作为索引 值的ViewData。为了让代码更加有序,我添加了一些命名空间到web.config中的<namespaces>节点,这些都是我用来 替换Imar代码的方式:

<namespaces>
<add namespace="System.Web.Mvc"/>
<add namespace="System.Web.Mvc.Ajax"/>
<add namespace="System.Web.Mvc.Html"/>
<add namespace="System.Web.Routing"/>
<add namespace="System.Linq"/>
<add namespace="System.Collections.Generic"/>
<add namespace="ContactManagerMVC.Views.ViewModels"/>
<add namespace="ContactManagerMVC.Models.BusinessObject"/>
<add namespace="ContactManagerMVC.Models.BusinessObject.Collections"/>
<add namespace="ContactManagerMVC.Models.BusinessLogic"/>
<add namespace="ContactManagerMVC.Models.DataAccess"/>
<add namespace="ContactManagerMVC.Models.Enums"/>
</namespaces>

上面代码意味着我可以在程序的任何地方访问上述命名空间。上面GetList()方法返回的类型是ContactPersonList,这个类型是 ContactPerson对象的集合,在显示List的View中的Page声明如下:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
    Inherits="System.Web.Mvc.ViewPage<ContactPersonList>" %>

我想你已经注意到了我使用了MasterPage,利用MasterPage,我借用了从Imar实例代码中的Css文件。用于显示 ContactPerson对象的HTML如下:

<table class="table">
    <tr>
      <th scope="col">Id</th>
      <th scope="col">Full Name</th>
      <th scope="col">Date of Birth</th>
      <th scope="col">Type</th>
      <th scope="col">&nbsp;</th>
      <th scope="col">&nbsp;</th>
      <th scope="col">&nbsp;</th>
      <th scope="col">&nbsp;</th>
      <th scope="col">&nbsp;</th>  
    </tr>
    <%
      if (Model != null)
      {
        foreach (var person in Model)
        {%>
    <tr>
      <td><%= person.Id %></td>
      <td><%= person.FullName %></td>
      <td><%= person.DateOfBirth.ToString("d") %></td>
      <td><%= person.Type %></td>
      <td title="address/list" class="link">Addresses</td>
      <td title="email/list" class="link">Email</td>
      <td title="phone/list" class="link">Phone Numbers</td>
      <td title="contact/edit" class="link">Edit</td>
      <td title="contact/delete" class="link">Delete</td>
    </tr>
    <%
        }
      }else{%>
    <tr>
      <td colspan="9">No Contacts Yet</td>
    </tr>  
     <% }%>
  </table>

我想你已经能发现强类型View的好处了吧,Model的类型是ContactPersonList,所以你可以任意使用 ContactPersonList的属性而不用强制转换,而强制转换错误只有在运行时才能被发现,所以这样省了不少事。

在做Html时,我小小的作弊了一下,我本可以使用vs对list自动生成的view,但我没有。因为我需要和Imar的css文件相匹配的html.所 以我运行了imar的程序,在浏览器中查看源代码,并把生成的html复制下来,imar使用GridView来生成列表,所以CSS会在运行时嵌入到 html代码中。我将这些css代码移到一个外部的css文件中。并给<th>和<td>元素添加了一些额外样式,你可以在代码 的下载链接中找到。

我还未一些表格的单元格添加了title属性。这些包含了访问其他action的链接。我希望用户在浏览电话本或者编辑或者删除电话本时页面不会post back,换句话说,我希望我的网站是ajax的。而title属性就发挥作用了。而“。link”这个css选择符可以让普通的文本看起来像超链接,也 就是有下划线和鼠标hover时出现小手。

JQuery AJax

在我们深入实现ajax功能的相关代码之前,下面3行代码是需要加入到html中:

<input type="button" id="addContact" name="addContact" value="Add Contact" />
<div id="details"></div>
<div id="dialog" title="Confirmation Required">Are you sure about this?</div>

第一行代码的作用是一个允许用户添加新联系人的按钮,第二行代码是一个空div,方便后面显示信息,第三行代码是JQuery modal 提示验证对话框的一部分,用于提示用户是否删除一条记录。

还需要在页面中添加3个js文件,第一个是主要的jQuery文件,其他两个分别是jQuery UI的核心js,以及date picker和modal dialog部分的js

<script src="../../Scripts/jquery-1.3.2.min.js" type="text/javascript"></script>
<script src="../../Scripts/ui.core.min.js" type="text/javascript"></script>
<script src="../../Scripts/jquery-ui.min.js" type="text/javascript"></script>

下面是我们生成后的列表显示视图以及完全的js代码:

<script type="text/javascript">
  $(function() {
    // row colours
    $('tr:even').css('background-color', '#EFF3FB');
    $('tr:odd').css('background-color', '#FFFFFF');
    // selected row managment
    $('tr').click(function() {
      $('tr').each(function() {
        $(this).removeClass('SelectedRowStyle');
      });
      $(this).addClass('SelectedRowStyle');
    }); 
    // hide the dialog div
    $('#dialog').hide();
    // set up ajax to prevent caching of results in IE
    $.ajaxSetup({ cache: false });
    // add an onclick handler to items with the "link" css class
    $('.link').live('click', function(event) {
      var id = $.trim($('td:first', $(this).parents('tr')).text());
      var loc = $(this).attr('title');
      // check to ensure the link is not a delete link
      if (loc.lastIndexOf('delete') == -1) {
        $.get(loc + '/' + id, function(data) {
          $('#details').html(data);
        });
      // if it is, show the modal dialog   
      } else {
        $('#dialog').dialog({
          buttons: {
            'Confirm': function() {
              window.location.href = loc + '/' + id;
            },
            'Cancel': function() {
              $(this).dialog('close');
            }
          }
        }); 
        $('#dialog').dialog('open');
        }
      }); 
      // add an onclick event handler to the add contact button
      $('#addContact').click(function() {
        $.get('Contact/Add', function(data) {
          $('#details').html(data);
        });
      }); 
    });
</script>

上面代码看起来让人望而却步,但实际上,使用jQuery,这非常简单。并且在上面代码中我多处加入了注释,代码一开始是用js代码是替换了数据源 控件默认呈现出来的隔行变色的颜色。然后我加入了使点击行点击时可以变色的代码。然后下面的代码是防止IE对页面进行缓存。如果不禁止了IE缓存,你会为 执行编辑或者删除以后,数据库改变了但页面却没有发生变化而撞墙的。

接下来的代码更有趣了,依靠live()方法可以确$选择器中所有匹配的元素都被附加了相应的事件,无论元素当前是否存在于页面中。比如说,当你点击了 Addresses链接,相应的结果就会在另一个表中出现:

上图中可以看出表中包含编辑和删除链接,如果不使用live()方法,相应的链接就不会被附加事件。所有具有class为link的元素都会被委派上 click事件。这个事件会首先获取到当前条目的id.以ContactPerson表为例,这个id就是ContactPersonId.在相应的子表 中,则id会是电话号码或者是email地址。这些都是需要传递给controller action作为参数进行编辑,删除,或者显示的。

你现在可以看出为什么我为每个单元格加上title属性了吧。title属性包含相关的route信息,而完全的url则把id附加到route作为 url.然后,上面js会做一个检查,来看route信息内是否包含delete,如果包含delelte,则弹出一个对话框来提示用户是否要删除。看, 是不是很简单?

最后,代码会为添加联系人按钮添加click事件,在文章的下部分我们再进行讨论

添加联系人以及自定义View Models

使用ASP.NET添加一条记录时,通常的做法是提供一个包含一系列输入框的form.对于ContactPerson类的大多属性来说,都是可以直接赋 值的,比如:姓,名字,生日。但是其中有一个属性却不能直接赋值–Type属性,这个值是从PersonType.cs(朋友,同事等)中的枚举类型中 选择出来的.这意味着用户只能从有限的几种选项中选择一种。可以用DropDwonList来实现,但是ContactPerson对象并不包含枚举类型 的所有选项,所以我们只能提供一个自定义版本的ContactPerson类传递给View.这也是为什么要用到自定义View Model

我看过很多关于在程序的哪里放置View Models的讨论,有些人认为自定义View Models是Model的一部分,但是我认为它和View紧密相关,View Models并不是MVC程序必不可少的一部分,也不能服用,所以它不应该放到Model中区。我把所有的View Model放入一个创建的ViewModel文件夹下,ContactPersonViewModel.cs的源代码如下:

using System;
using System.Collections.Generic;
using System.Web.Mvc;

namespace ContactManagerMVC.Views.ViewModels
{
  public class ContactPersonViewModel
  {
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string MiddleName { get; set; }
    public string LastName { get; set; }
    public DateTime DateOfBirth { get; set; }
    public IEnumerable<SelectListItem> Type { get; set; }
  }
}

看上面类的最后一个属性,我将这个type属性设置成IEnumerable<SelectListItem>类型,这样就可以将Type绑 定到View中的Html.DropDownList了。

对应的,也要在Controller中添加2个action,第一个action使用AcceptVerbs(HttpVerbs.Get)标签,第二个 action使用AcceptVerbs(HttpVerbs.Post)标签,第一个方法用于从服务器向客户端传值,第一个方法处理从form提交的数 据。

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Add()
{
  var personTypes = Enum.GetValues(typeof (PersonType))
    .Cast<PersonType>()
    .Select(p => new
                   {
                     ID = p, Name = p.ToString()
                   });
  var model = new ContactPersonViewModel
                {
                  Type = new SelectList(personTypes, "ID", "Name")
                };
  return PartialView(model);
}


[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Add(ContactPerson person)
{
  ContactPersonManager.Save(person);
  return RedirectToAction("List");
}

上面第一个action的前几行代码将ContactType枚举类型转换成数组,数组中的每一个元素都是一个包含id和name属性的匿名对象,id是 枚举值,Name是和对应枚举匹配的constant值,ContactPersonViewModel然后进行初始化并且Type属性被赋值相应的类 型。我使用Partial View来添加联系人和使用ContactPersonViewModel类型的强类型,对应View部分的代码如下:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ContactPersonViewModel>" %>

<script type="text/javascript">
  $(function() {
  $('#DateOfBirth').datepicker({ dateFormat: 'yy/mm/dd' });
  });
</script>

<% using (Html.BeginForm("Add", "Contact", FormMethod.Post)) {%>
      <table>
        <tr>
          <td class="LabelCell">Name</td>
          <td><%= Html.TextBox("FirstName") %></td>
        </tr>
        <tr>
          <td class="LabelCell">Middle Name</td>
          <td><%= Html.TextBox("MiddleName") %></td>
        </tr>
        <tr>v
          <td class="LabelCell">Last Name</td>
          <td><%= Html.TextBox("LastName") %></td>
        </tr>
        <tr>
          <td class="LabelCell">Date of Birth</td>
          <td><%= Html.TextBox("DateOfBirth", String.Empty)%></td>
        </tr>
        <tr>
          <td class="LabelCell">Type</td>
          <td><%=Html.DropDownList("Type")%>
          </td>
        </tr>
        <tr>
          <td class="LabelCell"></td>
          <td><input type="submit" name="submit" id="submit" value="Save" /></td>
        </tr>
      </table>
<% } %>

在最上面的代码我使用了jQuery UI的Date picker插件作为DateOfBirth输入框的选择方式,而DateOfBirth的第二个参数保证这个textbox在初始状态下为空。此外,所 有的输入框对ContactPerson的对应属性名相同,这是为了确保默认的model binder不出差错,MVC还自动为ContactType枚举进行绑定。

负责响应Post请求的方法可以自动将Request.Form的值和ContactPerson对象的对应属性进行匹配,然后调用BLL层的 Save()方法,然后RedicrectToAction会让页面刷新最后调用List这个action.

编辑一个联系人

和前边一样,需要在Controller添加两个action来完成编辑,一个用于响应Get请求,另一个用于响应Post请求:

[AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Edit(int id)
    {
      var personTypes = Enum.GetValues(typeof (PersonType))
        .Cast<PersonType>()
        .Select(p => new { ID = p, Name = p.ToString() });

      var contactPerson = ContactPersonManager.GetItem(id);
      var model = new ContactPersonViewModel
                    { 
                      Id = id,
                      FirstName = contactPerson.FirstName,
                      MiddleName = contactPerson.MiddleName,
                      LastName = contactPerson.LastName,
                      DateOfBirth = contactPerson.DateOfBirth,
                      Type = new SelectList(personTypes, "ID", "Name", contactPerson.Type)
                    };
      return PartialView(model);
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Edit(ContactPerson person)
    {
      ContactPersonManager.Save(person);
      return RedirectToAction("List");
    }

我们在前面已经看到jQuery是如何调用Edit这个action并把被编辑人的id作为参数进行传递的了,然后id用于通过众所周知的bll调用 DAL从数据库将联系人的详细信息取出来。在这个过程中ContactPersonViewModel被创建来返回从数据库取出的 值,SelectList的通过三个参数构造,第一个参数是Person的类型,第三个参数是DropDownList当前选择的值

partial view的代码和Add View几乎一样:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ContactPersonViewModel>" %>

<script type="text/javascript">
  $(function() {
    $('#DateOfBirth').datepicker({dateFormat: 'yy/mm/dd'});
  });
</script>

<% using (Html.BeginForm("Edit", "Contact", FormMethod.Post)) {%> 
     <table>
        <tr>
          <td class="LabelCell">Name</td>
          <td><%= Html.TextBox("FirstName") %></td>
        </tr>
        <tr>
          <td class="LabelCell">Middle Name</td>
          <td><%= Html.TextBox("MiddleName") %></td>
        </tr>
        <tr>
          <td class="LabelCell">Last Name</td>
          <td><%= Html.TextBox("LastName") %></td>
        </tr>
        <tr>
          <td class="LabelCell">Date of Birth</td>
          <td><%= Html.TextBox("DateOfBirth", Model.DateOfBirth.ToString("yyyy/MM/dd")) %></td>
        </tr>
        <tr>
          <td class="LabelCell">Type</td>
          <td><%= Html.DropDownList("Type")%></td>
        </tr>
        <tr>
          <td class="LabelCell"><%= Html.Hidden("Id") %></td>
          <td><input type="submit" name="submit" id="submit" value="Save" /></td>
        </tr>
      </table>
<% } %>

关键不同是DateOfBirth包含一个将生日信息转换更容易识别的方式,还有在提交按钮附近添加了一个Html.Hidden(),用于保存被 编辑用户的id。当然,form的action属性肯定是和Add的View不同。在Form中可以加一个参数用于告诉Action是Add View还是Edit View,这样就能减少代码的重复,但是在示例代码中我还是把他们分成了不同的action,来让职责划分的更清晰一些。

删除一个联系人

删除action就简单多了,并且不需要与之相关的View.仅仅是重新调用List这个action,被删除的数据就不复存在了。

public ActionResult Delete(int id)
{
  ContactPersonManager.Delete(id);
  return RedirectToAction("List");
}

上面代码是我对BLL和DAL做出改变的地方,原来的ContactPersonManager.Delete()方法的参数是Person的实例,而在 DAL中,只有id作为参数。我看不出传递整个对象给BLL,但BLL却只传递对象的唯一ID给DAL的意义所在,所以我将BLL的代码改成接受int类 型的参数,这样写起来会更简单。

当删除链接被点击时,调用jQuery的confirmation modal dialog:

如果点击取消,则什么事也不发生,如果点击确认,则链接就会指向响应的delete action.

管理集合

所有的集合–PhoneNumberList,EmailAddressLIst,AddressList被管理的方式如出一辙。所以,我仅仅挑选 EmailAddressLIst作为示例来说明方法,你可以下载示例代码来看其他部分。

首先,我们来看显示被选择联系人的email地址,这包含了controller中List这个Action:

public ActionResult List(int id)
{
  var model = new EmailAddressListViewModel
                {
                  EmailAddresses = EmailAddressManager.GetList(id),
                  ContactPersonId = id
                };
  return PartialView(model);
}

上面方法使用联系人的id作为联系人,并且返回一个自定义View Model—EmailAddressListViewModel.这也是将联系人的id传到View的方法:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<EmailAddressListViewModel>" %>
<script type="text/javascript">
  $(function() {
    $('#add').click(function() {
      $.get('Email/Add/<%= Model.ContactPersonId %>', function(data) {
        $('#details').html(data);
      });
    });
  });
</script>
<table class="table">
   <tr>
     <th scope="col">Contact Person Id</th>
     <th scope="col">Email</th>
     <th scope="col">Type</th>
     <th scope="col">&nbsp;</th>
     <th scope="col">&nbsp;</th>
   </tr>
   <%if(Model.EmailAddresses != null)
     {foreach (var email in Model.EmailAddresses) {%>
   <tr>
     <td><%= email.Id %></td>
     <td><%= email.Email %></td>
     <td><%= email.Type %></td>
     <td title="email/edit" class="link">Edit</td>
     <td title="email/delete" class="link">Delete</td>
   </tr>
        <%}
    }else
 {%>
   <tr>
     <td colspan="9">No email addresses for this contact</td>
   </tr>
 <%}%>
</table>
<input type="button" name="add" value="Add Email" id="add" />

你可以看出添加方法需要ContactPersonID作为参数,我们需要确保可以添加新的联系人到响应的联系人列表。编辑和删除方法也是如此–id参 数通过url传递到相关action.而所有的单元格都有title属性,这样就可以使用前面部署的live()方法:

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Add(int id)
{
  var contactTypes = Enum.GetValues(typeof(ContactType))
    .Cast<ContactType>()
    .Select(c => new
    {
      Id = c,
      Name = c.ToString()
    });
  var model = new EmailAddressViewModel
                {
                  ContactPersonId = id,
                  Type = new SelectList(contactTypes, "ID", "Name")
                };
  return PartialView("Add", model);
}


[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Add(EmailAddress emailAddress)
{
  emailAddress.Id = -1;
  EmailAddressManager.Save(emailAddress);
  return RedirectToAction("List", new {id = emailAddress.ContactPersonId});
}

创建自定义View Model存在的目的是为了对现有的EmailAddress进行添加或编辑,这包括一些绑定 IEnumerable<SelectListItem>集合到Type dropdown的属性,上面两个Add不同之处在于他们的返回不同,第一个返回partial view,第二个返回List这个action.

集合中的每一个选在在一开始都会将id设为-1,这是为了保证符合”Upsert”存储过程的要求,因为Imar对于添加和删除使用的是同一个存储过程, 如果不设置成-1,则当前新建的联系人会更新掉数据库中的联系人。如果你想了解更多,请看他的文章。下面是添加email address的partial view:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<EmailAddressViewModel>" %>

<script type="text/javascript">
  $(function() {
    $('#save').click(function() {
      $.ajax({
        type: "POST",
        url: $("#AddEmail").attr('action'),
        data: $("#AddEmail").serialize(),
        dataType: "text/plain",
        success: function(response) {
          $("#details").html(response);
        }
      });
    });
  });
</script>

<% using(Html.BeginForm("Add", "Email", FormMethod.Post, new { id = "AddEmail" })) {%>
<table class="table">
<tr>
  <td>Email:</td>
  <td><%= Html.TextBox("Email")%></td>
</tr>
<tr>
  <td>Type:</td>
  <td><%= Html.DropDownList("Type") %></td>
</tr>
<tr>
  <td><%= Html.Hidden("ContactPersonId") %></td>
  <td><input type="button" name="save" id="save" value="Save" /></td>
</tr>
</table>
<% } %>

上面jQuery代码负责通过Ajax提交form.jQuery给html button(而不是input type=”submit”)附加事件,然后将页面中的内容序列化并通过post 请求发送到使用合适AcceptVerbs标签修饰的名为add()的action.

编辑和删除EmailAddress对象

编辑EmailAddress对象所涉及的action和前面提到的很相似,仍然是两个action,一个用于处理get请求,另一个用于处理post请 求:

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Edit(int id)
{
  var emailAddress = EmailAddressManager.GetItem(id);
  var contactTypes = Enum.GetValues(typeof(ContactType))
    .Cast<ContactType>()
    .Select(c => new
    {
      Id = c,
      Name = c.ToString()
    });
  var model = new EmailAddressViewModel
  {
    Type = new SelectList(contactTypes, "ID", "Name", emailAddress.Type),
    Email = emailAddress.Email,
    ContactPersonId = emailAddress.ContactPersonId,
    Id = emailAddress.Id
  };
  return View(model);
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(EmailAddress emailAddress)
{
  EmailAddressManager.Save(emailAddress);
  return RedirectToAction("List", "Email", new { id = emailAddress.ContactPersonId });
}

下面的partial View代码现在应该很熟悉了吧:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<EmailAddressViewModel>" %>

<script type="text/javascript">
  $(function() {
    $('#save').click(function() {
      $.ajax({
        type: "POST",
        url: $("#EditEmail").attr('action'),
        data: $("#EditEmail").serialize(),
        dataType: "text/plain",
        success: function(response) {
          $("#details").html(response);
        }
      });
    });
  });
</script>

<% using(Html.BeginForm("Edit", "Email", FormMethod.Post, new { id = "EditEmail" })) {%>
<table class="table">
<tr>
  <td>Email:</td>
  <td><%= Html.TextBox("Email")%></td>
</tr>
<tr>
  <td>Type:</td>
  <td><%= Html.DropDownList("Type") %></td>
</tr>
<tr>
  <td><%= Html.Hidden("ContactPersonId") %><%= Html.Hidden("Id") %></td>
  <td><input type="button" name="save" id="save" value="Save" /></td>
</tr>
</table>

上面代码仍然和Add View很像,除了包含EmailAddress.Id的html隐藏域,这是为了保证正确的email地址被更新,而Delete action就不用过多解释了吧。

public ActionResult Delete(int id)
{
  EmailAddressManager.Delete(id);
  return RedirectToAction("List", "Contact");
}

总结

这篇练习的目的是为了证明ASP.NET MVC在没有Linq To Sql和Entity framework的情况下依然可以很完美的使用。文章使用了现有分层结构良好的Asp.net 2.0 form程序,还有可重用的,business Objects层,bussiness logic层以及Data Access 层。而DAL层使用Ado.net调用Sql Server的存储过程来实现。

顺带的,我还展示了如何使用强类型的View Models和简洁的jQuery来让UI体验更上一层。这个程序并不是完美的,也不是用于真实世界。程序中View部分,以及混合编辑和删除的 action都还有很大的空间可以重构提升,程序还缺少验证用户输入的手段,所有的删除操作都会返回页面本身。而更好的情况应该是显示删除后用一个子表来 显示删除后的内容,这需要将ContactPersonId作为参数传递到相关action,这也是很好实现的。

—————————————————

原文链接:http://www.mikesdotnetting.com/Article/132/ASP.NET-MVC-is-not-all-about-Linq-to-SQL

translated by CareySon

[转载]LECCO SQL Expert工具之优化sql语句

mikel阅读(1245)

[转载]LECCO SQL Expert工具之优化sql语句

程序开发,特别是web开发,对性能的要求比较高,在线列表信息要快又要占用减少服务器资源,在大众化的网络中可以已较快的速度读取数 据,在程序方面优 化,主要只书写代 码质量,这里对代 码优化不进行讨论,此 篇主是针对数据库查询语句的优化.

编程开手或DBA通过自己的多年的经验可能重写性能低下 的SQL语句,但对于我这样菜鸟来说,写出高性能的SQL有些困难.…,不用怕,我现在介绍个工具, LECCO SQL Expert,这 个工具的下载及使用帮助让大家baidu,就有一大堆.这里不提供下载链接了.

好了,开始我们今天的话题:

1.打开软件,登录后,出现以下界面.

找到我要进行测试的sql语句,如下:

SELECT dbo.合同申请表.ID,

dbo.合同申请表.ApplyUser,

dbo.UserTable.UserName,

dbo.合同申请表.ApplyType,

dbo.合同申请表.Name,

dbo.合同申请表.Object,

dbo.合同申请表.Intention,

dbo.合同申请表.Delivery,

dbo.合同申请表.Howmuch,

dbo.合同申请表.Payment,

dbo.合同申请表.Transit,

dbo.合同申请表.Others,

dbo.合同申请表.Memo,

dbo.合同申请表.Status,

dbo.合同状态表.title AS StatusTitle,

dbo.合同申请表.Attime,

dbo.UserTable.Phone,

dbo.合同申请表.Serial

FROM dbo.合同申请表,

dbo.UserTable,

dbo.合同状态表

WHERE dbo.合同申请表.status = @nStatus

and dbo.合同申请表.ApplyType = @cType

and dbo.合同申请表.WorkerID = @nOfficer

AND dbo.合同申请表.Status = dbo.合同状态表.id

AND dbo.合同申请表.ApplyUser = dbo.UserTable.ID

2.将其copyLECCO SQL Expert sql编辑器中,如图:


3.
点击工具栏的优化按钮,输入参数(sql语句用的是变量@nStatus, @cType, @nOfficer),如图:

4.输入相应的变量值,点击确定,进行优化语句查询:如图:



找到5个可选的执 行计划.,批运行 这5sql,看运行时间最短的,其是最好的优化sql

5.批运行,并显示这5个运行时间

结果显示:sql4是最优的,其语句为:

SELECT dbo.合同申请表.ID,

dbo. 合同申请表.ApplyUser,

dbo.UserTable.UserName,

dbo. 合同申请表.ApplyType,

dbo. 合同申请表.Name,

dbo. 合同申请表.Object,

dbo. 合同申请表.Intention,

dbo. 合同申请表.Delivery,

dbo. 合同申请表.Howmuch,

dbo. 合同申请表.Payment,

dbo. 合同申请表.Transit,

dbo. 合同申请表.Others,

dbo. 合同申请表.Memo,

dbo. 合同申请表.Status,

dbo. 合同状态表.title AS StatusTitle,

dbo. 合同申请表.Attime,

dbo.UserTable.Phone,

dbo. 合同申请表.Serial

FROM dbo.合同申请表

INNER JOIN dbo.合同状态表

ON dbo.合同申请表.Status = dbo.合同状态表.id

INNER JOIN dbo.UserTable

ON dbo.合同申请表.ApplyUser = dbo.UserTable.ID

WHERE dbo.合同申请表.status = @nStatus

and dbo.合同申请表.ApplyType = @cType

and dbo.合同申请表.WorkerID = @nOfficer

OPTION (FORCE ORDER)

好了,本篇只是对LECCO SQL Expert软件进行简 单应用实例说明.ok,这次就到这了.

[转载]LECCO SQL Expert Pro for SQL Server提供下载

mikel阅读(1200)

[转载]LECCO SQL Expert Pro for SQL Server提供下载

写出专家级的SQL语句

LECCO SQL Expert的出现,使SQL的优化变得极其简单,只要能够写出SQL语句,它就能帮用户找到最好性能的写法。LECCO SQL Expert不仅能在很短的时间内找到所有可能的优化方案,而且能够通过实际测试,确定最有效的优化方案。同以往的数据库优化手段相比较,LECCO SQL Expert将数据库优化技术带到了一个崭新的技术高度,依赖人的经验、耗费大量时间、受人的思维束缚的数据库优化手段已经被高效、省时且准确的自动优化 软件所取代了。通过内建的“LECCO小助手”的帮助,即使是SQL的开发新手,也能快速且简单地写出专家级的SQL语句。
下载

[转载]企业定制软件开发的两个核心问题 - TAOWEN的一些个人记录 - 博客园

mikel阅读(1439)

[转载]企业定制软件开发的两个核心问题 – TAOWEN的一些个人记录 – 博客园.

企业定制软件开发不是计算机科学,需要解决的不是编译原理也不是组合数学。那么,企业定制软件开发的核心问题是什么?

越来越感觉到,从事一个领域不需要有特别深刻的理解,但起码要知道做这个领域的事情,需要解决的核心问题是什么。比如说,开发C/S结构软件,状态 同步(C/S状态同步以及窗口之间的状态同步)就是核心问题之一,而开发B/S结构的软件,状态同步就不是那么核心的问题。如果事先知道需要有这些核心问 题需要考虑,在日常应对接踵而来的具体的事务的时候,就能够把解决问题的层次抬到更宏观的层面。

目前而言,个人感觉企业定制软件开发的核心问题有两个:

1、如何保证所有参与者(包括客户在内的开发团队,以及最终用户)的沟通强度,使其能够满足完成开发目标的需要

2、如何管理企业定制带来的软件自身内在的高复杂度,使得复杂度不会超过团队的维护能力范围

在前面一篇介绍组 织的Blog中,谈到了核心问题中的第一个,沟通强度问题。团队而言,刨去个人能力,最重要的就是人与人之间的沟通。没有好的沟通,即便团队的个 体能力都超级强,也无法形成合力。但只要有好的沟通,至少可以做到人尽其用。假设一个标准人的生产力是1/day。某些特别强的人可以达到3/day。但 是如果需要达到10/day的生产力才能在市场允许的时间下完成项目,那么就无法用一个人来完成之间事情,所以才会有团队存在的必要。但是有两个标准人, 并不会达到1+1=2/day的生产力。可能只有1.5。有三个标准人,更加不会达到1+1+1=3/day的生产力,很有可能有1.8。那么是什么制约 了团队的整体生产力,那就是沟通。当两个相关联的任务A和B是一个人做的时候,关于任务A的知识存在于左脑,关于任务B的知识存在于右脑。那么结合两个任 务的知识把其组装起来的沟通效率就是大脑内部的电信号的速度。但是如何任务A是由Dev甲完成的,任务B是由Dev乙完成的,那么整合两个任务的效率就受 到人嘴皮的震动频率的约束,受到表达能力的约束,受到理解能力的约束。有人研究过,两个人即便是面对面,传输的比特率也要低于最早的拨号式Modem。更 不用说,有的时候开发团队是分布式的。在无法看到表情,肢体语言,无法共享白板,只能在越洋电话里听到声音,而且其中还有一方是非工作时间,在这种情况 下,1+1几乎很难达到1/day的生产力。什么是高效的团队,就是能够让1+1的值尽可能大的团队。如何变得高效,让沟通变得更加高效。怎么样让沟通变 得高效高强度?这就是我们要处理的核心问题。

第一个问题可以应用于所有的人的团队行为之中。人只要聚集成群,就会有沟通问题。所谓,有人的地方就有江湖。第二个问题则特定于企业定制软件开发。 对于互联应用开发,也许复杂度的管理是其次的,最需要关注的是大用户量下的可扩展性。但是对于企业定制软件开发,由于业务自身的复杂度,导致了定制软件的 复杂度。特别是业务的组合,导致的组合复杂性。假设在理想情况下,一个系统可以分解为模块A,B,C,其复杂度都是2。在复杂度管理良好的情况下 ,这些模块是被明确划分的,要理解A,只需要关注A,以及B与C少量与A交互的部分,也许理解的复杂度只是2 + 0.5.。但是在复杂度没有管理的情况下,所有的“逻辑”(也就是复杂度)都是随意地放置的。那么也就是没法办法保证,读A的逻辑只需要关注A,甚至这个 A都是不存在的,你看到的知识一个系统,包含了A,B,C的功能,是完整的一块。这个时候要真正了解这个系统行为,可能就需要2 * 2 * 2 = 8这么高的代价了。随着模块(变量,方法,类,包,模块,Bundle)的增加,我们需要同时理解的东西也在不断增加。不去可以地管理复杂度,很有可能, 我们需要了解一块功能,就需要把所有的代码都去阅读一遍。或者说,改动了一个方法,使得整个项目都需要重新被测试,因为没有地方是可以被信任的。如何管理 复杂度?这就是我们要处理的核心问题。

有意思的事情是,这两个核心问题是重叠的。把人,角色等同于类,接口,都抽象地看成点。把沟通理解为人与人之间的联系问题。把复杂度也理解为类与类 的依赖问题。那么沟通问题和复杂度的管理问题都是如何把这些点联上线,组成一个高效的图的问题。这个图,就是一张关于“依赖”的图。人与人之间的依赖,类 与类之间的依赖,包与包之间的依赖。依赖的另外的一个名字就是Coupling。而我们追求的就是Cohesion。Coupling(耦合)/ Cohesion(内聚)这两个词的妙处在于,明白的人根据自己的经验,一看就点头。不明白的人,由于没有对应的经验,无论怎么解释,都是摸不着头脑。正 是因为其“妙不可言”性,所以我可以说这两个词就是所有问题的答案(你也无法反驳)。但至少我们可以知道企业定制软件开发的核心问题其实就是一个:就是管 理好人与人之间的Dependency,包与包之间的Dependency,使得信息可以在高度依赖的人与人之间快速传递(强调Coupling带来的消 息传递的效率),而理解又可以局限在高度内聚的模块内部(强调Cohesion带来的维护便利),但同时又不能让某人过度被依赖倒置工作过劳死了,被依赖 得越多要求其体能越好,对于包的内聚也一样,高内聚做到极致就是最小的编译单元(类?),又会导致包的粒度过小,使得包的数量变得巨大,失去了维护的便利 性。我们需要做的,就是在To Depend or Not To Depend中,根据场景作出取舍。

那么抽象而言,无论是解决沟通问题还是复杂度问题都可以归纳为:

1、设置目标指标

2、度量现有的指标,观测现有的依赖图

3、做出依赖图的调整计划,并执行

4、观察指标的变化

5、重复步骤3,4,直到目标达到

但是问题是:

1、如何度量指标?沟通的效率?代码的质量?都很能度量。

2、如何观测现有的依赖图?包的依赖还可以观测,但是团队的协作是比较难观测的。

3、如何对依赖图做调整?重构?Change Agent?人不比代码那么容易改变。

4、如果指标不是简单数字,怎么比较?怎么知道指标是朝着目标发生变化?

这四个问题,几乎没有硬的科学问题。管理复杂系统的复杂度,可能是一门硬的科学。但是夹杂了人的因素的企业定制软件开发,一定不是一门硬的科学。那 么,数学公式不是这些问题的答案。那么该朝哪个方向努力?

TO BE CONTINUED

[原创]SWFUpload能上传视频不能提交表单的问题解决办法

mikel阅读(1312)

最近在做模板时候发现,原来能用的swfupload上传视频的功能,现在不能用了?!但是视频文件上传完成后不能提交表单信息,总是提示:“Error submit form”

开始以为是代码调用的问题,去除了不必要的js代码,问题依然存在!swfupload上传的流程可以通过调用代码分析出来,代码如下:
……..

upload_progress_handler: uploadProgress, //上传进度显示处理函数uploadProgress
upload_error_handler: uploadError,//上传错误处理函数uploadError
upload_success_handler: uploadSuccess,//上传成功处理函数uploadSuccess
upload_complete_handler: uploadComplete,//上传完成处理函数uploadComplete 问题应该就出在这个函数中
………..

于是打开handler.js中查看uploadComplete的代码如下:

function uploadComplete(file) {
try {
if (this.customSettings.upload_successful) {

this.setButtonDisabled(true);
uploadDone(); //这个就是完成上传文件后的处理函数了
} else {
file.id = “singlefile”;
var progress = new FileProgress(file, this.customSettings.progress_target);
progress.setError();
progress.setStatus(“File rejected”);
progress.toggleCancel(false);

var txtFileName = document.getElementById(“txtFileName”);
txtFileName.value = “”;
//validateForm();

alert(“There was a problem with the upload.\nThe server did not accept it.”);
}
} catch (e) {
}
}

继续查看uploadDone()函数的代码,代码如下:

// Called by the queue complete handler to submit the form
function uploadDone() {
try {

document.forms[1].submit();//罪魁祸首在此

居然遍历取当前页面的forms的第二个进行提交,不管是不是要提交的表单都提交

没有找到时候就提示:Error submitting form

}

catch (ex) {
alert(“Error submitting form”);
}
}

问题找到了于是准备进行如下修改:

1.swfupload引用时的customSettings设置上传文件完成后要提交的form的Id:upload_form

2.修改uploadComplete中读取customSettings设置的upload_form,传递给uploadDone()函数

3.修改uploadDone函数加入参数id,用于传递要提交的表单的ID进行提交操作。

具体代码如下:

1.设置代码

custom_settings : {
progress_target : “fsUploadProgress”,
upload_successful : false,
upload_form:”Upload”
},

2.uploadComplete()

if (this.customSettings.upload_successful) {

this.setButtonDisabled(true);
uploadDone(this.customSettings.upload_form);
} else {

3.uploadDone()

function uploadDone(id) {
try {

document.getElementById(id).submit();
}

catch (ex) {
alert(“Error submitting form”);
}
}