在Entity Framework中使用存储过程(一):实现存储过程的自动映
之前给自己放了一个比较长的假期,在这期间基本上没怎么来园子逛。很多朋友的留言也没有一一回复,在这里先向大家道个歉。最近一段时间的工作任务是如何将ADO.NET Entity Framework 4.0(以下简称EF)引入到我们的开发框架,进行相应的封装、扩展,使之成为一个符合在特定场景下进行企业级快速开发的ORM。在此过程中遇到了一些挑战,也有一些心得。为了向大家分享这些心得,也为了借助大家的脑袋解决我们遇到的问题,接下来我会写一系列相关的文章。这些文章的读者适合那些对EF有基本了解的人。 第一个主题是关于在EF中使用存储过程的问题。我们知道EF不仅仅支持将一个存储过程(或者用户定义函数)转变成方法,也可以为每一个实体的映射三个Function(ADO.NET Entity Framework的术语,将存储过程和用户自定义函数统称为Function):InsertFunction、UpdateFunction和DeleteFunction,分别用于执行添加、修改和删除操作。虽然通过VS提供的设计器,我们很容易实现存储过程的导入和映射。但是,如果模型中实体和实体属性(数据表中的列)过多,这是一项很繁琐并且容易出错的工作。这篇文章就是如何避免这种烦琐的操作,实现存储过程映射的自动化。[Source Code从这里下载]
一、使用存储过程的必要性我们知道EF通过元数据,即概念模型(Concept Model)、存储模型(Storage Model)和概念/存储映射(C/S Mapping),和状态追踪(State Tracking)机制可以为基于模型的操作自动生成SQL。对于一些简单的项目开发,这是非常理想的,因为他们完全可以不用关注数据存储层面的东西,你可以采用一些完全不具有数据库知识的开发者。但是理想总归是理想,对于企业级开发来说,我们需要的是对数据库层面数据的操作有自己的控制。在这方面,我们可以随便举两个典型的场景:
让解决这些问题,就不能使用EF为我们自动生成的SQL,只有通过使用我们自定义的存储过程。 二、实现存储过程自动匹配的必要条件本篇文章提供的存储过程自动映射机制是通过代码生成的方式完成的。说白了,就是读取原来的.edmx模型文件,通过分析在存储模型中使用的数据表,导入基于该表的CUD存储过程;然后再概念/存储映射节点中添加实体和这些存储过程的映射关系。那实现这样的代码生成,需要具有如下三个固定的映射规则。
在实际的开发过程中,这样的标准存储过程一般都是通过代码生成器生成的(在我的文章《创建代码生成器可以很简单:如何通过T4模板生成代码?[下篇]》中有过相应的实现),它们具有这样的映射关系。 基于这三种映射关系,我定义了如下一个名为IProcedureNameConverter的接口。其中OperationKind是我自定义的一个表示CUD操作类型的枚举。 1: public interface IProcedureNameConverter 3: string GetProcedureName(string tableName,OperationKind operationKind); 5: DataRowVersion GetVersion(string parameterName);
7:? 9: { 11: Update, 13: } 按照我们当前项目采用的命名规范,我定义了如下一个默认的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)。
四、看看生成出来的.emdx通过上面创建的TT模板(你指定的数据库中一定要存在具有相应映射关系的存储过程),新的.edmx模型文件会作为该tt文件的依赖文件被生成出来。而这个新生成的.edmx具有存储过程映射信息。具体来说,下面是原始的.edmx文件(只保留元数据节点)。 4: edmx:Runtime<!-- SSDL content -->
10: </EntityContainer 11: EntityType ="T_USER" 12: Key 13: PropertyRef ="USER_ID" 14: 15: Property ="USER_ID" Type="varchar" Nullable="false" MaxLength="50" 16: ="USER_NAME" ="nvarchar" /> edmx:ConceptualModels 22: ="Artech.UserModel" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" ="http://schemas.microsoft.com/ado/2008/09/edm"="EFExtensionsEntities" annotation:LazyLoadingEnabled="true" 24: ="Users" ="Artech.UserModel.User" ="User" 27: 28: ="ID" 29: 30: ="ID" ="String" ="50" UnicodeFixedLength="false" 31: ="Name" ="true" 32: 33: 34: 35: <!-- C-S mapping content --> Function ="P_USER_I" AggregateBuiltInNiladicFunctionIsComposableParameterTypeSemantics="AllowImplicitConversion" 19: Parameter ="p_user_id" Mode="In" 20: ="p_user_name" 21: Function 22: ="P_USER_U" 23: ="o_user_id" ="P_USER_D" 28: 29: 30: 31: <!-- CSDL content --> 47: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: ModificationFunctionMapping 59: InsertFunction FunctionName="Artech.UserModel.Store.P_USER_I" 60: ParameterName="p_user_id" 61: ="p_user_name" 62: InsertFunction 63: UpdateFunction ="Artech.UserModel.Store.P_USER_U" 64: ="Original" 65: ="Current" 66: UpdateFunction 67: DeleteFunction ="Artech.UserModel.Store.P_USER_D" 68: ="o_user_id" 69: DeleteFunction 70: 71: 72: 73: 74: 75: 76: 77: > 顺便来看看.edmx中的数据表T_USER(只具有两个字段USER_ID和USER_NAME)和对应CUD存储过程的SQL。 2: (
5: ) PROCEDURE [dbo].[P_USER_I] 11: 13: BEGIN
15: VALUES(@p_user_id,@p_user_name)
17: GO
20: @o_user_id 21: @p_user_name NVARCHAR(50)
23: AS
25: UPDATE T_USER
27: WHERE USER_ID = @o_user_id
31: @o_user_id VARCHAR(50)
33: 34: DELETE T_USER 37: END 五、局限性EF最大的好处就是实现了概念模型和存储模型的分离。你可以为概念实体和存储实体起不同的名称,还可以将一个概念实体映射到多个存储实体,反之亦然。还可以建立概念实体的之间的继承关系。而我们这里提供的存储过程的自动映射机制,却依赖于我们预定义的标准存储过程。换句话说,我们的存储过程是完全依赖与存储模型的,而最终我们需要建立概念模型与存储过程之间的映射,当然会出现问题。 所以这种依赖于标准存储过程的映射机制基本上只能适用于概念模型与存储模型结构一致的情况。但是我相信在真正的开发中,很多人还是采用基于数据库生成.edmx模型的开发发生。如果你不对概念模型的结构(比如拆分、继承)作调整,你可以直接采用本文提供的自动映射机制。如果你需要对概念模型的结构作局部调整,由于我们生成的还是.edmx文件,你可以直接在这上面作调整。 总之一句话,如果你的概念模型和存储模型的结构相差不大,这样的自动存储过程映射机制才有意义。 在Entity Framework中使用存储过程(一):实现存储过程的自动映射
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |