发布网友 发布时间:2022-04-13 14:41
共1个回答
热心网友 时间:2022-04-13 16:10
一、使用存储过程的必要性我们知道EF通过元数据,即概念模型(ConceptModel)、存储模型(StorageModel)和概念/存储映射(C/SMapping),和状态追踪(StateTracking)机制可以为基于模型的操作自动生成SQL。对于一些简单的项目开发,这是非常理想的,因为他们完全可以不用关注数据存储层面的东西,你可以采用一些完全不具有数据库知识的开发者。但是理想总归是理想,对于企业级开发来说,我们需要的是对数据库层面数据的操作有自己的控制。在这方面,我们可以随便举两个典型的场景:逻辑删除:对于一些重要的数据,我们可能需要让它们永久保存。当我们试图“删除”这些数据的时候,我们并不是将它们从数据表中移除(物理删除),而是为这条记录作一个已经被删除的标记;并发处理:为了解决相同的数据在获取和提交这段时间内被另一个用户修改或者删除,我们往往SQL层面增加并发控制的逻辑。比较典型的做法是在每一个表中添加一个VersionNo这样的字段,你可以采用TimeStamp,也可以直接采用INT或者GUID。在执行Update或者Delete的SQL中判断之前获取的VersionNo是否和当前的一致。让解决这些问题,就不能使用EF为我们自动生成的SQL,只有通过使用我们自定义的存储过程。二、实现存储过程自动匹配的必要条件本篇文章提供的存储过程自动映射机制是通过代码生成的方式完成的。说白了,就是读取原来的.edmx模型文件,通过分析在存储模型中使用的数据表,导入基于该表的CUD存储过程;然后再概念/存储映射节点中添加实体和这些存储过程的映射关系。那实现这样的代码生成,需要具有如下三个固定的映射规则。数据表名-存储过程名:这个映射关系帮助我们通过存储模型中的实体名找到对应CUD三个存储过程(如果实体是数据表);数据表列名-存储过程参数名:当存储过程被执行的时候,通过这个映射让概念模型实体某个属性值作为对应的参数;存储过程参数名-版本:当进行参数赋值的时候,通过这个映射决定是使用Original或者Current版本。在实际的开发过程中,这样的标准存储过程一般都是通过代码生成器生成的(在我的文章《创建代码生成器可以很简单:如何通过T4模板生成代码?[下篇]》中有过相应的实现),它们具有这样的映射关系。基于这三种映射关系,我定义了如下一个名为IProcereNameConverter的接口。其中OperationKind是我自定义的一个表示CUD操作类型的枚举。1:publicinterfaceIProcereNameConverter2:{3:stringGetProcereName(stringtableName,OperationKindoperationKind);4:stringGetColumnName(stringparameterName);5:DataRowVersionGetVersion(stringparameterName);6:}7:8:publicenumOperationKind9:{10:Insert,11:Update,12:Delete13:}按照我们当前项目采用的命名规范,我定义了如下一个默认的DefaultNameConverter。它体现的是这样的映射关系,比如有个数据表明为T_USER(大写,单词之间用“_”隔开,并以T_为前缀),它对应的CUD存储过程名分别为:P_USER_I、P_USER_U和P_USER_D(大写,以代表存储过程的P_为前缀,后缀_I/U/D表示CUD操作类型,中间为去除前缀的表名)。如果列名为USER_ID,参数名为p_user_id(小写,加p_前缀)。如果需要用Original值为参数赋值,需要将p_前缀改成o_前缀(o_user_id)。1:publicclassDefaultNameConverter:IProcereNameConverter2:{3:publicstringGetProcereName(stringtableName,OperationKindoperationKind)4:{5:switch(operationKind)6:{7:caseOperationKind.Insert:8:returnstring.Format("P_{0}_I",tableName.Substring(2));9:caseOperationKind.Update:10:returnstring.Format("P_{0}_U",tableName.Substring(2));11:default:12:returnstring.Format("P_{0}_D",tableName.Substring(2));13:}14:}15:16:publicstringGetColumnName(stringparameterName)17:{18:returnparameterName.Substring(2).ToUpper();19:}20:21:publicDataRowVersionGetVersion(stringparameterName)22:{23:if(parameterName.StartsWith("o"))24:{25:returnDataRowVersion.Original;26:}27:else28:{29:returnDataRowVersion.Current;30:}31:}32:}三、通过T4生成新的.edmx模型我们采用的基于T4的代码生成,了解EF的应该对T4不会感到陌生了。如果对代码生成感兴趣的话,可以看看我的文章《与VS集成的若干种代码生成解决方案[博文汇总(共8篇)]》。这里利用借助于T4ToolBox这个开源工具箱,并采用SQLServerSMO获取存储过程信息。所有涉及到的文本转化都实现在如下一个ProcereMappingTemplate类型中,由于内容较多,具体实现就忽略了,有兴趣的朋友可能下载源代码。ProcereMappingTemplate具有两个构造函数的参数分别表示:源.edmx文件,服务器和数据库名,存储过程的Schema(默认为dbo)和具体的ProcereNameConverter(默认为DefaultNameConverter)。1:publicclassProcereMappingTemplate:Template2:{3:publicXmlDocumentSourceModel{get;privateset;}4:publicIProcereNameConverterProcereNameConverter{get;privateset;}5:publicDatabaseDatabase{get;privateset;}6:publicstringSchema{get;privateset;}7:8:publicProcereMappingTemplate(stringsourceModelFile,stringserverName,stringdatabaseName);9:publicProcereMappingTemplate(stringsourceModelFile,stringserverName,stringdatabaseName,10:IProcereNameConverterprocereNameConverter,stringschema);11:12:protectedvirtualXElementGenerateStorageModelNode();13:protectedvirtualXElementGenerateMappingNode();14:publicoverridestringTransformText()15:{16:XElementnewStorageModelNode=this.GenerateStorageModelNode();17:XElementnewMappingNode=this.GenerateMappingNode();18:19:XmlNodestorageModelNode=this.SourceModel.GetElementsByTagName("edmx:StorageModels")[0];20:storageModelNode.InnerXml=newStorageModelNode.Elements().ToArray()[0].ToString();21:22:XmlNodemappingNode=this.SourceModel.GetElementsByTagName("edmx:Mappings")[0];23:mappingNode.InnerXml=newMappingNode.Elements().ToArray()[0].ToString();24:25:this.WriteLine("");26:this.Write(this.SourceModel.DocumentElement.OuterXml.Replace("xmlns=\"\"",""));27:returnGenerationEnvironment.ToString();28:}29:}在使用过程中,你只需要在tt模板中创建这个ProcereMappingTemplate对象,调用Render方法即可。1:2:3:4:5:6:7:8:9:四、看看生成出来的.emdx通过上面创建的TT模板(你指定的数据库中一定要存在具有相应映射关系的存储过程),新的.edmx模型文件会作为该tt文件的依赖文件被生成出来。而这个新生成的.edmx具有存储过程映射信息。具体来说,下面是原始的.edmx文件(只保留元数据节点)。1:2:3:4:5:6:7:8:9:10:11:12:13:14:15:16:17:18:19:20:21:22:23:24:25:26:27:28:29:30:31:32:33:34:35:36:37:38:39:40:41:42:43:44:45:46:47:48:49:50:51: