[转载]使用sqlite扩展,避免在sql语句中使用不易看懂和难以维护的数字或名称缩写

mikel阅读(856)

[转载]使用sqlite扩展,避免在sql语句中使用不易看懂和难以维护的数字或名称缩写 – 达叔 – 博客园.

本人最近使用C/C++操作SQLite数据库进行GIS数据转换时,感觉SQLite 有个不方便的地方就是不能在SQL中定义枚举量和常量。相比C/C++就提供了宏定义,枚举和常量定义来帮助程序员避免在代码使用无意义的数字。本人操作 的GIS数据库中有很多枚举量,如道路表中道路类型列就是枚举量:0表示高速,1表示国道,2表示省道,3表示县道等。如果不能在sql中定义这些枚举 量,那么在根据道路类型选择道路时我们就必须在sql中使用数字,如:选出所有高速的sql将是select * from road_table where road_type = 0,这种sql太恶心了,你根本不知道0代表什么意义。

通过搜索引擎知道sqlite可以让用户自 己扩展sql函数,感觉可以使用sqlite扩展函数来解决这个问题。sqlite扩展sql函数的方法和步骤:首先按sqlite扩展函数的原型定义自 己的函数,然后使用sqlite3_create_function注册到sqlite中,然后就可以在sql中使用自己定义的函数了,就跟使用预定义好 的sqlite核心函数一样。
例如,我们可以把高速类型定义成一个扩展函数,这个函数返回0:
static void fun_ROAD_TYPE_HIGHWAY( sqlite3_context * context, int argc, sqlite3_value ** value ) { sqlite3_result_int64( context, 0 ); };
然后把它注册到sqlite中,注册名为ROAD_TYPE_HIGHWAY:
sqlite3_create_function( db, “ROAD_TYPE_HIGHWAY”, 0, SQLITE_ANY, 0, fun_ROAD_TYPE_HIGHWAY, 0, 0 )
注 册完后我们就可以在sql语句中通过注册名ROAD_TYPE_HIGHWAY来使用这个函数了,如:选出所有高速的sql可以写成select * from road_table where road_type = ROAD_TYPE_HIGHWAY(),这样是不是好多了。

由于GIS数据库中的枚举量居多,如果每个都要手工写个函数并注册,这肯定是一个超级体力活,所以用perl写了一个根据c/c++头文件生成sqlite扩展函数的小程序: http://files.cnblogs.com/adgnat/sqlitefungen.7z
程序使用方法:
1.确保安装了perl5和vc
2.下载程序并解压,
3.打开cmd.exe,并进入程序(sqlitefungen)目录

4.运行vcvars32.bat初始化VC编译环境,


5.执行自动生成脚本:perl sqlitefungen.perl mydb.h,mydb.h可以换成自己定义的头文件。


6.执行完后头文件目录下将生成一个同名的dll文件,你可以在sqlite3.exe中使用.load命令或sql中使用load_extension函数来加载该dll,加载后就可以使用dll中定义的扩展函数了。

[转载]程序员,你应该知道

mikel阅读(1122)

[转载]程序员,你应该知道_知识库_博客园.

(说明:本文转自公司知识库的一篇原创文章,作者:李瑞宽)

想象你是一个初出茅庐的小菜鸟,上头交代你做任务,你对其中的一项任务有那么一小点疑问,你怯生生地问:“我觉得这项任务说得不是很清楚,那样做行吗?为 什么呢?我觉得应该这样做更好。”项目很紧,上头头大着呢,他很不耐烦地对你说:“不要问那么多!我都考虑清楚了,只要照着做就行!”你应该怎么办呢?你 是就这样屁颠屁颠地去做了,还是还是觉得你应该知道为什么要按照他说的做,而不是按照你说的做吗? 你应该知道。而且你要一直坚持“我应该知道”这种想法,因为这种想法,会带领你走进一片新天地。你可能还意识不到,你现在的是否坚持和你后续的发展休戚相 关。坚持了,你会向着更深的层次迈进;放弃了,你就一直呆在原地,没有办法进步。

依我看来,技术人员的发展,不外乎往深度发展或者往广度发展。

什么是深度呢?就是你对技术的掌握程度,你一套扎进去,对技术有自己越来越深的理解,你从不会到会,从会到精,你会显著感觉到自己能力的提升,等到精通了技术之后,使用这项技术自然是挥洒自如,易如反掌。

什么是广度呢?就是十八般武艺,刀枪剑戟棍,.NET, JAVA, Silverlight,逮着什么就是什么,都去参上那么一腿,这个就是广度。你学习了N多技术,每次见到一种新技术新语言你就两眼放光四肢冒汗,你都要 根据它们的入门指南操作那么一次,好吧你是很踏实的人,所以你多操作了那么两次,之后你就能够很NB地在简历上面吹牛逼:“本人精通C#、C++、 JAVA、PHP、HTML、C、汇编、Python 不拉不拉不拉,神马技术神马语言对于我都是浮云!”说出去唬的别人一愣一愣的,当然,他们并不知道,你只写过这一堆语言的Hello world。

深度很奥妙,广度很有趣,你应该怎么选?

如果是二选一,毋庸置疑,你应该选择深度!为啥呢?深度是一个技术人员的核心竞争力。在这个IT从业人员多如过江之鲫的今天,IT民工、码农到处都是;本 科如狗,硕士满街走,只有博士还能抖一抖;街边随便哪个修钥匙档上面都贴着广告“200元企业建站!立等可取!”技术人员如何安身立命?

这个时候,你应该靠你的深度,你也只能靠你的深度了。道理很简单,技术人员靠什么吃饭?靠自己的脑子。从你人生到现在的经历中,你早早就依靠脑 子跟别人竞争了。从小学到初中到高中到大学,哪一次升学考试你不是靠自己的脑子将别人PK下去?你知识掌握的越牢固,学得越深入,对那一堆公式定理理解的 越深刻,你就越能够在竞争中占据有势,最终赢得求学阶段的胜利。不管现在学校扩招多厉害,名牌大学还是高质量的金字招牌,在普罗大众的眼中熠熠生辉,阿弥 陀佛。进入了社会,这样的竞争其实还是很类似的,只要你比人功夫深,你就比人香喷喷。

深度就是你的内功,它带来效率,它能够让你非常稳健地向前进。我们来看看倚天屠龙记里面对乾坤大挪移的描述:“正如要一个七八岁的小孩去挥舞百斤重的大铁 锤,锤法越是精微奥妙,越会将他自己打得头破血流,脑浆迸裂,但若舞锤者是个大力士,那便得其所哉了”。你的深度,就是你自己的力量,你要使用的技术,就 一把大铁锤,你要掌控好这把铁锤,没有深度,就等着自己被砸伤吧!深刻地掌握一样东西,不单明白了它什么时候非常适合使用,什么时候能够使用,也能明白它 什么时候最好不用。在平时的生活中你是否经常听到这样的话:“微软/Java/etc的XXX技术太难用了!我想要XXX它XXX就是搞不定!”很显然, 你碰到了大铁锤的受害者。不排除微软的确是有难用的东西,臭名昭著的就有MSN,但依据我粗浅的技术眼光,至少在开发技术这块,微软还是相当有想法的,而 且构建的东西只要摸清它的底,用起来还是颇为畅快的。

说到深度,就要提到境界。从你选择了深度这条路开始,你已经开始踏入了境界之旅。

境界是分层次的,学拳的说“招熟,懂劲,神明”,孔子说“三十而立,四十不惑,五十知天命,六十耳顺,七十为所欲为不守规矩!哦,是从心所欲不 逾矩”。这两家说的都是不同的境界层次。你在开始境界之旅中的某一个时刻,将会处在某一个特定的境界层次中。不同的境界的认识是不同的,同样的话,境界低 的人会看不到什么东西,境界高的人就能一眼看到其中的道理。即使大家都能看到其中有东西,看到的东西本身往往是不一样的。你平时有没有试过在一个问题上面 困扰很久,旁边一个人过来一句话就将它解决了?这是因为他的境界比你高出一层不止。

站在地上往下看,能看见直径1m的范围,有蚂蚁、落叶和浮土;站在4层楼顶上往下看,能看见直径100m的范围,有汽车、马路和绿化带;站在50层楼顶上 往下看,能看见直径10000m的范围,有成片的楼群,飘过的直升机和绵延的山岭。境界不同,看到的东西就不同。

境界不同,有时候交流起来是比较困难的,同样的概念,大家的理解是不一样的,虽然可能在不同的境界中他们都正确。好多东西你觉得很新奇很给力, 在更上一层的人看来,那不过是常识罢了。有人说人民日报是最真的媒体,从上面获益良多;有些老板什么电视都不看,天天就看新闻联播;而这些在愤青眼中,只 不过是党欺骗P民的谎言,令人作呕。你说谁才是对的?老板看完了,能够摸到政策的动向,赚到实实在在的money;而在同样的愤青群体眼中,他们就会觉得 这位受到喉舌毒害的愤青是正确的,就应该呕吐,唾弃它们,鄙视这些五毛党!慈禧年间,有个农妇和别人吹水:“老佛爷吃的煎饼,那可真不得了啊!至少一定得 有锅盖那么大!”殊不知连低慈禧N个档次的人,可能都已经不知道煎饼是何物了。这就是境界不同的缘故了。我现在回想起中学学习的思想政治课,开始觉得它们 不再如当初的枯燥,变得非常有道理。

技术的境界和学拳的是非常类似的,一开始你只要学会怎么用,用熟练就好,然后你开始好奇它内在是怎么实现的,开始研究它实现的机制,等你学会了 这些机制,你就懂得怎么发力了,你能够将这些机制用到其他地方,招招见血啊!再然后你通过不断的思索总结,你窥到了其中的概念和原理,你悟到了它存在的理 由,于是你就神明了,你悟到的这些道理,不单可以用到类似的应用场景中,还可以用到更深远的地方,甚至日常生活之中。譬如事务这个玩意,一开始你只知道它 是数据库里面的一个东西,你不知道它是啥,但是上头就是要求你用它。后来你知道它怎么用了,再然后你可以搞定死锁,将它用得很帅。终于有一天,你悟到了事 务原来并不是只有一种实现,它是一个概念,是一个协议,它能够达到概念和实现的分离,它可以通过二阶段提交,也可以通过补偿等方式实现,更要命的是它不单 可以通过计算机搞,还可以通过手工来搞。事务协调器可以不单只是一段代码,还可以是一封Email,一张贴在墙上的纸……那你才真正明白了事务这个玩意可 以怎么玩,然后再明白了怎么去根据这种思路去探索其他的东西。想象你去超市买东西,你先给了钱给收银的美女,然后你还没有踏出大门,你就后悔了——TMD 怎么又买了这么多用不着的东西?你对收银MM说:“不好意思咱不买了,你退钱给我吧!”收银MM又PP又通情达理,她在POS机里面将你的消费记录XX 掉,将钱退回给你,顺便还送给你一盒益达——是你的益达!这中间,就体现了一种事务的思想。你体会了这种思想,就可以一下子老了四五十岁,达到七十岁的境 界,为所欲为。计算机相关的这些东西,集人类智慧之大成,研究这个而能有所感悟,真真正正能达到境界上的提高。

境界到了最高妙之处,就会由实入虚,从术而道,这时你就悟到了如何将事物的本源跟事物的存在区分开来,你能够看清楚什么是本质,什么是表象,什么是要做到 的目标,而什么是具体的行为。你从本质的角度看过去,你就具有了一双慧眼,将这纷扰看得清清楚楚明明白白真真切切,这双慧眼就是做洞察力。

什么是本质呢?本质就是事物赖以存在的根本原因,若然没有这个原因,这个事物就没有存在的意义,就会消亡。所谓存在即合理,这个合理不是说的合 乎情理,而是说存在都是具有它深层次的根本原因的,也就是都是有本质的。不知道你有没有注意到,很多技术书籍第一章都是介绍技术出现的历史的,之前我觉得 这些罗里啰嗦,都是废话,直接翻过去直奔技术本身而去也,其实它是全书的总纲(天之道,损有余而补不足,是故虚胜实,不足胜有余……),它论述的是这种技 术的存在理由,如果没有这种理由,这种技术都没必要存在了,我们还学它干毛啊。

我们来说一个普通的项目,它的本质是什么?是它复杂纠结的需求吗?是它深奥精妙的设计吗?是它完全符合CMMI5滴水不漏的流程吗?都不是,它存在的意 义,就是为了能为客户产生价值,真真正正消除客户的痛处。本质和表象是很容易混杂的,要将东西乱七八糟混杂在一起很容易,要清清楚楚区分它们,将它们有条 有理摆放得整整齐齐是很困难的,这里快刀斩乱麻行不通,你得抽丝剥茧,一条条分得清清楚楚,不然你永远达不到透彻的境界。

本质是虚的,表象是实的。我们很难捉摸得到虚的东西,而实实在在的东西一直围绕在我们周围,所以我们很容易就能够接受它们,然后我们很容易就被这些东西迷 惑,看不到背后还存在深刻的东西。我们需要透过现象看到本质,找到它们。本质坚如磐石,相对不变,只要有存在的理由,它就一直都存在,而表象如行云流水, 花开花落,一直都在改变。就如用兵,目标是求胜,这是断然不会改变的,而行军就是兵无常势,水无常形,因时制宜,随机应变。倘若我们混淆了本质和表象,追 求的是表象的东西,由于表象千变万化,到头来疲于奔命,却又两手空空,最后郁闷得对天大骂:“TMD这技术变得太快了!老子跟不上不玩了!”。你应该知 道,凡是觉得乱的,都是因为没有得到本质。当你具有了深邃的洞察力,你得到了本质,你就能够不役于物,你就能看清变以不变,你就不会再去盲目追求具体的行 为。

当你做项目,你强调的不再是符合某个规范来做,你深深知道规范只是一种具体的行为,你关注你的目标,你能够带给客户的价值,你团队的效率。当现 在的规范不符合你的项目,你就大胆裁剪它,甚至抛弃它,而不是盲目将项目往规范上靠——只因为它是CMMI的建议,你所作的一切,都是为了你的这个目标而 努力。

具有洞察力,通晓变与不变,你不但能看清现在的情形,你还能看清今后的趋势,然后针对趋势采取措施。我们可以清楚地看到,技术日新月异,但人们 的思想变化却不大,一部道德经,几千年下来我们的思想仍然无法突破它论述的范畴,所以我们可以利用将来可能出现的新技术,结合人们不怎么改变的思想需求, 做一些东西出来——不要问我是什么东西,我想到也不会告诉你。

是不是觉得上面的这些东西都很虚很哲学?虚实只是相对的,当你达到这样的境界,你自然就会明白我说的是什么,当然,当你觉得已经知道我在说什么了,我觉得 你还是可以继续再想想,后面再来看,感觉又不一样。张三丰教张无忌太极剑的时候,连续两次教招,却无一式相同。张无忌“细细体会其中圆转如意,绵绵无尽之 意”,杨逍殷天正“粗略窥到这个层次”,而周颠则是“大为担心,两遍都不一样,咱们教主怎么记得住?”道理不在于讲,而是在于悟,否则讲得再多也没用,不 深刻,没效果。一阵风吹过,吹过松散的茅草屋,那就是一场灾难;吹过石壁,最多也只能带走几颗细沙;吹过水面,能荡起了一圈圈的波浪;吹过风车,则产生了 源源不断的电力。苹果掉到牛顿头上,就诞生了万有引力,掉到你我头上,只能产生一个包和一颗苹果核。好吧我们就不讨论假如掉的不是苹果而是椰子的事情了。 上士闻道,勤而行之;中士闻道,若存若亡;下士闻道,大笑之,不笑,不足以为道。邓小平之所以喜欢看金庸的小说,称呼金庸为大师,他必然是看到了小说里面 的这些道理。

虚到九天之上,还是得落地。让我们看看一些稍微实际点的东西。你水平提高了,开始做设计了,开始写设计文档,你应该写些什么?在你记录下你做出来的东西 (你搞出了CDM、PDM)的时候,我觉得你还需要记录下你这样做的原因,因为随着时间推移,你设计的东西很可能不适用,但你的想法还是很有用很有参考意 义的,你的想法是你CDM存在的原因。别人看你的设计文档,不单能看到死的结果,还能看到活生生的你的思想,然后延续这种思想,让设计具有生命力,能够延 续下去。平时我们做需求,也要很清楚地知道什么才是真正的需求。我们来研究一下手机铃声,它表示了一种什么样的需求呢?你写下需求“我们的手机需要一种铃 声”,于是,设计就产生了蜂鸣声铃声、十六和弦铃声、MP3铃声,但这就是我们的需求吗?不是的,其实你想说的是“我们需要一种提醒方式,这样在短信、或 者电话到来的时候就能提醒使用者注意了”,这样就能设计出各种铃声,还有震动,还有铃声混合震动,在极端情况下,假如用户处于一个高度紧张的环境之下,很 难注意到这种提醒,我们还能设计出让手机弹出一根刺,刺他一下的方式,一下不行?来多几下。记得之前看到一个闹钟的笑话:推销员对一个职员说,我们这种闹 钟非常好用!它一开始放轻柔的音乐,如果还不醒,那它改放嘈杂的音乐,如果还不醒,它就往你头上洒水,再不醒,它就开始刺你。如果这样都还不醒,它就会打 电话给你老板,告诉他你生病了,请假一天。这个闹钟深刻把握了自己的本质啊。

具有洞察力,我们就能根据行为和目标的契合程度,评价行为是否合理。作为技术人员,我们身边的争论随处可见,语言之争,框架之争,各种流派,各种山寨,比 比皆是。“{”到底放在方法定义的最右边,还是放在方法定义的下一行呢,这可不是一件小事,这是一件很严肃的大事情,关乎信仰!从我了解的历史开始,我还 没有见到任何一方说服了另外一方。面对这这样疯狂的一个世界,你怎样选择加入哪一方呢?李开复说:“Follow your heart!”我说,你首先看看你的目标,你就知道选择那一方了。譬如说开发语言,我们用它的目标是什么?假如我们需要能够很自然地去面对复杂的问题,而 且开发过程中,我们需要能够飞快地利用语言的特性,构建我们的软件,那么C#就是很好的选择,用了汇编你就死翘翘,至少干十年才能把一个增删改查Web系 统搞出来;假如我们需要进行很底层的开发,逻辑不复杂,但要做到精确的控制,性能是第一要义,那么你要选择汇编,甚至用刀子在硬盘上刻程序也在所不惜啊。 语言之争之混乱,在于大家将目标和选择两者混为一谈,从而成功地将它们捣成了一桶浆糊。框架也是如此,我们现在的目标是什么?我们要自然,要快速开发,而 且我们还需要能够扩展,那么我们评价框架的标准就很清楚了。你框架里头技术含量多么深厚,都到地核了,你用了三层架构,你共使用了23种不同的模式,你使 用了微软到目前为止出现的所有技术,这些都没有意义。你做不到让我用起来那种如臂使指的感觉,你甚至阻碍我整体流的思考方式,你就说服不了我,你的框架是 适用的。

到达了虚的境界之后,是不是实就没有用了呢?不是的,年轻人,看问题不能那么片面。虚和实,就相当于战略和战术,你看清楚了目标,只能说明你最高能去到什 么地方,这只是一种可能。而实实在在你能够达到什么高度,是由你的战术来决定的。眼界只能决定你看到的高度,而实操才真正决定了你达到的高度。虚实到头来 要结合到一起,用虚指导实,用实来实现虚,相得益彰,互为阴阳。

这个世界规律是虚的,推动世界发展实实在在的力量是实的。我们想想电灯,电脑,抽水马桶,哪一个不是实在的术创造出来的?我们看看下图的这张凳子,它就是 传说中的“Aeron chair”,它具有出众的外观,具有领衔的人体工学设计,它能根据就座者不同的体重、姿势和动作进行精准的调节,与你的身体自然贴合,让你感受一整天不 会疲倦的轻松自如,更有一小撮份子宣称,由于坐起来太舒服了,他们经常不知不觉加了好多次班还不知道!(广告!广告!赶快Cut掉!)对比一下我们平时坐 的电脑椅呢?这就是术的差距,术的力量!当然,也在于他们把握了椅子不单只是用来坐的,而是要用来坐得非常舒服的这个道有关系。


说了那么久深度,让我们也来聊聊广度。广度也是人认识中很重要的一部分。你只有掌握了很多东西,你才能够了解到什么时候应该用什么技术。有这么一句话: “假如你是一把锤子,那么所有东西在你眼中都是钉子”。假如你只会铁锤这项技能,你只能打桩用锤,砍树用锤,连吃饭你都用锤盛着吃。好吧,你锤子练到了最 高境界,达到了刚极而柔的化境,你用来吃饭一点问题都没有,可是,你真……没觉得吃相有那么一点难看么?打桩用铁锤,砍树用斧头,吃饭用筷子,耶!这样就 温文尔雅得多了。

对广度的关注,也会促成转化为深度的思考,考虑不同东西的相同之处。对广度的探寻,常常会引出创新的主意,将不同的东西混合起来解决问题,是一种创新的做 法;综合多种不同的思想,产生一种新的技术,就是创新的领域。近亲繁殖多产生怪胎,相距越远,就越能产生优良的品种。所以我们也要追求广度,不过要留意要 达到创新的地步的话,至少在深度上面是要达到一定境界的。

你看我扯了这么多方外之言,内心在颤抖,这个行业水太深了!我是不是应该转行?你这样想。完全不必要,假如你朝着深度这条路走下去,你最终也能有这样的感悟。而且即使是其他行业,也是要有深度的,我们最终走向的将会是同一条路。

要往深度走下去,你应该怎么做呢?假如你抓住我,让我尽快提升你的境界,锤炼你的深度,我是做不到的。要做到这件事,只能靠你自己的不断思考,思考一切后 面的原因。想象一个胖子需要减肥,有什么万试万灵的方式吗?假如他一天需要减十斤,我认为是搞不定的,我们只能从他身上切下一块肉,称一下——十斤!真正 有效的办法是他自己运动起来,注意健康的饮食,然后长期下来,才能达到一个健康的体魄,才能有真正的精气神。

所以你的思考,就是这么一个过程,只有你自己不断思考不断突破,最终才能达到一个较高的境界。别人说的再多,你没有经过思考消化吸收,最后也没能转化为你 的肌肉,提供给你力量,只会变成你的脂肪,增加你的负担,阻碍你的行动,最后还影响你对其他营养的吸收。看书本怎么说,看我怎么说,然后教条主义去运用, 是行不通的,你需要将这些东西真真正正变成你自己的东西。知难行易,你可能轻易就开始行动了,却惨不透其中的道理;知易行难,知道了跑步有益身心,你却很 难去坚持它。这些都是你的障碍,只能靠你自己去突破。

我们什么时候会思考?就我自己而言,我通常在看书的时候思考,在和人讨论的时候思考,在遇到困难的时候思考,在独处的时候,有时候也会发呆思考。基本上, 都是思想遇到冲击的时候进行思考。所以,你如果要往更深的地方发展,要做的是从现在开始,珍惜看书的机会,珍惜和别人讨论的机会,创造机会和别人讨论(听 君一席话,胜读十年书啊,八卦除外),去完成具有挑战性的任务。平时也要注意一些你觉得有思想的文章的微言大义,琢磨一下,都是能够对你思想进行冲击的, 比如我之前推荐的《火星人的耳机》、《一百年后人类怎么编程》 等貌似不着边际的文章。普通的解决问题的文章,讲究的是头痛医头脚痛医脚,违反中医之道,通常是很难促进你体质的提高的。书要挑经典的书,否则它说不定将 你带进歧途深处,积重难返。眼中看着《金瓶梅》,脑中想着世界运转的道理,人间色相,尽是脂粉骷髅,这种境界,小弟目前修炼尚浅,是万万达不到的。

最重要的是你认识到这一点,从今天开始觉得,你应该知道。上头再跟你说“你TMD不需要知道,你只需要做就行了”,你应该认识到他说这句话,他的意思其可 能是:他很忙,没空跟你说;他自己也不知道。而绝对不是你不需要知道的意思。你需要知道,从现在开始思考,开始寻找为什么吧。

[转载]简单利用Memcached进行缓存层设计

mikel阅读(1159)

[转载]简单利用Memcached进行缓存层设计 – Jeff Wong – 博客园.

正在考虑web应用缓存层的设计,参考了不少资料,估计还是需要用到相对成熟应用广泛的分布式缓存Memcached。在.net平台上早就有相对成熟的Memcached客户端产品,如BeITMemcachedEnyimMemcached,业余时间看了一下源码,自己分析并调用一下并不困难。这里简单介绍一下利用Memcached的一个简单的缓存层设计,示例代码基于EnyimMemcached,下面以贴代码为主。

一、公共缓存接口

分析ASP.NET web caching的缓存类,我们大致可以抽象出如下几个接口方法:

namespace DotNet.Common.EnyimCache
{
///
/// memcached公共缓存调用方法接口(读)
///
public interface ICacheReaderService
{

///
/// 返回指定key的对象
///
///
<span> </span> ///
object Get(string key);

///
/// 返回指定key的对象
///
///
///
<span> </span> ///
T Get(string key);

///
/// 是否存在
///
///
<span> </span> ///
bool isExists(string key);
}

///
/// memcached公共缓存调用方法接口(写)
///
public interface ICacheWriterService
{
///
/// 缓存有效间隔时间 (以分钟为单位)
///
int TimeOut { set; get; }

///
/// 添加指定key的对象
///
///
<span> </span> ///
<span> </span> void Add(string key, object obj);

///
/// 添加指定key的对象
///
///
///
<span> </span> ///
<span> </span> void Add(string key, T obj);

///
/// 移除指定key的对象
///
///
<span> </span> bool Remove(string key);

///
/// 修改指定key的对象
///
///
<span> </span> ///
bool Modify(string key, object destObj);

///
/// 清空缓存
///
///
bool Release();
}
}

看命名就知道,增删改查是也。根据个人使用缓存的经验,修改操作通常是不需要的,如果确实需要修改缓存数据,直接删除然后添加就是改了。

还有,你可能会问,这里为什么要定义两个接口?原因主要是考虑到读操作(查询)是经常使用的,而写操作(增删改)相对较少,所以也把它们设计成读写分离的方式。

二、缓存服务实现

这里就需要调用Memcached客户端封装好的调用方法,实现增删改查等方法。

using System;

namespace DotNet.Common.EnyimCache
{
using Enyim.Caching.Memcached;

public class CacheReaderService : BaseService, ICacheReaderService
{

public int TimeOut
{
get;
set;
}

public CacheReaderService()
{

}

public object Get(string key)
{
object obj = null;
Client.TryGet(key, out obj);
return obj;
}

public T Get(string key)
{
object obj = Get(key);
T result = default(T);
if (obj != null)
{
result = (T)obj;
}
return result;
}

public bool isExists(string key)
{
object obj = Get(key);
return (obj == null) ? false : true;
}
}

public class CacheWriterService : BaseService, ICacheWriterService
{
public int TimeOut
{
get;
set;
}

public CacheWriterService()
{

}

public CacheWriterService(int timeOut)
{
this.TimeOut = timeOut;
}

public void Add(string key, object obj)
{
if (TimeOut &gt; 0)
{
Client.Store(StoreMode.Add, key, obj, DateTime.Now.AddMinutes(TimeOut));
}
else
{
Client.Store(StoreMode.Add, key, obj);
}
}

public void Add(string key, T obj)
{
if (TimeOut &gt; 0)
{
Client.Store(StoreMode.Add, key, obj, DateTime.Now.AddMinutes(TimeOut));
}
else
{
Client.Store(StoreMode.Add, key, obj);
}
}

public bool Remove(string key)
{
return Client.Remove(key);
}

public bool Modify(string key, object destObj)
{
return Client.Store(StoreMode.Set, key, destObj);
}

///
/// 清空缓存 TO DO
///
///
public bool Release()
{
throw new NotImplementedException();
}
}
}

基类里初始化一个MemcachedClient示例Client,这个Client的方法里封装了较多的函数。查看源码可以知道,它们本质上都是 向Memcached服务端发送相关指令(run command),然后解析返回的二进制数据,如果您熟悉memcached所使用的协议,理解起来应该会相当简单。本文示例只使用了客户端提供的几个方 法。

同时要注意,在实现具体缓存服务的时候,CacheWriterService有两个构造函数,其中带参数的是为缓存显式指定过期时间。这个参数在实际应用中通常需要配置,显然是比较灵活一些的。

备注:在接口中有一个函数Release,本来的目标是清空所有的缓存数据,但是客户端没有直接提供对应的函数,如果您有好的方法,请不吝赐教。

三、简单的读写测试

贴一下字符串、时间、单个类和集合的增删改查示例代码:

ICacheWriterService writer = CacheBuilder.GetWriterService();//writer 使用memcached默认过期时间
ICacheReaderService reader = CacheBuilder.GetReaderService();//reader

#region 字符串

string strKey = "hello";

bool isOK = writer.Remove(strKey); //移除
Console.WriteLine("Removed key {0}:{1}", strKey, isOK);

writer.Add(strKey, "hello world"); //添加
Console.WriteLine("Add key {0}, value:hello world", strKey);

bool isExists = reader.isExists(strKey);//是否存在
Console.WriteLine("Key {0} exists:{1}", strKey, isExists);

string result = reader.Get(strKey) as string;//查询
Console.WriteLine("Get key {0}:{1}", strKey, result);

bool isModify = writer.Modify(strKey, "Hello Memcached!");//修改
Console.WriteLine("Modify key {0}, value:Hello Memcached. The result is:{1}", strKey, isModify);

result = reader.Get(strKey);
Console.WriteLine("Generic get key {0}:{1}", strKey, result);

isOK = writer.Remove(strKey);
Console.WriteLine("Removed key {0}:{1}", strKey, isOK);

isExists = reader.isExists(strKey);
Console.WriteLine("Key {0} exists:{1}", strKey, isExists);

result = reader.Get(strKey) as string;
Console.WriteLine("Get key {0}:{1}", strKey, result);

result = reader.Get(strKey);
Console.WriteLine("Generic get key {0}:{1}", strKey, result);
Console.WriteLine();
Console.WriteLine("===========================================");
Console.Read();

#endregion

#region 时间

DateTime dtNow = DateTime.Now;
strKey = "datetime";
isOK = writer.Remove(strKey); //移除
Console.WriteLine("Removed key {0}:{1}", strKey, isOK);

writer.Add(strKey, dtNow); //添加
Console.WriteLine("Add key {0}, value:{1}", strKey, dtNow);

isExists = reader.isExists(strKey);//是否存在
Console.WriteLine("Key {0} exists:{1}", strKey, isExists);

DateTime dt = (DateTime)reader.Get(strKey);//查询
Console.WriteLine("Get key {0}:{1}", strKey, dt);

dt = reader.Get(strKey);
Console.WriteLine("Generic get key {0}:{1}", strKey, dt);

isOK = writer.Remove(strKey);
Console.WriteLine("Removed key {0}:{1}", strKey, isOK);

isExists = reader.isExists(strKey);
Console.WriteLine("Key {0} exists:{1}", strKey, isExists);

Console.WriteLine("Get key {0}:{1}", strKey, reader.Get(strKey));

Console.WriteLine("Generic get key {0}:{1}", strKey, reader.Get(strKey));//default(datetime)
Console.WriteLine();
Console.WriteLine("===========================================");

Console.Read();

#endregion

#region 类

dtNow = DateTime.Now;
Province province = new Province(13579, "江苏", dtNow, dtNow);

strKey = string.Format("{0}_{1}", province.GetType().Name, province.Id);//省
isOK = writer.Remove(strKey); //移除
Console.WriteLine("Removed key {0}:{1}", strKey, isOK);

writer.Add(strKey, province); //添加
Console.WriteLine("Add key {0}, value:{1}", strKey, dtNow);

isExists = reader.isExists(strKey);//是否存在
Console.WriteLine("Key {0} exists:{1}", strKey, isExists);

Province queryProvince = (Province)reader.Get(strKey);//查询
Console.WriteLine("Get key {0}:{1}", strKey, queryProvince.ProvinceName);

queryProvince = reader.Get
(strKey);
Console.WriteLine("Generic get key {0}:{1}", strKey, queryProvince.ProvinceName);

isOK = writer.Remove(strKey);
Console.WriteLine("Removed key {0}:{1}", strKey, isOK);

isExists = reader.isExists(strKey);
Console.WriteLine("Key {0} exists:{1}", strKey, isExists);

Console.WriteLine("Get key {0}:{1}", strKey, reader.Get(strKey));

Console.WriteLine("Generic get key {0}:{1}", strKey, reader.Get
(strKey));
Console.WriteLine();
Console.WriteLine("===========================================");

Console.Read();

#endregion

#region 集合(列表)

dtNow = DateTime.Now;
IList listCities = new List();
City city = new City(135, province.Id, "南京", "210000", dtNow, dtNow);
listCities.Add(city);
city = new City(246, province.Id, "苏州", "215000", dtNow, dtNow);
listCities.Add(city);

strKey = string.Format("List_{0}_{1}_{2}", province.GetType().Name, province.Id, city.GetType().Name);//省份对应城市
isOK = writer.Remove(strKey); //移除
Console.WriteLine("Removed key {0}:{1}", strKey, isOK);

writer.Add(strKey, listCities); //添加
Console.WriteLine("Add key {0}, value:", strKey);
foreach (var item in listCities)
{
Console.WriteLine("CityId:{0} CityName:{1}", item.Id, item.CityName);
}

isExists = reader.isExists(strKey);//是否存在
Console.WriteLine("Key {0} exists:{1}", strKey, isExists);

IList queryCities = reader.Get(strKey) as IList;//查询
Console.WriteLine("Get key {0}:", strKey);
foreach (var item in queryCities)
{
Console.WriteLine("CityId:{0} CityName:{1}", item.Id, item.CityName);
}

queryCities = reader.Get&gt;(strKey);
Console.WriteLine("Generic get key {0}:", strKey);
foreach (var item in queryCities)
{
Console.WriteLine("CityId:{0} CityName:{1}", item.Id, item.CityName);
}

isOK = writer.Remove(strKey);
Console.WriteLine("Removed key {0}:{1}", strKey, isOK);

isExists = reader.isExists(strKey);
Console.WriteLine("Key {0} exists:{1}", strKey, isExists);

Console.WriteLine("Get key {0}:{1}", strKey, reader.Get(strKey));

Console.WriteLine("Generic get key {0}:{1}", strKey, reader.Get&gt;(strKey));
Console.WriteLine();
Console.WriteLine("===========================================");

Console.Read();

#endregion

#region 集合(字典)

dtNow = DateTime.Now;
IDictionary dictCities = new Dictionary();
city = new City(123, province.Id, "镇江", "212000", dtNow, dtNow);
dictCities.Add(city.Id, city);
city = new City(321, province.Id, "扬州", "225000", dtNow, dtNow);
dictCities.Add(city.Id, city);

strKey = string.Format("Dictionary_{0}_{1}_{2}", province.GetType().Name, province.Id, city.GetType().Name);//省份对应城市
isOK = writer.Remove(strKey); //移除
Console.WriteLine("Removed key {0}:{1}", strKey, isOK);

writer.Add(strKey, dictCities); //添加
Console.WriteLine("Add key {0}, value:", strKey);
foreach (var item in dictCities)
{
Console.WriteLine("CityId:{0} CityName:{1}", item.Key, item.Value.CityName);
}

isExists = reader.isExists(strKey);//是否存在
Console.WriteLine("Key {0} exists:{1}", strKey, isExists);

IDictionary queryDictCities = reader.Get(strKey) as IDictionary;//查询
Console.WriteLine("Get key {0}:", strKey);
foreach (var item in queryDictCities)
{
Console.WriteLine("CityId:{0} CityName:{1}", item.Key, item.Value.CityName);
}

queryDictCities = reader.Get&gt;(strKey);
Console.WriteLine("Generic get key {0}:", strKey);
foreach (var item in queryDictCities)
{
Console.WriteLine("CityId:{0} CityName:{1}", item.Key, item.Value.CityName);
}

isOK = writer.Remove(strKey);
Console.WriteLine("Removed key {0}:{1}", strKey, isOK);

isExists = reader.isExists(strKey);
Console.WriteLine("Key {0} exists:{1}", strKey, isExists);

Console.WriteLine("Get key {0}:{1}", strKey, reader.Get(strKey));

Console.WriteLine("Generic get key {0}:{1}", strKey, reader.Get&gt;(strKey));
Console.WriteLine();
Console.WriteLine("===========================================");

Console.Read();

#endregion

这里就不贴全部代码了,文章最后有示例可以下载。

在我的简单测试中,对常见基础数据类型如(字符串、数组、数字和时间)、集合(列表和字典)都有良好的表现,对datatable和dataset同样表现不俗,但是不太建议直接缓存这两种重粒度的类型。

在显式指定过期时间的示例中,指定过期时间是一分钟,但是memcached实际过期时间有时候好像会多于一分钟,估计是系统内部的延迟。

在本地计算机上进行10万次循环添加缓存的过程中,发现系统内存果然增加的非常厉害。然后查询性能并没有显著下降,也许和我的单机测试环境有关,所以我认为测试结果并没有说服力,要知道,memcached的优势是它的分布式缓存实现。

有人发现如何保证缓存系统的键唯一也非常令人头疼。同样的缓存框架,不同项目不同开发者如何保证自己程序添加的缓存键唯一呢?有一种简单方法就是通 过拼接字符串成为有意义的主键,比如按照项目名、命名空间、类名、数据库中的主键组合构成主键等等。当然了,在查询的时候也要自己封装特定格式的字符串主 键。个人感觉确实是一个行之有效的方法。

demo下载:SimpleCacheApp

[转载]Entity Framework之深入分析

mikel阅读(894)

[转载]Entity Framework之深入分析 – 小城岁月 – 博客园. EF虽然是一个晚生畸形的ORM框架,但功能强大又具有灵活性的,给了开发人员一定的发挥空间。因为微软出发点总是好的,让开发变得简单,但实际上不是所有的事情都这么理想。这里顺便推荐马丁大叔的书《企业应架构模式》。 本节主要深入分析EF的分层问题,下面是本节的已列出的要探讨内容。

  • 领域模型的概念
  • DbContext与Unit of Work 的概念
  • DbContext 创建实例及线程安全问题
  • 不要随便using或Dispose DbContext
  • DbContext的SaveChanges事务
  • Repository与UnitOfWork引入
  • DbContext T4模板的应用
  • EDM文件是放在DAL层还是Model层中?
  • EF MVC项目分层

一、领域模型的概念

领域模型:是描述业务用例实现的对象模型。它是对业务角色和业务实体之间应该如何联系和协作以执行业务的一种抽象。 业务对象模型从业务角色内部的观点定义了业务用例。定义很商业,很抽象,也很理解。一个商业的概念被引入进来之后,引发很多争议和思考。而 DomainObject  在我们实际的项目又演化成大致下面几种 1.纯事务脚本对象(只有字段的set,get),没有任何业务(包括没有导航属性),可以以理解为贫血的领域模型。 2.带有自身业务的对象,例如验证业务,关联导航等。 3.对象包含量了大量的业务,而这些业务中并不是所有业务都和它相关。 尤其是第2种,界限很难划分,怎么判断这个业务是自身的,还是其它的? 或者是否重用度高呢? 第一种和第三种在之前的项目都使用过,目前个人觉得EF现在走的是第2种路线,EF在生成Model模型后,依然可以对模型进行业务修改。我们也不必在这 样上面纠结太多,项目怎么做方便就怎么去实现。比如纯净的POCO我可以当DTO或VO使用;而第3种情况,我们在微软的DataSet时,也是大量使用 的。想详细了解这段的可以参照这篇讨论

二、DbContext与Unit of Work 的概念

在马丁大叔中书看我们可以准看到Unit of Work 的定义:维护受业务事务影响的对象列表,并协调变化的写入和并发问题的解决。即管理对象的CRUD操作,以及相应的事务与并发问题等。Unit of Work的是用来解决领域模型存储和变更工作,而这些数据层业务并不属于领域模型本身具有的。而DbContext其实就是一个Unit of work ,只是如果直接使用这个DbContext 的话,那DbContext所有的业务都是直接暴露的,当然这是看是否项目需要了。可以看出微软的EF DbContext借用了Unit of work的思想。

三、DbContext 创建实例及线程安全问题

1. DbContext不适合创建成单例模式,例如A对象正在编辑,B对象编辑完了提交,导致正在编辑的A对象也被提交了,但是A的改可能要取消的,但是最终都被提交到数据库中了。 2. 如果DbContext创建过多的实例,就要控制好并发的问题,因为不同实例的DbContext可能会对同一条记录进行修改。 3. DbContext线程安全问题,同一实例的DbContext被不同线程调用会引发第一条场景的情况。不同线程使用不同实例的DbContext时又会引发第二种场景的情况。 第一种情况很难控制,而第二种情况可以采用乐观并发锁来解决,其次就是尽量避免对一记录的写操作。

四、不要随便using或Dispose DbContext

我们先来看一段代码:

 BlogCategory cate = null; using (DemoDBEntities context = new DemoDBEntities()) { //context.Configuration.LazyLoadingEnabled = false; DbSet set = context.Set(); cate = set.Find(2); }  //肯定会出错 因为DbContext被释放了 无法延迟加载对象 BlogArticle blog = cate.BlogArticle.First(); 

当我们在使用延迟加载的时候,如果使用using或dispose 释放掉DbContext后,就无法延迟加载导航属性。为什么?我们来看一下DbContext是如何加载对象以及导航属性的。 将上面的代码修改一下:

static void Main(string[] args)
{
BlogCategory cate = null;
using (DemoDBEntities context = new DemoDBEntities())
{
//context.Configuration.LazyLoadingEnabled = false;
DbSet set = context.Set();
cate = set.Find(2);

//肯定会出错 因为DbContext被释放了 无法延迟加载对象
BlogArticle blog = cate.BlogArticle.First();
}

Console.ReadLine();
}

我们打开SQL Server Profiler 来监视一上面的代码执行情况

可以看如果DbContext如果在第一次读取BlogCategory被释放后,那在加载导航属性的时候肯定不会执行成功。

另外一点:为什么很多人一定要using 或dispose掉DbContext ?

是担心数据库连接没有释放?还是担心DbContext占用过多资源呢?

首先担心数据库连接没有释放肯定是多余的,因为DbContext在SaveChanges完成后会释放掉打开的数据库连接,我们来反编译一下SaveChages的源码看看:

public virtual int SaveChanges(SaveOptions options)
{
this.OnSavingChanges();
if ((SaveOptions.DetectChangesBeforeSave &amp; options) != SaveOptions.None)
{
this.ObjectStateManager.DetectChanges();
}
if (this.ObjectStateManager.SomeEntryWithConceptualNullExists())
{
throw new InvalidOperationException(Strings.ObjectContext_CommitWithConceptualNull);
}
bool flag = false;
int objectStateEntriesCount = this.ObjectStateManager.GetObjectStateEntriesCount(EntityState.Modified | EntityState.Deleted | EntityState.Added);
using (new EntityBid.ScopeAuto(" %d#, affectingEntries=%d", this.ObjectID, objectStateEntriesCount))
{
EntityConnection connection = (EntityConnection) this.Connection;
if (0 &gt;= objectStateEntriesCount)
{
return objectStateEntriesCount;
}
if (this._adapter == null)
{
IServiceProvider providerFactory = connection.ProviderFactory as IServiceProvider;
if (providerFactory != null)
{
this._adapter = providerFactory.GetService(typeof(IEntityAdapter)) as IEntityAdapter;
}
if (this._adapter == null)
{
throw EntityUtil.InvalidDataAdapter();
}
}
this._adapter.AcceptChangesDuringUpdate = false;
this._adapter.Connection = connection;
this._adapter.CommandTimeout = this.CommandTimeout;
try
{
this.EnsureConnection();
flag = true;
Transaction current = Transaction.Current;
bool flag2 = false;
if (connection.CurrentTransaction == null)
{
flag2 = null == this._lastTransaction;
}
using (DbTransaction transaction = null)
{
if (flag2)
{
transaction = connection.BeginTransaction();
}
objectStateEntriesCount = this._adapter.Update(this.ObjectStateManager);
if (transaction != null)
{
transaction.Commit();
}
}
}
finally
{
if (flag)
{
this.ReleaseConnection();
}
}
if ((SaveOptions.AcceptAllChangesAfterSave &amp; options) == SaveOptions.None)
{
return objectStateEntriesCount;
}
try
{
this.AcceptAllChanges();
}
catch (Exception exception)
{
if (EntityUtil.IsCatchableExceptionType(exception))
{
throw EntityUtil.AcceptAllChangesFailure(exception);
}
throw;
}
}
return objectStateEntriesCount;
}

可以看到DbContext 每次打开  EntityConnection 最后都会 finally 时 通过this.ReleaseConnection() 释放掉连接,所以这个担心是多余的。

其次DbContext 是否占用过多的资源呢?DbContext确实占用了资源,主要体现在DbContext的Local属性上,每一次的增删改查,Loacl都会从数据库 中加载数据,而这些数据在SaveChanges之后并没有释放掉。因此释放DbContext 是需要的,但是这样又会影响到延迟加载。这样的话,我们可以通过重载SaveChanges,在SaveChanges之后清除掉Local中的数据。但 是这样做为什么有问题,我也不知道,有待考证。上一节中有介绍重载SaveChanges 清除Local 数据阻止查询数据更新。

五、DbContext的SaveChanges自带事务与分布式事务

通过反编译可以看到单实例DbContext的SaveChanges方式默认开启了事务,当同时更新多条记录时,有一条失败就会RollBack。模拟测试代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EF.Model;
using EF.DAL;
using System.Data.Entity;
using System.Collections;
using System.Transactions;
namespace EF.Demo
{
class Program
{
static void Main(string[] args)
{
BlogCategory cate = null;
DemoDBEntities context = new DemoDBEntities();

//DemoDBEntities context2 = new DemoDBEntities();
try
{
//using (TransactionScope scope = new TransactionScope())
//{
//context.Configuration.LazyLoadingEnabled = false;
DbSet set = context.Set();

cate = new BlogCategory();
cate.CateName = "2010-7";
cate.CreateTime = DateTime.Now;

cate.BlogArticle.Add(new BlogArticle() { Title = "2011-7-15" });
set.Add(cate);

//由于没设置Title字段,并且CreateTime字段不能为空,故会引发异常
context.Set().Add(new BlogArticle { BlogCategory_CateID = 2 });
int a = context.SaveChanges();

//    context2.Set().Add(new BlogArticle { BlogCategory_CateID = 2 });
//    int b = context2.SaveChanges();

//    scope.Complete();
//}
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}

Console.ReadLine();
}
}
}

通过SQL SERVER Profile 监视到没有一句SQL语句被执行,SaveChanges事务是预执新所有操作成功后才会更新到数据库中。

我们再来测试一下分布式事务,创建的Context2用于模拟代表其它数据库:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EF.Model;
using EF.DAL;
using System.Data.Entity;
using System.Collections;
using System.Transactions;
namespace EF.Demo
{
class Program
{
static void Main(string[] args)
{
BlogCategory cate = null;
DemoDBEntities context = new DemoDBEntities();

DemoDBEntities context2 = new DemoDBEntities();
try
{
using (TransactionScope scope = new TransactionScope())
{
//context.Configuration.LazyLoadingEnabled = false;
DbSet set = context.Set();

cate = new BlogCategory();
cate.CateName = "2010-7";
cate.CreateTime = DateTime.Now;

cate.BlogArticle.Add(new BlogArticle() { Title = "2011-7-15" });
set.Add(cate);
//实例1 对数据库执行提交
int a = context.SaveChanges();

//实例2 模拟其它数据库提交 时间字段为空,无法更新成功
context2.Set().Add(new BlogArticle { Title="2011-7-16", BlogCategory_CateID = 2 });
int b = context2.SaveChanges();

scope.Complete();
}
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}

Console.ReadLine();
}
}
}

通过SQL SERVER Profile 监视,虽然context实际执行了两条SQL记录,但是context2的SQL没有执行成功,导致事务回滚,所有操作都被没有执行成功。

六、Repository与UnitOfWork引入

Repository是什么? 马丁大叔的书上同样也有解释:它是衔接数据映射层和域之间的一个纽带,作用相当于一个在内存中的域对象映射集合,它分离了领域对象和数据库访问代码的细 节。Repository受DomainObject驱动,Repository用于实现不属于DomainObject的自身相关的,但又受 DomainObject约束的业务。如CRUD操作就不是领域模型要关注的业务,但是领域模型最终要映射为数据关系保存到数据库中。一个领域模型要有对 应的Repository来处理与数据层衔接过程。但不是所有的DomainObject对Repository约束是相同的,可能这个领域对象没有对应 Repository删除操作,而别外一个却有,所以我们经常使用的泛型Repository<T> 是不合适的。但是为了代码简洁重用,大家根据实际情况还是使用了简洁的IRepository<T>接口,就像我们有时为了简单直接把 POCO当DTO或VO使用了。如果不引入Repository,我觉得没有必要实现DAL层,因为DbContext本身就是DAL层,然后只要为 DbContext定义好接IDAL接口从而必免与BLL层的耦合。从这里就可以看出Repository与DAL的区别,一个受域业务驱动出现的,一个 是出于解除耦合出现的。

UnitOfWork 工作单元,前面已经介绍过。为了减少业务层频繁调用DbContext的SaveChanges同步数据库操作(将多个对象的更新一次提交,减少与数据库 交互过程),又要保证DbContext对业务层封闭,所以我们要增加一个对业务层开放的接口。想一想如果把SaveChanges的方法下放到每个 Repository中或者DAL中,那业务层在协调多个Repository事务操作时,就会频繁的写数据库。而分离了Repository中的所有 SaveChanges (或者撤销以及完成单元工作后销毁等操作)后,并通过接口在业务层统一调用,这样既大大提高了效率,也体现了一个完整的单元工作 业务。

七、DbContext T4模板的应用

在Model First中,我们借助于EDMX 和T4模板完成了DbContext和Model的初步设计。但是微软提供的这些模板不能满足用户的所有需求,这个时候我就要修改T4 来生成我们想要的代码。

T4模板应用非常广泛,很多ORM工具的模板也在使用的T4模板,T4也可以生成HTML,JS等多种语言。T4模板支持多种语言书写,可读性很强,也容易上手。

DbContext模板 一共分为两个 DemoDB.DbContext.tt (unit of work)和DemoDb.tt (model) 。前一节我们介绍了如何修改DemoDb.tt 模板 使我们POCO模型继承POCOEntity,这一节再修改一下DemoDb.DbContext.tt模板 使其继承IUnitOfWork接口。

首先我们在Model层中增加IUnitOfWork接口如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EF.Model
{
public interface IUnitOfWork
{
//事务提交
int Save();
}
}

我们再修改DemoDb.DbContext.tt模板:

&lt;#@ template language="C#" debug="false" hostspecific="true"#&gt;
&lt;#@ include file="EF.Utility.CS.ttinclude"#&gt;&lt;#@  output extension=".cs"#&gt;&lt;#

var loader = new MetadataLoader(this);
var region = new CodeRegion(this);
//---------------------------------------------------这里导入了DemoDB.edmx映射文件---------------add by mecity
var inputFile = @"DemoDB.edmx";
//---------------------------------------------------映射文件转为集合方便模板篇历生成代码--------add by mecity
var ItemCollection = loader.CreateEdmItemCollection(inputFile);

Code = new CodeGenerationTools(this);
EFTools = new MetadataTools(this);
ObjectNamespace = Code.VsNamespaceSuggestion();
ModelNamespace = loader.GetModelNamespace(inputFile);

EntityContainer container = ItemCollection.GetItems().FirstOrDefault();
if (container == null)
{
return string.Empty;
}
#&gt;
//------------------------------------------------------------------------------
//
// &lt;#=GetResourceString("Template_GeneratedCodeCommentLine1")#&gt;
//
// &lt;#=GetResourceString("Template_GeneratedCodeCommentLine2")#&gt;
// &lt;#=GetResourceString("Template_GeneratedCodeCommentLine3")#&gt;
//
//------------------------------------------------------------------------------

&lt;# if (!String.IsNullOrEmpty(ObjectNamespace)) { #&gt;
namespace &lt;#=Code.EscapeNamespace(ObjectNamespace)#&gt;
{
&lt;#     PushIndent(CodeRegion.GetIndent(1)); } #&gt;
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
//---------------------------------------------------这加加入对EF.Model命名空间的引用---------------add by mecity
using EF.Model;
&lt;# if (container.FunctionImports.Any()) { #&gt;
using System.Data.Objects;
&lt;# } #&gt;

//---------------------------------------------------这里加入对IUnitOfWork接口继承---------------add by mecity
&lt;#=Accessibility.ForType(container)#&gt; partial class &lt;#=Code.Escape(container)#&gt; : DbContext,IUnitOfWork
{
public &lt;#=Code.Escape(container)#&gt;()
: base("name=&lt;#=container.Name#&gt;")
{
&lt;#         WriteLazyLoadingEnabled(container); #&gt;
}

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}

//---------------------------------------------------这里加入对IUnitOfWork接口方法的实现---------------add by mecity
public int Save()
{
return base.SaveChanges();
}

注意T4模板中加了注释的地方,保存模板后,就会重新创建DemoDBEntities,看一下模板修改后生成后的代码:

//------------------------------------------------------------------------------
//
//    此代码是根据模板生成的。
//
//    手动更改此文件可能会导致应用程序中发生异常行为。
//    如果重新生成代码,则将覆盖对此文件的手动更改。
//
//------------------------------------------------------------------------------

namespace EF.DAL
{
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
//---------------------------------------------------这加加入对EF.Model命名空间的引用---------------add by mecity
using EF.Model;

//---------------------------------------------------这里加入对IUnitOfWork接口继承---------------add by mecity
public partial class DemoDBEntities : DbContext,IUnitOfWork
{
public DemoDBEntities()
: base("name=DemoDBEntities")
{
}

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}

//---------------------------------------------------这里加入对IUnitOfWork接口方法的实现---------------add by mecity
public int Save()
{
return base.SaveChanges();
}

public DbSet BlogArticle { get; set; }
public DbSet BlogCategory { get; set; }
public DbSet BlogComment { get; set; }
public DbSet BlogDigg { get; set; }
public DbSet BlogTag { get; set; }
public DbSet BlogMaster { get; set; }
}
}

八、EDM文件是放在DAL层还是Model层中?

记得我第一篇EF介绍中将EDMX文件和Model放在一起,这样做有一定风险,按照领域模型的概念,Model中这些业务对象被修改的可能性非常 高,并且每个业务对象的修改的业务都可能不同,因此修改DemoDB.tt模板满足所有对象是不实现的, 并且意外保存EDMX文件时,也会导致Model手动修改的内容丢失。因此EDMX不适合和Model放在一起,最好移至到DAL层或 Repository层。DAL中的DemoDb.DbContext.tt模板生成代码是相对固定的(只有一个DemoDBEntities类),因此 对DemoDb.DbContext.tt模板的修改基本可以满足要求。见上节T4应用。我们可以先在DAL中的EDMX完成POCO对象的初步生成与映 射关系工作后,再移至到Model中处理。

九、EF MVC项目分层

就目前CodePlex上的微软项目NorthwindPoco/Oxite/Oxite2)以及其它开源的.net mvc EF项目分层来看,大致结构如下

View   视图

Controller 控制器

IService  Controller调用具体业务的接口

Service   IService的具体实现 ,利用IOC注入到Controller

Repository 是IRepository 的具体实现,利用IOC注入到Service

Model+IRepository  因为IRepository接口对应的是DoaminModel约束业务,并且都是直接开放给Service 调用的,所以放在一个类库下也容易理解,当然分开也无影响。

VO/DTO  ViewObject与DTO 传输对象类库

当然这只是参考,怎么合理分层还是依项目需求,项目进度,资源情况以及后期维护等情况而定。

[转载]在Godaddy空间上部署ASP.NET MVC3 + EntityFramework4.1 + MySQL应用程序

mikel阅读(1057)

[转载]在Godaddy空间上部署ASP.NET MVC3 + EntityFramework4.1 + MySQL应用程序 – Miles Chen – 博客园.

首先一句话介绍一下godaddy.com这个网站。这是一家国外知名度极高的域名注册商+空间供应商,她提供Linux和Windows两种空间。本文当然是针对后者,在Windows空间上部署ASP.NET MVC3 + EntityFramework4.1 + MySQL应用程序。

从其官方网站介绍上可以看到,godaddy的Windows空间默认使用IIS7,支持ASP.NET 4.0集成模式(Integrated Mode), MVC2。那么,如何部署MVC3、EntityFramework以及MySQL呢?

其实很简单,我们知道,MVC3或者MVC2都是基于ASP.NET核心的扩展(通过IHttpModuler),EntityFramework更可直接当作一个外部引用集来发布,故对于MVC和EF只需要简单的把所有(相关)的程序集(Assembly)复制到网站的bin目录下即可

具体而言,对于MVC3来说,有如下程序集,

C:\Program Files\Microsoft ASP.NET\ASP.NET Web Pages\v1.0\Assemblies\*.dll

C:\Program Files\Microsoft ASP.NET\ASP.NET MVC 3\Assemblies\*.dll

对于EntityFramework来说,有如下程序集,

C:\Program Files\Microsoft ADO.NET Entity Framework 4.1\Binaries\*.dll

而MySQL则需要额外的一部工作。首先第一步是一样的,把MySQL Connector程序集复制到bin目录下,

(注:本文以MySQL Connector Net 6.3.6为例,不同版本配置文件略有不同)

C:\Program Files\MySQL\MySQL Connector Net 6.3.6\Assemblies\v4.0\*.dll

其次,由于godaddy使用共享网站服务器,故我们不能在上面安装任何软件(包括MySQL Connector),所以还需要额外在我们网站的Web.config里面加上一段内容,具体如下,

<configuration>

……

<system.data>
<DbProviderFactories>
<add name=”MySQL Data Provider” invariant=”MySql.Data.MySqlClient” description=”.Net Framework Data Provider for MySQL” type=”MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.3.6.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d” />
</DbProviderFactories>
</system.data>

……

</configuration>

至此,大功告成。接着上传所有内容到FTP即可。祝大家使用godaddy一切顺利!

[转载]SQL SERVER本地管理员和SA帐号都无法访问的问题和解决方案

mikel阅读(914)

[转载]SQL SERVER本地管理员和SA帐号都无法访问的问题和解决方案 – 陈希章 – 博客园.

这几天在讲SQL Server 2008的课程,今天头一天,因为主办方准备的环境是用做好的同一个虚拟机,然后分发到很多台实验用的机器后,用sysprepare这种工具,修改了电脑名称。一早就遇到大家无法登录到SQL Server的问题,不管是用本地管理员,还是SA帐号。

我 分析下来,本地管理员无法登录的原因就是因为电脑名称修改了之后,现在的本地管理员的SID已经不是修改之前那个本地管理员的SID了,而SQL Server里面其实保存的是SID,并不是用户名称,所以导致他无法登录服务器。而至于用SA帐号无法登录,是因为主办方在准备这个环境的时候,并没有 启用混合验证模式。

以前也遇到过类似的一个问题,但一下子忘记怎么解决的了。不管怎样,我们还是想办法解决掉了这个问题,参考了下面的博客文章

http://blogs.msdn.com/b/raulga/archive/2007/07/12/disaster-recovery-what-to-do-when-the-sa-account-password-is-lost-in-sql-server-2005.aspx

大致的步骤就是:

  1. 用本地管理员登录,停止SQL Server服务
  2. 修改SQL Server服务的启动参数,在原先的启动参数后面添加 “;-m”(不要带双引号),这是将SQL Server设置进入单用户模式
  3. 启动SQL Server服务,使用sqlcmd登录到SQL Server服务器
  4. 执行exec sp_addsvrrolemember ‘MachineName\Administrator’ ‘sysadmin’;GO;(请注意,红色部分要替换为你的本地管理员帐号)
  5. 停止SQL Server服务,将原先添加的启动参数“;-m”删除掉
  6. 启动SQL Server服务

建议大家安装SQL Server的时候,还是设置混合验证模式和SA密码,然后要保护好这个密码。

[转载]asp.net中web.config配置节点大全详解

mikel阅读(986)

[转载]asp.net中web.config配置节点大全详解 – 亚逊 – 博客园.

web.config 文件查找规则:

(1)如果在当前页面所在目录下存在web.config文件,查看是否存在所要查找的结

点名称,如果存在返回结果并停止查找。

(2)如果当前页面所在目录下不存在web.config文件或者web.config文件中不存在

该结点名,则查找它的上级目录,直到网站的根目录。

(3)如果网站根目录下不存在web.config文件或者web.config文件中不存在该节点

名则在%windir%”Microsoft.NET”Framework”v2.0.50727″CONFIG”web.config文件中查找。

(4) 如果在%windir%”Microsoft.NET”Framework”v2.0.50727″CONFIG”web.config

文件中不存在相应 结点,则在%

windir%”Microsoft.NET”Framework”v2.0.50727″CONFIG”machine.config文件 中查找。

(5)如果仍然没有找到则返回null。

ASP.NET应用程序运行过程中,如果web.config文件发生更改就会导致相应的应

用程序重新启动,这时存储在服务器内存中的用户会话信息 就会丢失(如存储在内存中的

Session)。

(一) appSetings配置节

<appSettings>节点主要用来存储ASP.NET应用程序的一些配置信息,比如上传文件

的保存路径等

<appSettings>

<add key=”ImageType” value=”.jpg;.bmp;.gif;.png;.jpeg”/> <!–允许

上传的图片格式类型–>

</appSettings>

string fileType=ConfigurationManager.AppSettings[“FileType “];

(二) <connectionStrings>节点

<connectionStrings> 节点主要用于配置数据库连接的,我们可以

<connectionStrings>节点中增加任意个节点来保存数据库连接字符串,将来在代码中 通

过代码的方式动态获取节点的值来实例化数据库连接对象,这样一旦部署的时候数据库连

接信息发生变化我们仅需要更改此处的配置即可,而不必因为数据库连接 信息的变化而需

要改动程序代码和重新部署

<connectionStrings>

<add name=”AspNetStudyConnectionString1″ connectionString=”Data

Source=(local);Initial Catalog=AspNetStudy;User ID=sa;Password=sa”/>

</connectionString>

string connectionString = ConfigurationManager.ConnectionStrings

[“AspNetStudyConnectionString1”].ConnectionString;

(三)<compilation>节点

<compilation>节点配置 ASP.NET 使用的所有编译设置。默认的Debug属性为

“true”,即允许调试,在这种情况下会影响网站的性能,所以在程序编译完成交付使用

之后应将其设为“false”。

(四)<authentication>节点

设置asp.net身份验证模式,有四种身份验证模式,它们的值分别如下:

Windows 使用Windows身份验证,适用于域用户或者局域网用户。

Forms 使用表单验证,依靠网站开发人员进行身份验证。

Passport 使用微软提供的身份验证服务进行身份验证。

None 不进行任何身份验证。

(五)<customErrors>节点

<customErrors>节点用于定义 一些自定义错误信息的信息。此节点有Mode和

defaultRedirect两个属性,其中defaultRedirect属性是一个可选属性,表示应 用程序发

生错误时重定向到的默认URL,如果没有指定该属性则显示一般性错误。Mode属性是一个必

选属性,它有三个可能值,它们所代表的意义分别如下:

On 表示在本地和远程用户都会看到自定义错误信息。

Off 禁用自定义错误信息,本地和远程用户都会看到详细的错误信息。

RemoteOnly 表示本地用户将看到详细错误信息,而远程用户将会看到自定义错误

信息。

这 里有必要说明一下本地用户和远程用户的概念。当我们访问asp.net应用程时所使用的

机器和发布asp.net应用程序所使用的机器为同一台机器时成为 本地用户,反之则称之为

远程用户。在开发调试阶段为了便于查找错误Mode属性建议设置为Off,而在部署阶段应将

Mode属性设置为On或者 RemoteOnly,以避免这些详细的错误信息暴露了程序代码细节从而

引来黑客的入侵。

(六)<error>子节点

在<customErrors>节点下还包含有< error>子节点,这个节点主要是根据服务器的

HTTP错误状态代码而重定向到我们自定义的错误页面,注意要使<error>子节点 下的配置

生效,必须将<customErrors>节点节点的Mode属性设置为“On”。下面是一个例子:

<customErrors mode=”On” defaultRedirect=”GenericErrorPage.htm”>

<error statusCode=”403″ redirect=”403.htm” />

<error statusCode=”404″ redirect=”404.htm” />

</customErrors>

(七)<httpHandlers>节点

<httpHandlers>节点用于根据用户请求的URL和HTTP谓词将用户的请求交给相应的

处理程序。可以在配置级别的任何层次配置此节点,也就是说可以针对某个特定目录下指

定的特殊文件进行特殊处理。

<httpHandlers>

<add path=”*.rules” verb=”*”

type=”System.Web.HttpForbiddenHandler” validate=”true”/>

<add path=”*.xoml” verb=”*”

type=”System.ServiceModel.Activation.HttpHandler, System.ServiceModel,

Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089″

validate=”false”/>

<add path=”trace.axd” verb=”*”

type=”System.Web.Handlers.TraceHandler” validate=”true”/>

<add path=”WebResource.axd” verb=”GET”

type=”System.Web.Handlers.AssemblyResourceLoader” validate=”true”/>

<add path=”*.axd” verb=”*” type=”System.Web.HttpNotFoundHandler”

validate=”true”/>

<add path=”*.aspx” verb=”*”

type=”System.Web.UI.PageHandlerFactory” validate=”true”/>

<add path=”*.ashx” verb=”*”

type=”System.Web.UI.SimpleHandlerFactory” validate=”true”/>

<add path=”*.asmx” verb=”*”

type=”System.Web.Services.Protocols.WebServiceHandlerFactory,

System.Web.Services, Version=2.0.0.0, Culture=neutral,

PublicKeyToken=b03f5f7f11d50a3a” validate=”false”/>

<add path=”*.rem” verb=”*”

type=”System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory,

System.Runtime.Remoting, Version=2.0.0.0, Culture=neutral,

PublicKeyToken=b77a5c561934e089″ validate=”false”/>

<add path=”*.soap” verb=”*”

type=”System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory,

System.Runtime.Remoting, Version=2.0.0.0, Culture=neutral,

PublicKeyToken=b77a5c561934e089″ validate=”false”/>

<add path=”*.asax” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”true”/>

<add path=”*.ascx” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”true”/>

<add path=”*.master” verb=”*”

type=”System.Web.HttpForbiddenHandler” validate=”true”/>

<add path=”*.skin” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”true”/>

<add path=”*.browser” verb=”*”

type=”System.Web.HttpForbiddenHandler” validate=”true”/>

<add path=”*.sitemap” verb=”*”

type=”System.Web.HttpForbiddenHandler” validate=”true”/>

<add path=”*.dll.config” verb=”GET,HEAD”

type=”System.Web.StaticFileHandler” validate=”true”/>

<add path=”*.exe.config” verb=”GET,HEAD”

type=”System.Web.StaticFileHandler” validate=”true”/>

<add path=”*.config” verb=”*”

type=”System.Web.HttpForbiddenHandler” validate=”true”/>

<add path=”*.cs” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”true”/>

<add path=”*.csproj” verb=”*”

type=”System.Web.HttpForbiddenHandler” validate=”true”/>

<add path=”*.vb” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”true”/>

<add path=”*.vbproj” verb=”*”

type=”System.Web.HttpForbiddenHandler” validate=”true”/>

<add path=”*.webinfo” verb=”*”

type=”System.Web.HttpForbiddenHandler” validate=”true”/>

<add path=”*.licx” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”true”/>

<add path=”*.resx” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”true”/>

<add path=”*.resources” verb=”*”

type=”System.Web.HttpForbiddenHandler” validate=”true”/>

<add path=”*.mdb” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”true”/>

<add path=”*.vjsproj” verb=”*”

type=”System.Web.HttpForbiddenHandler” validate=”true”/>

<add path=”*.java” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”true”/>

<add path=”*.jsl” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”true”/>

<add path=”*.ldb” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”true”/>

<add path=”*.ad” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”true”/>

<add path=”*.dd” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”true”/>

<add path=”*.ldd” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”true”/>

<add path=”*.sd” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”true”/>

<add path=”*.cd” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”true”/>

<add path=”*.adprototype” verb=”*”

type=”System.Web.HttpForbiddenHandler” validate=”true”/>

<add path=”*.lddprototype” verb=”*”

type=”System.Web.HttpForbiddenHandler” validate=”true”/>

<add path=”*.sdm” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”true”/>

<add path=”*.sdmDocument” verb=”*”

type=”System.Web.HttpForbiddenHandler” validate=”true”/>

<add path=”*.mdf” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”true”/>

<add path=”*.ldf” verb=”*” type=”System.Web.HttpForbiddenHandler”

validate=”true”/>

<add path=”*.exclude” verb=”*”

type=”System.Web.HttpForbiddenHandler” validate=”true”/>

<add path=”*.refresh” verb=”*”

type=”System.Web.HttpForbiddenHandler” validate=”true”/>

<add path=”*.svc” verb=”*”

type=”System.ServiceModel.Activation.HttpHandler, System.ServiceModel,

Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089″

validate=”false”/>

<add path=”*” verb=”GET,HEAD,POST”

type=”System.Web.DefaultHttpHandler” validate=”true”/>

<add path=”*” verb=”*”

type=”System.Web.HttpMethodNotAllowedHandler” validate=”true”/>

</httpHandlers>

从上面的配置中可以看出,针对*.mdf、*.ldf文件的Get或者Post请求都会交给

System.Web.HttpForbiddenHandler来处理,处理的结果就是用户不能查看或者下载相关的

文件。如果我们某个文件夹下的文件或 者某个类型的文件不允许用户下载,可以在

</httpHandlers>节点中增加相应的子节点。

下面我们以一个例子来说明<httpHandlers>节点的用法,在我们的asp.net应用程

序中建立一个IPData目录,在IPData目录中创建一个IPData.txt文件,然后在Web.config

中添加以下配置:

<httpHandlers>

<add path=”IPData/*.txt” verb=”*”

type=”System.Web.HttpForbiddenHandler”/>

</httpHandlers>

(九)<httpRuntime>节点

<httpRuntime>节点用于对 ASP.NET HTTP 运行库设置。该节可以在计算机、站点

、应用程序和子目录级别声明。

例如下面的配置控制用户最大能上传的文件为40M(40*1024K),最大超时时间为60秒,最

大并发请求为100个

<httpRuntime maxRequestLength=”40960″ executionTimeout=”60″

appRequestQueueLimit=”100″/>

(十)<pages>节点

<pages>节点用于表示对特定页设置,主要有三个属性,分别如下:

buffer 是否启用了 HTTP 响应缓冲。

enableViewStateMac 是否应该对页的视图状态运行计算机身份验证检查 (MAC),

以放置用户篡改,默认为false,如果设置为true将会引起性能的降低。

validateRequest 是否验证用户输入中有跨站点脚本攻击和SQL注入式漏洞攻击,

默认为true,如果出现匹配情况就会发HttpRequestValidationException 异常。对于包含

有在线文本编辑器页面一般自行验证用户输入而将此属性设为false。

<pages buffer=”true” enableViewStateMac=”true” validateRequest=”false”/>

(十一)<sessionState>节点

<sessionState>节点用于配置当前asp.net应用程序的会话状态配置。以下就是一

个常见配置:

<sessionState cookieless=”false” mode=”InProc” timeout=”30″ />

上面的节点配置是设置在asp.net应用程序中启用Cookie,并且指定会话状态模式

为在进程中保存会话状态,同时还指定了会话超时为30分钟。

<sessionState>节点的Mode属性可以是以下几种值之一:

Custom 使用自定义数据来存储会话状态数据。

InProc 默认值。由asp.net辅助进程来存储会话状态数据。

Off 禁用会话状态。

SQLServer 使用进程外SQL Server数据库保存会话状态数据。

StateServer 使用进程外 ASP.NET 状态服务存储状态信息。

一般默认情况下使用InProc模式来存储会话状态数据,这种模式的好处是存取速度

快,缺点是比较占用内存,所以不宜在这种模式下存储大型的用户会话数据

(十二)<globalization>节点

用于配置应用程序的全球化设置。此节点有几个比较重要的属性,分别如下:

fileEncoding 可选属性。设置.aspx、.asmx 和 .asax 文件的存储编码。

requestEncoding 可选属性。设置客户端请求的编码,默认为UTF-8.

responseEncoding 可选属性。设置服务器端响应的编码,默认为UTF-8.

以下就是asp.net应用程序中的默认配置:

<globalization fileEncoding=”utf-8″ requestEncoding=”utf-8″

responseEncoding=”utf-8″/>

(十三)web.config文件读写

public void SetAppSetting(string key, string value)

{

AppSettingsSection appSetting = (AppSettingsSection)config.GetSection

(“appSettings”);

if (appSetting.Settings[key] == null)//如果不存在此节点,则添加

{

appSetting.Settings.Add(key, value);

}

else//如果存在此节点,则修改

{

appSetting.Settings[key].Value = value;

}

}

[转载]Doodle.CMS模板引擎之代码下载

mikel阅读(847)

[转载]Doodle.CMS模板引擎之代码下载 – imfunny – 博客园.

上篇介绍了Doodle系统中的模版引擎,这篇就来讲下内置的开源的模版引擎。

Doodle的模版引擎原型是Ader Template Engine.原地址参考(http://www.adersoftware.com/index.cfm?page=templateEngine2)。作者已经在04年停止更新,但是因为代码实在写的太Good了,所以就拿来从新接着维护。

废话不说先放代码。代码托管在了google,可惜被强了。开源协议Apache。等弄好了就把代码托管上去。转载使用也请注明出处。

代码下载请点此。里面有代码的具体例子以及使用说明。

下载代码请点击

Apache ab下载(用来模拟并发操作的场景)

具体的测试数据在这里。也没过多的和NVelocity比,但是速度不会比那个差。页面中两条SQL语句,数据库4W条数据,在ab -n 1024的时候依然保持着很高的效率,内存在30M之内,在0.9秒之内响应。对于真的存在较大压力的时候比原始的aspx要更快对服务器资源的要消耗的 更少。所以就别去怀疑性能了。实在不想多解释这块。担心性能就是杞人忧天了点。
放上两张图 以及测试结果。

内存消耗  44为虚拟内存。27.168为为实际的内存。这点要比NVelocity和原生的aspx要好。

敢兴趣的大家测试试用下。

已经满足了下列几项

1:模版必须很快,同时内存控制的必须很好。内存必须在即使ab -m 1024的情况下内存在60M之内。(也就是同时有1024个人访问内存控制在单方法控制在60M之内。并且响应1秒之内)
2: 支持完整的逻辑表达式,for  foreach if elseif else break while do case ++ 等语句。
3: 必须彻底的开发者和使用者之间的关系。开发者开发完成了之后,使用者通过模版语句来控制显示什么的问题。比如直接的数据库使用的支持。多数据库的支持。
5:语句要简单易懂,并且有很好的扩展性和逻辑性。
6:直接的被使用,减少使用的侵入性。
7:不和一些存在的框架冲突,特别是JQuery
8: 当然就是生成的问题,生成页面的时候要支持web的生成和服务器的生成。通过服务器的生成来解决好数据的分布问题和多服务器的场景这些的。
9:同时支持动态和生成静态页面的浏览。
第4条为启用缓存,还没去制作,之后会增加的。

大家用的愉快。

[转载]VS2008 条件断点

mikel阅读(794)

[转载]VS2008 条件断点 – fangyukuan – 博客园.

条件成立触发断点

先设置一个普通断点。断下来的时候再设置它的条件。如下图:

在Breakpoints窗口中找到这个断点,右键选择Condition…如下图:

输入条件,这样就会在这个条件成立的时候,才触发断点。

内存改变时触发断点

有时候想知道一个指针什么时候被改变了。可以用这种方法。

先在指针处断下来。如下图:

然后在watch窗口查看 【指针变量本身存放的内存地址】。如下图第二个即:&g_p。注意不是g_p。

复制这个地址,然后的Breakpoints窗口中打开New->New Data Breakpoint如下图:

把这个地址写上,如下图:

这样再运行,当指针改变的时候,就会触发断点了。如下图:

方煜宽

2011.07.13

转载请保留连接

本文地址:http://www.cnblogs.com/fangyukuan/archive/2011/07/13/2105395.html

[转载]SQLite 入门教程(四)增删改查,有讲究

mikel阅读(801)

[转载]SQLite 入门教程(四)增删改查,有讲究 – 左洸 – 博客园.

增删改查操作,其中增删改操作被称为数据操作语言 DML,相对来说简单一点。 查操作相对来说复杂一点,涉及到很多子句,

所以这篇先讲增删改操作,以例子为主,后面再讲查操作。

一、插入数据 INSERT INTO 表(列…) VALUES(值…)

根据前面几篇的内容,我们可以很轻送的创建一个数据表,并向其中插入一些数据,不多说,看例子:

myqiao@ubuntu:~/My Documents/db$ sqlite3 test.db
-- Loading resources from /home/myqiao/.sqliterc
SQLite version 3.7.4
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite&gt; .tables
sqlite&gt;
sqlite&gt; CREATE TABLE Teachers(
...&gt; Id integer PRIMARY KEY,
...&gt; Name text NOT NULL,
...&gt; Age integer CHECK(Age&gt;22),
...&gt; Country text DEFAULT 'USA');
sqlite&gt; .tables
Teachers
sqlite&gt;
sqlite&gt; INSERT INTO Teachers VALUES(1,'Alice',25,'CHN');
sqlite&gt; INSERT INTO Teachers VALUES(2,'Bob',25,'BRA');
sqlite&gt; INSERT INTO Teachers(Id,Name,Age,Country) VALUES(3,'Charls',33,'USA');
sqlite&gt; INSERT INTO Teachers(Name,Age) VALUES('Jhon',43);
sqlite&gt; SELECT * FROM Teachers;
Id    Name             Age              Country
----  ---------------  ---------------  ---------------
1     Alice            25               CHN
2     Bob              25               BRA
3     Charls           33               USA
4     Jhon             43               USA
sqlite&gt;

很简单,创建了一个 Teachers 表并向其中添加了四条数据,设定了一些约束,其中有自动增加的主键、默认值等等。

二、修改数据 UPDATE 表 SET 列 = ‘新值’ 【WHERE 条件语句】

UPDATE 语句用来更新表中的某个列,如果不设定条件,则所有记录的这一列都被更新; 如果设定了条件,则符合条件的记录的这一列被更新, WHERE 子句被用来设定条件,如下例:

sqlite&gt;
sqlite&gt; SELECT * FROM Teachers;
Id    Name             Age              Country
----  ---------------  ---------------  ---------------
1     Alice            25               CHN
2     Bob              25               BRA
3     Charls           33               USA
4     Jhon             43               USA
sqlite&gt;
sqlite&gt;
sqlite&gt; UPDATE Teachers SET Country='China';
sqlite&gt; SELECT * FROM Teachers;
Id    Name             Age              Country
----  ---------------  ---------------  ---------------
1     Alice            25               China
2     Bob              25               China
3     Charls           33               China
4     Jhon             43               China
sqlite&gt;
sqlite&gt;
sqlite&gt; UPDATE Teachers SET Country='America' WHERE Id=3;
sqlite&gt; SELECT * FROM Teachers;
Id    Name             Age              Country
----  ---------------  ---------------  ---------------
1     Alice            25               China
2     Bob              25               China
3     Charls           33               America
4     Jhon             43               China
sqlite&gt;
sqlite&gt;
sqlite&gt; UPDATE Teachers SET Country='India' WHERE Age&lt;30; sqlite&gt; SELECT * FROM Teachers;
Id    Name             Age              Country
----  ---------------  ---------------  ---------------
1     Alice            25               India
2     Bob              25               India
3     Charls           33               America
4     Jhon             43               China
sqlite&gt;

三、删除数据 DELETE FROM 表 【WHERE 条件语句】

如果设定 WHERE 条件子句,则删除符合条件的数据记录;如果没有设定条件语句,则删除所有记录

sqlite&gt;
sqlite&gt; SELECT * FROM Teachers;
Id    Name             Age              Country
----  ---------------  ---------------  ---------------
1     Alice            25               India
2     Bob              25               India
3     Charls           33               America
4     Jhon             43               China
sqlite&gt;
sqlite&gt;
sqlite&gt; DELETE FROM Teachers WHERE Age&gt;30;
sqlite&gt; SELECT * FROM Teachers;
Id    Name             Age              Country
----  ---------------  ---------------  ---------------
1     Alice            25               India
2     Bob              25               India
sqlite&gt;
sqlite&gt;
sqlite&gt; DELETE FROM Teachers;
sqlite&gt; SELECT * FROM Teachers;
sqlite&gt;

四、查找数据 SELECT 列… FROM 表

为了后面的练习,需要一些样本数据。 首先将下面的 SQL 语句保存到 data.SQL 文件中

BEGIN TRANSACTION;
CREATE TABLE Cars(Id integer PRIMARY KEY, Name text, Cost integer);
INSERT INTO Cars VALUES(1,'Audi',52642);
INSERT INTO Cars VALUES(2,'Mercedes',57127);
INSERT INTO Cars VALUES(3,'Skoda',9000);
INSERT INTO Cars VALUES(4,'Volvo',29000);
INSERT INTO Cars VALUES(5,'Bentley',350000);
INSERT INTO Cars VALUES(6,'Citroen',21000);
INSERT INTO Cars VALUES(7,'Hummer',41400);
INSERT INTO Cars VALUES(8,'Volkswagen',21600);
COMMIT;

BEGIN TRANSACTION;
CREATE TABLE Orders(Id integer PRIMARY KEY, OrderPrice integer CHECK(OrderPrice&gt;0),
Customer text);
INSERT INTO Orders(OrderPrice, Customer) VALUES(1200, "Williamson");
INSERT INTO Orders(OrderPrice, Customer) VALUES(200, "Robertson");
INSERT INTO Orders(OrderPrice, Customer) VALUES(40, "Robertson");
INSERT INTO Orders(OrderPrice, Customer) VALUES(1640, "Smith");
INSERT INTO Orders(OrderPrice, Customer) VALUES(100, "Robertson");
INSERT INTO Orders(OrderPrice, Customer) VALUES(50, "Williamson");
INSERT INTO Orders(OrderPrice, Customer) VALUES(150, "Smith");
INSERT INTO Orders(OrderPrice, Customer) VALUES(250, "Smith");
INSERT INTO Orders(OrderPrice, Customer) VALUES(840, "Brown");
INSERT INTO Orders(OrderPrice, Customer) VALUES(440, "Black");
INSERT INTO Orders(OrderPrice, Customer) VALUES(20, "Brown");
COMMIT;

然后在在终端执行命令 .read data.sql,将数据导入到数据库中

sqlite&gt;
sqlite&gt; .tables
Friends
sqlite&gt; .read data.sql
sqlite&gt; .tables
Cars      Orders    Teachers
sqlite&gt;

可以看到,Cars 表和 Orders 表已经导入到数据库中,现在可以查询了

sqlite&gt;
sqlite&gt; SELECT * FROM Cars;
Id    Name             Cost
----  ---------------  ---------------
1     Audi             52642
2     Mercedes         57127
3     Skoda            9000
4     Volvo            29000
5     Bentley          350000
6     Citroen          21000
7     Hummer           41400
8     Volkswagen       21600
sqlite&gt; SELECT * FROM Orders;
Id    OrderPrice       Customer
----  ---------------  ---------------
1     1200             Williamson
2     200              Robertson
3     40               Robertson
4     1640             Smith
5     100              Robertson
6     50               Williamson
7     150              Smith
8     250              Smith
9     840              Brown
10    440              Black
11    20               Brown
sqlite&gt;

五、 限制返回数量 SELECT 列… FROM 表 LIMIT 数量 OFFSET 位置

有时候数据库中的数据太多,全部返回可不行,可以限制返回的数量,还可以设定返回的起始位置,如下:

sqlite&gt;
sqlite&gt; SELECT * FROM Cars LIMIT 4;
Id    Name             Cost
----  ---------------  ---------------
1     Audi             52642
2     Mercedes         57127
3     Skoda            9000
4     Volvo            29000
sqlite&gt;
sqlite&gt; SELECT * FROM Cars LIMIT 4 OFFSET 2;
Id    Name             Cost
----  ---------------  ---------------
3     Skoda            9000
4     Volvo            29000
5     Bentley          350000
6     Citroen          21000
sqlite&gt;

六、 别名 SELECT 列 AS 别名,列 AS 别名 FROM

我们可以给返回数据集中的某些列起一个比较直观的名字,比如把 Cost 改为”Price Of Car”

sqlite&gt;
sqlite&gt; SELECT Name , Cost AS 'Price Of Car' FROM Cars;
Name  Price Of Car
----  ---------------
Audi  52642
Merc  57127
Skod  9000
Volv  29000
Bent  350000
Citr  21000
Humm  41400
Volk  21600
sqlite&gt;

七、 条件查询 SELECT 列 FROM 表 【WHERE 条件语句】

一般的条件语句都是大于、小于、等于之类的,这里有几个特别的条件语句

LIKE


  • LIKE 用通配符匹配字符串
  • 下划线 _ 匹配一个字符串
  • 百分号 % 匹配多个字符串
  • LIKE 匹配字符串时不区分大小写
sqlite&gt;
sqlite&gt; SELECT * FROM Cars WHERE Name Like '____';
Id    Name             Cost
----  ---------------  ---------------
1     Audi             52642
sqlite&gt;
sqlite&gt; SELECT * FROM Cars WHERE Name Like '%en';
Id    Name             Cost
----  ---------------  ---------------
6     Citroen          21000
8     Volkswagen       21600
sqlite&gt;
sqlite&gt; SELECT * FROM Cars WHERE Name Like '%EN';
Id    Name             Cost
----  ---------------  ---------------
6     Citroen          21000
8     Volkswagen       21600
sqlite&gt;

GLOB


  • GLOB 用通配符匹配字符串
  • 下划线 ? 匹配一个字符串
  • 百分号 * 匹配多个字符串
  • LIKE 匹配字符串时,区分大小写

BETWEEN 值1 AND 值2


返回两个值之间的数据集合。下面的语句查询价格在 20000 到 55000 之间的车,都是好车啊。

sqlite&gt;
sqlite&gt; SELECT * FROM Cars WHERE Cost BETWEEN 20000 AND 55000;
Id    Name             Cost
----  ---------------  ---------------
1     Audi             52642
4     Volvo            29000
6     Citroen          21000
7     Hummer           41400
8     Volkswagen       21600
sqlite&gt;

IN (集合)


对应列的值必须在集合中。下面的语句查找奥迪和悍马的价格。

sqlite&gt;
sqlite&gt; SELECT * FROM Cars WHERE Name IN ('Audi','Hummer');
Id    Name             Cost
----  ---------------  ---------------
1     Audi             52642
7     Hummer           41400
sqlite&gt;

八、 排序 ORDER BY 列 ASC (DESC)

指定某个列进行排序,ASC 为升序,DESC 为降序。下面的语句查询汽车品牌和价格,并以价格排序

sqlite&gt;
sqlite&gt; SELECT Name, Cost FROM Cars ORDER BY Cost DESC;
Name  Cost
----  ---------------
Bent  350000
Merc  57127
Audi  52642
Humm  41400
Volv  29000
Volk  21600
Citr  21000
Skod  9000
sqlite&gt;

九、 区分 DISTINCT 列

有一些字段的值可能会出现重复,比如订单表中,一个客户可能会有好几份订单,因此客户的名字会重复出现。

到底有哪些客户下了订单呢?下面的语句将客户名字区分出来。

sqlite&gt;
sqlite&gt; Select * FROM Orders;
Id    OrderPrice       Customer
----  ---------------  ---------------
1     1200             Williamson
2     200              Robertson
3     40               Robertson
4     1640             Smith
5     100              Robertson
6     50               Williamson
7     150              Smith
8     250              Smith
9     840              Brown
10    440              Black
11    20               Brown
sqlite&gt;
sqlite&gt; SELECT DISTINCT Customer FROM ORDERS;
Customer
---------------
Black
Brown
Robertson
Smith
Williamson
sqlite&gt;

十、 分组 GROUP BY 列

分组和前面的区分有一点类似。区分仅仅是为了去掉重复项,而分组是为了对各类不同项进行统计计算。

比如上面的例子,我们区分出 5 个客户,这 5 个客户一共下了 11 个订单,说明很多客户都下了不止一个订单。

下面的语句统计每个客户在订单上总共花费了多少钱。

sqlite&gt;
sqlite&gt; SELECT sum(OrderPrice) AS Total, Customer FROM Orders GROUP BY Customer;
Total            Customer
---------------  ---------------
440              Black
860              Brown
340              Robertson
2040             Smith
1250             Williamson
sqlite&gt;

这里 Sum 是 SQLite 内置的统计函数,在这个例子中用来求每个顾客的订单价格的和。

统计结果也可以设定返回条件,但是不能用 WHERE 子句,而是用 HAVING 子句,如下例,返回订单总额大于 1000 的顾客。

sqlite&gt;
sqlite&gt; SELECT sum(OrderPrice) AS Total, Customer FROM Orders
...&gt;         GROUP BY Customer HAVING sum(OrderPrice)&gt;1000;
Total            Customer
---------------  ---------------
2040             Smith
1250             Williamson
sqlite&gt;

十一、 逻辑运算符

有的查询涉及的条件语句很复杂,是有好几个条件语句经过逻辑运算得来的,一共有三种逻辑运算符:

  • AND
  • OR
  • NOT

一般稍微了解点编程知识的应该都没问题。