细节决定成败:映射枚举

我听说有些人对这样的工具比较有兴趣:从数据库的元数据中获得映射对象有信息。我实在怀疑这种工具的价值。从数据库获取元数据信息非常容易,包括名称、类型(含有些可变长类型的长度)、是否可空、主键及外键引用(获取关系)。但是有建模经验的人基本上都知道,这些信息是远远不够的,其一是因为元数据还有元数据,有一些关系是不便通过数据库元数据定义来表达的(例如自引用),有些是表达不全面。例如每个Column除了Column名还有一个Caption(所以DataTable中的DataColumn就同时拥有ColumnNameCaption两个属性),这两个属性的功能是不同的,Column名用于数据库的Schema定义,而Caption基本上是用于领域定义(设计良好的MS Access可以获得Caption);另外他们的字符集(有些数据库规定只能用ASCII码,并且与标签的命名规则相同,尽管MS SQL Server在这方面比较宽松)、特性(有一些关键字不可以用于Column名定义)也不完全相同。所以二者不能互相替代。其二是数据库的元数据规范与面向对象的元数据规范也完全不同,很多情况下Table名不能直接用作Class名、Column名也不能直接用作ClassProperty名。t_Customer怎么也不象个Class名;f_CustomerName怎么看也不象个Property名。所以我反对直接从数据库结构直接生成业务层实体对象的源码不是没有道理的。

类似的道理,在将数据库元模型映射为领域元模型的时候,同时也将枚举映射到领域元模型中就非常有必要。例如:很多人对性别的定义是这样的:

public enum Gender
{
    Male,
    Female
}
当属性为Gender类型并映射到PropertyGrid的时候,弹出下拉列表,其中有“Male”和“Female”两项。如果你在一个英语国家,这没有问题。但是在中文里就非常遭人讨厌。于是,你可以这样定义:
 
[MappedEnum(typeof(int), “性别”, false)]
public enum Gender
{
    [MappedEnumEntry(
0, “男”, false)]
    Male,
    [MappedEnumEntry(
1, “女”, false)]
    Female
}

这样就简单了。你可以修改Gender类型的编辑器,以便让PropertyGrid可以识别MappedEnumAttributeMappedEnumEntryAttribute两个标签,能够列出“男”和“女”两个项。

有人已经注意到了,MappedEnum标签中,第一个参数是枚举的基类型(其实是保存到数据库中的基类型,可以支持intboolstringchar四种,在enum中的基类型永远都是System.Int32);第二个参数是对应的Caption;第三个参数是bool类型的可选的控制参数。当第三个参数为true的时候,将可以从该类型所在的assembly中获取资源,查找名称为Caption的资源串来替换运行时的Caption。一个非常简单的可选参数定义,解决了多语言化的问题。

当然,对于可以多选的词典项,MappedEnumEntry也可以象FlagsAttribute那样进行自动组合成集合类型。

枚举是领域中的一个非常重要的元素,所以Kanas.net2003年的第一个版本开始,就支持枚举的映射。

显然,域对象中定义为Gender类型的实体,有机会透明地按照枚举的定义来保存到数据库中,Coding的时候可以直接使用对应的标识,在UI层也可以如愿地按照客户的要求显示合适的内容,一切都是按部就班。

不过,在实际项目实践中,像性别、星期这样有穷且永远不会变化的枚举太少太少。更多的是一些能够变化、且不断丰富的枚举词典,相信更多的人都遇到过类似的情况。几乎所有的人都会为每个枚举词典定义一个表格。我见过一个国际性的行业软件,枚举词典所占用的表格几乎占了全部表格数的一半,原因是这些项目都是需要作为关键词进行分类统计的。

枚举词典有三个共性。第一个是业务无关性,无论在什么地方引用,都不牵涉到相互的联动关系。一旦枚举词典项发生改动,则一定是枚举项本身的改动所引起,与引用者不发生任何关系。第二个是相对稳定性,大部分情况下是不需要频繁变动的。第三个是共享性。引用可能发生在同一个实体内的多个属性中。这些共性决定了可能采用某种共同的、抽象的方式来处理。

我很早以前的项目实践中采用的策略是单一表:

其中有两个域复用:

category为自引用,值为零的时候,表示本行为类别;值为非零的时候表示本行为对应类别的项。fixedtrue的时候,如果本行为类别则表示该类别不允许添加新项,否则为允许添加新项。如果本行为项,则表示本行不可编辑,一方面是业务中肯定能够用到,另一方面可能是已经被用到了。

category是不可添加的(事实上添加category也不合乎逻辑),所有的操作只有添加新项和删除作废项两种。项的操作有三种:修改名称及可编辑状态、删除该项、合并到其他项。前两个比较好理解,最后一个项意味着可以删除已经被引用的项,将引用改到指定的其他项中。例如有一个项是30~40岁,后来有人错误地输入了35岁,当然要被合并了。记得当时我提供给用户的录入非常自由,可以随意在一个ComboBox中选择列表中的项同时也可以手工填入一个字符串,如果该字符串在该类别中不存在则在该类别中自动添加一个新项。久而久之,五花八门的输入都出现了,幸好我事先给管理员提供了合并项的功能,让他感激不尽。

这种方式一直觉得很好使,于是在Kanas.net1.0中加入了一个自动的类型:Dictionary。后来发现,这个类还是太简单,稍复杂一点就无法处理了。于是在后续的版本中我取消了这个类。事实上,不同的枚举词典类型并不像我设计的那样只有可添加项和不可添加项那么简单,很可能不同的枚举词典还必须承载其他的业务属性。例如单位,除了名称外还有换算关系,不同枚举类别间很可能还会发生一对多或者多对多的引用关系。不过可以肯定的是:枚举词典项不同于普通实体类型的处理,在对象关系映射中也必须采用完全不同的方式。

posted on 2005-12-07 00:12 双鱼座 阅读(3155) 评论(13)  编辑 收藏 网摘

评论

#1楼  2005-12-07 01:19 asdfsa [未注册用户]

<HTML>
<HEAD>
<META http-equiv='Content-Type' content='text/html; charset=gb2312'>
<TITLE>满屏跳动的图像(带链接)</TITLE>
</HEAD>
<BODY >

要完成此效果把如下代码加入到<body>区域中图片自选

<script>
var brOK=false;
var mie=false;
var aver=parseInt(navigator.appVersion.substring(0,1));
var aname=navigator.appName;
var mystop=0;
var step=0.2;

function checkbrOK()
{if(aname.indexOf("Internet Explorer")!=-1)
{if(aver>=4) brOK=navigator.javaEnabled();
mie=true;
}
if(aname.indexOf("Netscape")!=-1)
{if(aver>=4) brOK=navigator.javaEnabled();}
}
var vmin=1.5;
var vmax=3;
var vr=2;
var timer1;


function Chip(chipname,width,height)
{this.named=chipname;
this.vx=vmin+vmin;
this.vy=0;
this.w=width;
this.h=height;
this.xx=0;
this.yy=0;
this.timer1=null;
}

function movechip(chipname)
{
if(brOK && mystop==0)
{eval("chip="+chipname);
if(!mie)
{pageX=window.pageXOffset;
pageW=window.innerWidth;
pageY=window.pageYOffset;
pageH=window.innerHeight;
}
else
{
pageX=window.document.body.scrollLeft;
pageW=window.document.body.offsetWidth-8;
pageY=window.document.body.scrollTop;
pageH=window.document.body.offsetHeight+15;
}
chip.xx=chip.xx+chip.vx;
chip.vy=chip.vy+step;
chip.yy=chip.yy+chip.vy;



if(chip.xx<=pageX)
{chip.xx=pageX;
chip.vx=vmin;
}
if(chip.xx>=pageX+pageW-85)
{
chip.xx=pageX;
chip.vx=vmin;
chip.yy=pageY;
chip.vy=vmin+vmax;
}

if(chip.yy>(pageY+pageH-chip.h))
{
chip.yy=pageY+pageH-chip.h;
chip.vy=-chip.vy*0.65;
}

if(!mie)
{eval('document.'+chip.named+'.top ='+chip.yy);
eval('document.'+chip.named+'.left='+chip.xx);
}
else
{eval('document.all.'+chip.named+'.style.pixelLeft='+chip.xx);
eval('document.all.'+chip.named+'.style.pixelTop ='+chip.yy);
}
chip.timer1=setTimeout("movechip('"+chip.named+"')",20);
}
}

function stopme(flag)
{
brOk=true;
mystop=flag;
movechip("tome");
}

var tome;
var chip;
function tome()
{checkbrOK();
tome=new Chip("tome",80,80);
if(brOK && mystop==0)
{
movechip("tome");
}
}

ns4=(document.layers)?true:false;
ie4=(document.all)?true:false;

function cncover()
{
if(ns4){
document.cnc.left=window.innerWidth/2-400;
eval('document.cnc.top=document.'+chip.named+'.top');
document.cnc.visibility="show";
stopme(1);
mytime=setTimeout("cncout()",3000);
}else if(ie4)
{
document.all.cnc.style.left=window.document.body.offsetWidth/2-400;
document.all.cnc.style.top=parseInt(document.all.tome.style.top);
document.all.cnc.style.visibility="visible";
stopme(1);
mytime=setTimeout("cncout()",3000);
}
}

function cncout()
{
clearTimeout(mytime);
if(ns4){
document.cnc.visibility="hide";
stopme(0);
}else if(ie4)
{
document.all.cnc.style.visibility="hidden";
stopme(0);
}

}onload=tome;
</script>
<div id="tome" style="position:absolute;"><a href=# onMouseOver=cncover() onMouseOut=cncout() target="_blank"><img src=http://www.webasp.net/images/406/1602.gif">http://www.webasp.net/images/406/1602.gif border=0></a></div>
<div id="cnc" style="position:absolute; left:0;top:0;;visibility:hidden;"><a href=http://www.webasp.net/ onMouseOver=cncover() onMouseOut=cncout() target=_blank><img border=0 src=http://www.webasp.net/images/406/1.jpg></a></div>


</BODY></HTML>   回复  引用    

#2楼  2005-12-07 09:33 小残      

啧,看不懂你在讲什么。。。   回复  引用  查看    

#3楼  2005-12-07 09:58 肯.索夫特      

说实话,我也没有看明白,刚看到标题,我还以为你是准备做一个动态映射的枚举类,看完之后,不由得很失望。
另外:我最不喜欢的就是细节决定成败这句话,另有一句我可以告诉你“成大事者不拘小节”来反证“细节决定成败”这句话是十分错误的。   回复  引用  查看    

#4楼  2005-12-07 10:02 wu ming [未注册用户]

有些软件有一张MasterCodes表就是用来做这个事情的   回复  引用    

#5楼  2005-12-07 10:11 蛙蛙池塘      

前半截能看懂,后半截看迷糊了,呵呵,其实用数据库元数据直接生成业务类确实不好,可以用带批注的typedDatasets,在vs.net 2005里的DataSet设计器非常强大了,可以直接设计TableAdapder并生成好多代码,也就是说数据层大多都自动生成了,业务层引用一下这个项目就可以以面向对象的方式操作数据库了,而且是强类型的DataSet,用起来特舒服。
关于下面的枚举的说明,确实我也感觉枚举挺有用的,就是偶用的不熟,就以前研究权限计算的时候了解过一阵子位域枚举。   回复  引用  查看    

#6楼 [楼主] 2005-12-07 10:28 双鱼座      

@小残:
所谓映射,如果大家知道对象关系映射,换个主体,将对象换成枚举,枚举关系映射,就是本文的主题了。如果我行文能力比较差,就只好抱歉了。

@肯.索夫特:
我晕...你连“小节”和“细节”的区别都没有搞明白,就来证明这个证明那个。细节是支撑一座大厦的每一块砖、每一根梁柱。而小节则专指一些形而上的低级规范约束。

@wu ming:
那应该就是我早期使用的dictionary方式。此表的作用代替了一部分元数据。

@蛙蛙池塘:
DataSet无法解决关系映射的全部细节,不能替代o/r mapping,更无法解决枚举映射。   回复  引用  查看    

#7楼  2005-12-07 10:39 蛙蛙池塘      

后半拉也基本看懂了,其实就是提供了一个数据字典的设计方案,任何一个系统里都要维护一个数据字典,我们的wawaCRM正在考虑这个事情,正好给偶提供了一个思路,其实好多下来列表,比如说客户的接触来源,客户类型,销售阶段等都是下拉框来选择的,也就是枚举,但也有多选的,就是位域枚举(flagEnum),你提供的这个结构感觉还可以,我刚开始还打算用XML来持久化这些词典元素呢。
不过还有一些疑问,麻烦指教一下。
"如果本行为项,则表示本行不可编辑,一方面是业务中肯定能够用到,另一方面可能是已经被用到了。"
已经添加的项就不能修改了吗,不应该吧,比如说我以前这个项的名称叫“报刊电视”,现在我要改成“传统媒体”,其实本质一样的,我就愿意改,不可以吗?
“category是不可添加的(事实上添加category也不合乎逻辑),所有的操作只有添加新项和删除作废项两种。”
category怎么就不可以添加了呀,我以前有两个词典,现在再添加一个不行吗?

你能不能用一个例子来解释一下category和项,我可能没理解对。我感觉“性别”就是category,然后“男”和“女”就是item,对吗?然后我再添加一个国家的category不可以吗?或者我把“男”改成“man”,“女”改成“women”不可以吗?

实在理解不了你上面的两句话。

还有你最后提到的项合并是如何实现的呀,合并的话是多个标签对应同一个值吧,就你这个数据表结构怎么去做呀。   回复  引用  查看    

#8楼  2005-12-07 10:55 蛙蛙池塘      

typedDataSet的确不能解决关系映射的全部细节,但是o/r mappting能解决吗?typedDataSet本来就是一种o/r mappting技术,而且在vs2005里操作起来特方便,还能给TableAdapter添加自定义查询,并自动映射到业务实体类的方法,字段就不用说了,自动映射成Entity的属性呗,你说的字段名直接做Entity的属性名不好,在vs.net里可以直接可视化的设置别名,你也可以在.xsd里给DataSet模式添加批注字段,这都可以的。而且typedDataSets还可以设置约束来检测数据完整性,在ColumnChanging,ColumnChanged,RowChanging,RowChanged事件里加入自定义的数据验证业务逻辑,比如乐观并发检测,数据完整性检查等。而且像一些父子表的关系等,也可以在数据集的设计视图里转换成自定义查询,并自动给业务实体类添加响应的方法,现在的强类型数据集已经可以把数据库的字段,查询,约束,关系等可以映射了,就查索引了,索引在DataTable里也可以自定义呀,对吧。至于你说的枚举映射,我感觉只能通过其它类型的集合类或者字典类结合来处理了,总之我感觉vs2005已经给我们开发持久层提供了很方便的特性,再加上我们的一些手工操作,基本上不用其他什么nhibernate,ibaist等orm持久层框架就可以很快开发出数据库应用了。   回复  引用  查看    

#9楼 [楼主] 2005-12-07 11:28 双鱼座      

@蛙蛙池塘:
很高兴你关注此文。
1.已经添加的项就不能修改了吗,不应该吧,比如说我以前这个项的名称叫“报刊电视”,现在我要改成“传统媒体”,其实本质一样的,我就愿意改,不可以吗?
当然可以。系统管理员可以将此项的Fixed设置为False就可以改了。
2.Category不能添加是指在完成设计并发布以后不能再添加。事实上添加词典类别这个功能也不能由系统提供,而是设计时已经初始化好了。“性别”就是Category,“男”、“女”就是Item,没有错。Fixed这个域,在Category中的功能是是否可以在该类别添加项;而在Item中的功能却是是否可以修改和删除。这一点功能主要是可以供UI层参考。
3.词典项合并必须依赖对引用情况的了解。一般情况下,只要有一个控制器,知道哪些实体的属性引用了词典即可(不必了解具体引用了哪个类别),运行时根据引用情况去查找相关的引用。收集这些引用的实体及属性,用一个update语句就完成了合并。
4.我也注意到了,ado.net2.0的DataSet的确比ado.net1.1有比较多的改进,比如对row状态的改进,等等。但是在类型化DataSet方面并没有比1.1有什么大的突破。从ado.net1.0开始,dataset就具备直接设置row的属性来达到修改row中的数据的目的,但这并不是o/r mapping,这更象为设计期提供方便的一种工具。
当然,我也没有否认dataset的重要性,我甚至提供了一种快速映射到DataSet的机制,捕获对实例对象的修改,由框架自动修改DataTable中相应row中的值或者删除相应的row,在AcceptChanges中直接向事务控制器提交对对象的操作。当然这些设计都是为了方便UI层,并没有为业务层考虑更多。
在Dataset中,用DataRelation映射关系有一个很大的问题是,在DataTable中的某列的类型是一个object,而这个object是对另外一个对象集合中的某个object的引用。但是我们知道,object无法直接写到数据库中,必须转换成RDBMS支持的类型,例如,只能通过被引用object的主键类型的integer来识别。这就是一种失配,换句话说,无法完整地将领域层的关系映射到DataSet中。
讨论这些细节超出了本文的范围。本文的意图是说,对枚举的映射有别于对实体的映射,在.net上一个完整的o/r mapping框架必须提供一种对枚举的映射作为补充。   回复  引用  查看    

#10楼  2005-12-07 11:50 蛙蛙池塘      

恩,枚举映射确实挺有用的,偶好好吸收吸收,偶对o/r mappting知识掌握不到家,没考虑这么多东西。   回复  引用  查看    

#11楼  2005-12-28 17:24 路过 [未注册用户]

标题挺吸引人的

内容就不敢恭维了……   回复  引用    

#12楼  2006-01-24 10:32 Nicol      

学习,不错。
提到的问题我就犯过这样的错误,后来也基本是按照楼主的思路来解决了。刚开始email请教,你说等你的Kanas.net1.3,不知道现在怎么样了?   回复  引用  查看    

#13楼  2006-01-24 15:14 [天道酬勤] [未注册用户]

支持一下吧,虽然我也不是很..,
但我像做为一个开发人员主动思考是很可贵的.   回复  引用    





标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
Google站内搜索
[推荐职位]上海盛大网络招聘.Net开发工程师

China-pub 计算机图书网上专卖店!6.5万品种 2-8折!
近千种 9-95 新二手计算图书火热销售中!
开发者征途系统新作:《设计模式——基于C#的工程化实现及扩展》

相关文章:

相关链接:
 

导航

<2005年12月>
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567

统计

与我联系

搜索

 

常用链接

留言簿(32)

我参与的团队

我的标签

随笔档案

文章分类

相册

芸芸众生

最新评论

  • 1. re: ORM之硬伤
  • @阿水 --引用-------------------------------------------------- 阿水: 哈哈 讨论了很多了来一个 业务吧,也是我们项目中实际的功能。<b...
  • --Raze911
  • 2. re: ORM之硬伤
  • 我是个初学者,本身为业务人员(会计),正在学习ECO 自己做了个案例,感觉好傻瓜化的 就是 1、自己根据一个现实需求进行分析需要管理的实体类与抽象类,以及各个类之间的关系,在ECO框架中画了个E...
  • --Raze911
  • 3. re: ORM漫谈
  • 请问你的Kanas.net Framwork 2.0版本进展如何,我一直关注呢
  • --xjb
  • 4. re: ORM漫谈
  • 双鱼的文章都很有深度
  • --xjb
  • 5. re: ORM漫谈
  • @巫云

    我们公司是使用存储过程来隔离T和O,同时使用存储过程解决T和O的差异。
  • --碧落

阅读排行榜

评论排行榜