现在的位置: 首页 > 综合 > 正文

在Entity Framework中使用存储过程(一):实现存储过程的自动映射

2012年01月06日 ⁄ 综合 ⁄ 共 5924字 ⁄ 字号 评论关闭

之前给自己放了一个比较长的假期,在这期间基本上没怎么来园子逛。很多朋友的留言也没有一一回复,在这里先向大家道个歉。最近一段时间的工作任务是如何将ADO.NET Entity Framework 4.0(以下简称EF)引入到我们的开发框架,进行相应的封装、扩展,使之成为一个符合在特定场景下进行企业级快速开发的ORM。在此过程中遇到了一些挑战,也有一些心得。为了向大家分享这些心得,也为了借助大家的脑袋解决我们遇到的问题,接下来我会写一系列相关的文章。这些文章的读者适合那些对EF有基本了解的人。

第一个主题是关于在EF中使用存储过程的问题。我们知道EF不仅仅支持将一个存储过程(或者用户定义函数)转变成方法,也可以为每一个实体的映射三个Function(ADO.NET Entity Framework的术语,将存储过程和用户自定义函数统称为Function):InsertFunction、UpdateFunction和DeleteFunction,分别用于执行添加、修改和删除操作。虽然通过VS提供的设计器,我们很容易实现存储过程的导入和映射。但是,如果模型中实体和实体属性(数据表中的列)过多,这是一项很繁琐并且容易出错的工作。这篇文章就是如何避免这种烦琐的操作,实现存储过程映射的自动化。[Source Code从这里下载]

目录
一、使用存储过程的必要性
二、实现存储过程自动匹配的必要条件
三、通过T4生成新的.edmx模型
四、看看生成出来的.emdx
五、局限性

一、使用存储过程的必要性

我们知道EF通过元数据,即概念模型(Concept Model)、存储模型(Storage Model)和概念/存储映射(C/S Mapping),和状态追踪(State Tracking)机制可以为基于模型的操作自动生成SQL。对于一些简单的项目开发,这是非常理想的,因为他们完全可以不用关注数据存储层面的东西,你可以采用一些完全不具有数据库知识的开发者。但是理想总归是理想,对于企业级开发来说,我们需要的是对数据库层面数据的操作有自己的控制。在这方面,我们可以随便举两个典型的场景:

  • 逻辑删除:对于一些重要的数据,我们可能需要让它们永久保存。当我们试图“删除”这些数据的时候,我们并不是将它们从数据表中移除(物理删除),而是为这条记录作一个已经被删除的标记;
  • 并发处理:为了解决相同的数据在获取和提交这段时间内被另一个用户修改或者删除,我们往往SQL层面增加并发控制的逻辑。比较典型的做法是在每一个表中添加一个VersionNo这样的字段,你可以采用TimeStamp,也可以直接采用INT或者GUID。在执行Update或者Delete的SQL中判断之前获取的VersionNo是否和当前的一致。

让解决这些问题,就不能使用EF为我们自动生成的SQL,只有通过使用我们自定义的存储过程。

二、实现存储过程自动匹配的必要条件

本篇文章提供的存储过程自动映射机制是通过代码生成的方式完成的。说白了,就是读取原来的.edmx模型文件,通过分析在存储模型中使用的数据表,导入基于该表的CUD存储过程;然后再概念/存储映射节点中添加实体和这些存储过程的映射关系。那实现这样的代码生成,需要具有如下三个固定的映射规则。

  • 数据表名-存储过程名:这个映射关系帮助我们通过存储模型中的实体名找到对应CUD三个存储过程(如果实体是数据表);
  • 数据表列名-存储过程参数名:当存储过程被执行的时候,通过这个映射让概念模型实体某个属性值作为对应的参数;
  • 存储过程参数名-版本:当进行参数赋值的时候,通过这个映射决定是使用Original或者Current版本。

在实际的开发过程中,这样的标准存储过程一般都是通过代码生成器生成的(在我的文章《创建代码生成器可以很简单:如何通过T4模板生成代码?[下篇]》中有过相应的实现),它们具有这样的映射关系。

基于这三种映射关系,我定义了如下一个名为IProcedureNameConverter的接口。其中OperationKind是我自定义的一个表示CUD操作类型的枚举。

   1: public interface IProcedureNameConverter

   2: {

   3:     string GetProcedureName(string tableName, OperationKind operationKind);

   4:     string GetColumnName(string parameterName);

   5:     DataRowVersion GetVersion(string parameterName);

   6: }

   7:  

   8: public enum OperationKind

   9: {

  10:     Insert,

  11:     Update,

  12:     Delete

  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)。

   1: public class DefaultNameConverter: IProcedureNameConverter

   2: {

   3:     public string GetProcedureName(string tableName, OperationKind operationKind)

   4:     {

   5:         switch (operationKind)

   6:         { 

   7:             case OperationKind.Insert:

   8:                 return string.Format("P_{0}_I", tableName.Substring(2));

   9:             case OperationKind.Update:

  10:                 return string.Format("P_{0}_U", tableName.Substring(2));

  11:             default:

  12:                 return string.Format("P_{0}_D", tableName.Substring(2));

  13:         }

  14:     }

  15:  

  16:     public string GetColumnName(string parameterName)

  17:     {

  18:         return parameterName.Substring(2).ToUpper();

  19:     }

  20:  

  21:     public DataRowVersion GetVersion(string parameterName)

  22:     {

  23:         if(parameterName.StartsWith("o"))

  24:         {

  25:             return DataRowVersion.Original;

  26:         }

  27:         else

  28:         {

  29:             return DataRowVersion.Current;

  30:         }

  31:     }

  32: }

三、通过T4生成新的.edmx模型

我们采用的基于T4的代码生成,了解EF的应该对T4不会感到陌生了。如果对代码生成感兴趣的话,可以看看我的文章《与VS集成的若干种代码生成解决方案[博文汇总(共8篇)]》。这里利用借助于T4 ToolBox这个开源工具箱,并采用SQL Server SMO获取存储过程信息。所有涉及到的文本转化都实现在如下一个ProcedureMappingTemplate类型中,由于内容较多,具体实现就忽略了,有兴趣的朋友可能下载源代码。ProcedureMappingTemplate具有两个构造函数的参数分别表示:源.edmx文件,服务器和数据库名,存储过程的Schema(默认为dbo)和具体的ProcedureNameConverter(默认为DefaultNameConverter)。

   1: public class ProcedureMappingTemplate: Template

   2: {

   3:     public XmlDocument SourceModel { get; private set; }

   4:     public IProcedureNameConverter ProcedureNameConverter { get; private set; }

   5:     public Database Database { get; private set; }

   6:     public string Schema { get; private set; }

   7:  

   8:     public ProcedureMappingTemplate(string sourceModelFile, string serverName, string databaseName);

   9:     public ProcedureMappingTemplate(string sourceModelFile, string serverName, string databaseName, 

  10:         IProcedureNameConverter procedureNameConverter, string schema);

  11:  

  12:     protected virtual XElement GenerateStorageModelNode();    

  13:     protected virtual XElement GenerateMappingNode();

  14:     public override string TransformText()

  15:     {

  16:         XElement newStorageModelNode = this.GenerateStorageModelNode();

  17:         XElement newMappingNode = this.GenerateMappingNode();

  18:  

  19:         XmlNode storageModelNode = this.SourceModel.GetElementsByTagName("edmx:StorageModels")[0];

  20:         storageModelNode.InnerXml = newStorageModelNode.Elements().ToArray()[0].ToString();

  21:  

  22:         XmlNode mappingNode = this.SourceModel.GetElementsByTagName("edmx:Mappings")[0];

  23:         mappingNode.InnerXml = newMappingNode.Elements().ToArray()[0].ToString();

  24:  

  25:         this.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");

  26:         this.Write(this.SourceModel.DocumentElement.OuterXml.Replace("xmlns=\"\"",""));

  27:         return GenerationEnvironment.ToString();

  28:     }

  29: }

在使用过程中,你只需要在tt模板中创建这个ProcedureMappingTemplate对象,调用Render方法即可。

   1: <#@ template  debug="true" hostSpecific="true" #>

   2: <#@ output extension=".edmx" #>

   3: <#@ assembly name="Microsoft.SqlServer.ConnectionInfo" #>

   4: <#@ assembly name="Microsoft.SqlServer.Smo" #>

   5: <#@ assembly name="Microsoft.SqlServer.Management.Sdk.Sfc" #>

   6: <#@ assembly name="$(TargetDir)Artech.ProcedureMapping.dll" #>

   7: <#@ import namespace="Artech.ProcedureMapping" #>

   8: <#@ include file="T4Toolbox.tt" #>

   9: <#

  10: new ProcedureMappingTemplate(this.Host.ResolvePath("UserModel.edmx"),".","EFExtensions").Render();

  11: #>

四、看看生成出来的.emdx

通过上面创建的TT模板(你指定的数据库中一定要存在具有相应映射关系的存储过程),新的.edmx模型文件会作为该tt文件的依赖文件被生成出来。而这个新生成的.edmx具有存储过程映射信息。具体来说,下面是原始的.edmx文件(只保留元数据节点)。

   1: <?xml version="1.0" encoding="utf-8"?>

   2: <edmx:Edmx Version="2.0" xmlns:edmx="http://schemas.microsoft.com/ado/2008/10/edmx">

   3:   <!-- EF Runtime content -->

   4:   <edmx:Runtime>

   5:     <!-- SSDL content -->

   6:     <edmx:StorageModels>

   7:       <Schema Namespace="Artech.UserModel.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2008" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns="http://schemas.microsoft.com/ado/2009/02/edm/ssdl">

   8:         <EntityContainer Name="ArtechUserModelStoreContainer">

   9:           <EntitySet Name="T_USER" EntityType="Artech.UserModel.Store.T_USER" store:Type="Tables" Schema="dbo" />

  10:         </EntityContainer>

  11:         <EntityType Name="T_USER">

  12:           <

抱歉!评论已关闭.