Entity Framework 实体框架的形成之旅--实体数据模型 (EDM)的处理(4)

在前面几篇关于Entity Framework 实体框架的介绍里面,已经逐步对整个框架进行了一步步的演化,以期达到统一、高效、可重用性等目的,本文继续探讨基于泛型的仓储模式实体框架方面的改进优化,使我们大家能够很好理解其中的奥秘,并能够达到通用的项目应用目的。本篇主要介绍实体数据模型 (EDM)的处理方面的内容。

1、实体数据模型 (EDM)的回顾

前面第一篇随笔,我在介绍EDMX文件的时候,已经介绍过实体数据模型 (EDM),由三个概念组成:概念模型由概念架构定义语言文件 (.csdl)来定义;映射由映射规范语言文件 (.msl);存储模型(又称逻辑模型)由存储架构定义语言文件 (.ssdl)来定义。

这三者合在一起就是EDM模式。EDM模式在项目中的表现形式就是扩展名为.edmx的文件。这个文件本质是一个xml文件,可以手工编辑此文件来自定义CSDL、MSL与SSDL这三部分。

CSDL定义了EDM或者说是整个程序的灵魂部分 – 概念模型。这个文件完全以程序语言的角度来定义模型的概念。即其中定义的实体、主键、属性、关联等都是对应于.NET Framework中的类型。

SSDL这个文件中描述了表、列、关系、主键及索引等数据库中存在的概念。

MSL这个文件即上面所述的CSDL与SSDL的对应,主要包括CSDL中属性与SSDL中列的对应。

2、EDMX文件的处理

我们在编译程序的时候,发现EDMX文件并没有生成在Debug目录里面,而EF框架本身是需要这些对象的映射关系的,那肯定就是这些XML文件已经通过嵌入文件的方式加入到程序集里面了,我们从数据库连接的字符串里面也可以看到端倪。

    <add name="sqlserver" connectionString="metadata=res://*/Model.sqlserver.csdl|res://*/Model.sqlserver.ssdl|res://*/Model.sqlserver.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=.;initial catalog=WinFramework;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />

我们看到,这里面提到了csdl、ssdl、msl的文件,而且这些是在资源文件的路径,我们通过反编译程序集可以看到,其实是确实存在这三个文件的。

但是我们并没有把edmx文件进行拆分啊,而且也没有把它进行文件的嵌入处理的啊?有点奇怪!

我们知道,一般这种操作可能是有针对性的自定义工具进行处理的,我们看看这个文件的属性进行了解下。

这个edmx文件的属性,已经包含了【自定义工具】,这个工具应该是生成对应的数据访问上下文类代码和实体类代码的了,那么生成操作不是编译或者内容,而是EntityDeploy是什么处理呢,我们通过搜索了解下。

EntityDeploy操作:一个用于部署 Entity Framework 项目的生成任务,这些项目是依据 .edmx 文件生成的。 可将这些项目作为资源嵌入,或将这些项目写入文件。

根据这句话,我们就不难解释,为什么编译后的程序集自动嵌入了三个csdl、ssdl、msl的xml文件了。

如果我们想自己构建相关的数据访问上下文类,以及实体类的代码生成(呵呵,我想用自己的代码生成工具统一生成,可以方便调整注释、命名、位置等内容),虽然可以调整T4、T5模板来做这些操作,不过我觉得那个模板语言还是太啰嗦和复杂了。

这样我把这个自定义工具【EntityModelCodeGenerator】置为空,也就是我想用自己的类定义格式,自己的生成方式去处理。当置为空的时候,我们可以看到它自动生成的类代码删除了,呵呵,这样就挺好。

3、EF框架的多数据库支持

在前面的例子里面,我们都是以默认SqlServer数据库为例进行介绍EDMX文件,这个文件是映射的XML文件,因此对于不同的数据库,他们之间的映射内容是有所不同的,我们可以看看SqlServer的edmx文件内容(以TB_City表为例)。

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="3.0" xmlns:edmx="http://schemas.microsoft.com/ado/2009/11/edmx">
  <!-- EF Runtime content -->
  <edmx:Runtime>
    <!-- SSDL content -->
    <edmx:StorageModels>
    <Schema Namespace="WinFrameworkModel.Store" Provider="System.Data.SqlClient" ProviderManifestToken="2005" Alias="Self"
            xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator"
            xmlns:customannotation="http://schemas.microsoft.com/ado/2013/11/edm/customannotation"
            xmlns="http://schemas.microsoft.com/ado/2009/11/edm/ssdl">
        <EntityType Name="TB_City">
          <Key>
            <PropertyRef Name="ID" />
          </Key>
          <Property Name="ID" Type="bigint" StoreGeneratedPattern="Identity" Nullable="false" />
          <Property Name="CityName" Type="nvarchar" MaxLength="50" />
          <Property Name="ZipCode" Type="nvarchar" MaxLength="50" />
          <Property Name="ProvinceID" Type="bigint" />
        </EntityType>
        <EntityContainer Name="WinFrameworkModelStoreContainer">
          <EntitySet Name="TB_City" EntityType="Self.TB_City" Schema="dbo" store:Type="Tables" />
        </EntityContainer>
      </Schema></edmx:StorageModels>

<!-- CSDL content -->
    <edmx:ConceptualModels>
      <Schema Namespace="EntityModel"
              Alias="Self" annotation:UseStrongSpatialTypes="false"
              xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation"
              xmlns:customannotation="http://schemas.microsoft.com/ado/2013/11/edm/customannotation"
              xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
        <EntityType Name="City">
          <Key>
            <PropertyRef Name="ID" />
          </Key>
          <Property Name="ID" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
          <Property Name="CityName" Type="String" MaxLength="50" FixedLength="false" Unicode="true" />
          <Property Name="ZipCode" Type="String" MaxLength="50" FixedLength="false" Unicode="true" />
          <Property Name="ProvinceID" Type="Int32" />
        </EntityType>

        <EntityContainer Name="SqlEntity" annotation:LazyLoadingEnabled="true">
          <EntitySet Name="City" EntityType="EntityModel.City" />
        </EntityContainer>
      </Schema>
    </edmx:ConceptualModels>

<!-- C-S mapping content -->
    <edmx:Mappings>
      <Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2009/11/mapping/cs">
        <EntityContainerMapping StorageEntityContainer="WinFrameworkModelStoreContainer" CdmEntityContainer="SqlEntity">
          <EntitySetMapping Name="City">
            <EntityTypeMapping TypeName="EntityModel.City">
              <MappingFragment StoreEntitySet="TB_City">
                <ScalarProperty Name="ID" ColumnName="ID" />
                <ScalarProperty Name="CityName" ColumnName="CityName" />
                <ScalarProperty Name="ZipCode" ColumnName="ZipCode" />
                <ScalarProperty Name="ProvinceID" ColumnName="ProvinceID" />
              </MappingFragment>
            </EntityTypeMapping>
          </EntitySetMapping>
        </EntityContainerMapping>
      </Mapping>
    </edmx:Mappings>
  </edmx:Runtime>

  .........其他内容

  </Designer>
</edmx:Edmx>

而对MySql而言,它的映射关系也和这个类似,主要是SSDL部分的不同,因为具体是和数据库相关的内容。下面是Mysql的SSDL部分的内容,从下面XML内容可以看到,里面的数据库字段类型有所不同。

<edmx:Edmx Version="3.0" xmlns:edmx="http://schemas.microsoft.com/ado/2009/11/edmx">
  <!-- EF Runtime content -->
  <edmx:Runtime>
    <!-- SSDL content -->
    <edmx:StorageModels>
      <Schema Namespace="testModel.Store" Provider="MySql.Data.MySqlClient" ProviderManifestToken="5.5" Alias="Self" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns:customannotation="http://schemas.microsoft.com/ado/2013/11/edm/customannotation" xmlns="http://schemas.microsoft.com/ado/2009/11/edm/ssdl">
        <EntityType Name="tb_city">
          <Key>
            <PropertyRef Name="ID" />
          </Key>
          <Property Name="ID" Type="int" Nullable="false" />
          <Property Name="CityName" Type="varchar" MaxLength="50" />
          <Property Name="ZipCode" Type="varchar" MaxLength="50" />
          <Property Name="ProvinceID" Type="int" />
        </EntityType>

        <EntityContainer Name="testModelStoreContainer">
          <EntitySet Name="tb_city" EntityType="Self.tb_city" Schema="test" store:Type="Tables" />
        </EntityContainer>
      </Schema>
    </edmx:StorageModels>

从以上的对比,我们可以考虑,以一个文件为蓝本,然后在代码生成工具里面,根据不同的数据类型,映射成不同的XML文件,从而生成不同的EDMX文件即可,实体类和数据访问上下文的类,可以是通用的,这个一点也不影响概念模型的XML内容了,所有部分变化的就是SSDL数据存储部分的映射XML内容。

为了测试验证,我增加了Mysql、Oracle共三个的EDMX文件,并且通过不同的配置来实现不同数据库的访问调用。

我们知道,数据上下文的类构建的时候,好像默认是指向具体的配置连接的,如下代码所示(注意红色部分)。

    /// <summary>
    /// 数据操作上下文
    /// </summary>
    public partial class DbEntities : DbContext
    {
        //默认的构造函数
        public DbEntities()  : base("name=DbEntities")
        {
        }

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

        public virtual DbSet<City> City { get; set; }
        public virtual DbSet<Province> Province { get; set; }
        public virtual DbSet<DictType> DictType { get; set; }
    }

如果我们需要配置而不是通过代码硬编码方式,那么是否可以呢?否则硬编码的方式,一次只能是指定一个特定的数据库,也就是没有多数据库的配置的灵活性了。

找了很久,发现真的还是有这样人提出这样的问题,根据他们的解决思路,修改代码如下所示,从而实现了配置的动态性。

    /// <summary>
    /// 数据操作上下文
    /// </summary>
    public partial class DbEntities : DbContext
    {
        //默认的构造函数
        //public DbEntities()  : base("name=DbEntities")
        //{
        //}

        /// <summary>
        /// 动态的构造函数
        /// </summary>
        public DbEntities() : base(nameOrConnectionString: ConnectionString())
        {
        }    

        /// <summary>
        /// 通过代码方式,获取连接字符串的名称返回。
        /// </summary>
        /// <returns></returns>
        private static string ConnectionString()
        {
            //根据不同的数据库类型,构造相应的连接字符串名称
            AppConfig config = new AppConfig();
            string dbType = config.AppConfigGet("ComponentDbType");
            if (string.IsNullOrEmpty(dbType))
            {
                dbType = "sqlserver";
            }

            return string.Format("name={0}", dbType.ToLower());
        }

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

        public virtual DbSet<City> City { get; set; }
        public virtual DbSet<Province> Province { get; set; }
        public virtual DbSet<DictType> DictType { get; set; }
    }

我通过在配置文件里面,指定ComponentDbType配置项指向那个连接字符串就可以了。

<configuration>
  <configSections>
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
  </configSections>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="mssqllocaldb" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="Oracle.ManagedDataAccess.Client" type="Oracle.ManagedDataAccess.EntityFramework.EFOracleProviderServices, Oracle.ManagedDataAccess.EntityFramework" />
      <provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6"></provider>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>
  <connectionStrings>
    <add name="oracle" connectionString="metadata=res://*/Model.oracle.csdl|res://*/Model.oracle.ssdl|res://*/Model.oracle.msl;provider=Oracle.ManagedDataAccess.Client;provider connection string=&quot;DATA SOURCE=ORCL;DBA PRIVILEGE=SYSDBA;PASSWORD=whc;PERSIST SECURITY INFO=True;USER ID=WHC&quot;" providerName="System.Data.EntityClient" />
    <add name="sqlserver" connectionString="metadata=res://*/Model.sqlserver.csdl|res://*/Model.sqlserver.ssdl|res://*/Model.sqlserver.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=.;initial catalog=WinFramework;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />
    <add name="mysql" connectionString="metadata=res://*/Model.mysql.csdl|res://*/Model.mysql.ssdl|res://*/Model.mysql.msl;provider=MySql.Data.MySqlClient;provider connection string=&quot;server=localhost;user id=root;password=root;persistsecurityinfo=True;database=test&quot;" providerName="System.Data.EntityClient" />
  </connectionStrings>
  <appSettings>
    <add key="ComponentDbType" value="mysql" />
  </appSettings>

OK,这样就很好解决了,支持多数据库的问题了。

4、框架分层结构的提炼

我们在整个业务部分的项目里面,把一些通用的内容可以抽取到一个Common目录层(如BaseBLL/BaseDAL等类或接口),这样我们在BLL、DAL、IDAL、Entity目录层,就只剩下一些和具体表相关的对象或者接口了,这样的结构我们可能看起来会清晰一些,具体如下所示。

但是这样虽然比原先清晰了一些,不过我们如果对基类接口进行调整的话,每个项目都可能导致不一样了,我想把它们这些通用的基类内容抽取到一个独立的公用模块里面(暂定为WHC.Framework.EF项目),这样我在所有项目里面引用他就可以了,这个做法和我在Enterprise Library框架的做法一致,这样可以减少每个项目都维护公用的部分内容,提高代码的重用性。

基于这个原则,我们重新设计了项目的分层关系,如下所示。

 

这样我们既可以减少主体项目的类数量,也可以重用公用模块的基类内容,达到更好的维护、使用的统一化处理。

这个系列文章索引如下:

Entity Framework 实体框架的形成之旅--基于泛型的仓储模式的实体框架(1)

Entity Framework 实体框架的形成之旅--利用Unity对象依赖注入优化实体框架(2) 

Entity Framework 实体框架的形成之旅--基类接口的统一和异步操作的实现(3)

Entity Framework 实体框架的形成之旅--实体数据模型 (EDM)的处理(4)

本文转自博客园伍华聪的博客,原文链接:Entity Framework 实体框架的形成之旅--实体数据模型 (EDM)的处理(4),如需转载请自行联系原博主。

时间: 2024-09-16 18:13:39

Entity Framework 实体框架的形成之旅--实体数据模型 (EDM)的处理(4)的相关文章

Entity Framework 实体框架的形成之旅--实体框架的开发的几个经验总结

在前阵子,我对实体框架进行了一定的研究,然后把整个学习的过程开了一个系列,以逐步深入的方式解读实体框架的相关技术,期间每每碰到一些新的问题需要潜入研究.本文继续前面的主题介绍,着重从整体性的来总结一下实体框架的一些方面,希望针对这些实际问题,和大家进行学习交流. 我的整个实体框架的学习和研究,是以我的Winform框架顺利升级到这个实体框架基础上为一个阶段终结,这个阶段事情很多,从开始客运联网售票的WebAPI平台的开发,到微软实体框架的深入研究,以及<基于Metronic的Bootstrap开

Entity Framework 实体框架的形成之旅--几种数据库操作的代码介绍(9)

本篇主要对常规数据操作的处理和实体框架的处理代码进行对比,以便更容易学习理解实体框架里面,对各种数据库处理技巧,本篇介绍几种数据库操作的代码,包括写入中间表操作.联合中间表获取对象集合.递归操作.设置单一字段的修改等几种方式. 1.写入中间表操作 一般情况下,我们可以通过执行数据库脚本方式写入. /// <summary> /// 增加用户IP信息 /// </summary> /// <param name="userID"></param&

Entity Framework 实体框架的形成之旅--Code First的框架设计(5)

在前面几篇介绍了Entity Framework 实体框架的形成过程,整体框架主要是基于Database First的方式构建,也就是利用EDMX文件的映射关系,构建表与表之间的关系,这种模式弹性好,也可以利用图形化的设计器来设计表之间的关系,是开发项目较多采用的模式,不过问题还是这个XML太过复杂,因此有时候也想利用Code First模式构建整个框架.本文主要介绍利用Code First 来构建整个框架的过程以及碰到的问题探讨.  1.基于SqlServer的Code First模式 为了快

Entity Framework 实体框架的形成之旅--数据传输模型DTO和实体模型Entity的分离与联合B

在使用Entity Framework 实体框架的时候,我们大多数时候操作的都是实体模型Entity,这个和数据库操作上下文结合,可以利用LINQ等各种方便手段,实现起来非常方便,一切看起来很美好.但是如果考虑使用WCF的时候,可能就会碰到很多相关的陷阱或者错误了.因为实体模型Entity的对象可能包括了其他实体的引用,在WCF里面就无法进行序列化,出现错误:而且基于WCF的时候,可能无法有效利用Express表达式,无法直接使用LINQ等问题都一股脑出现了.本文基于上面的种种问题,阐述了我的整

Entity Framework 实体框架的形成之旅--为基础类库接口增加单元测试,对基类接口进行正确性校验(10)

本篇介绍Entity Framework 实体框架的文章已经到了第十篇了,对实体框架的各个分层以及基类的封装管理,已经臻于完善,为了方便对基类接口的正确性校验,以及方便对以后完善或扩展接口进行回归测试,那么建立单元测试就有很大的必要,本篇主要介绍如何利用VS创建内置的单元测试项目进行实体框架的基类接口测试. 在采用单元测试这个事情上,很多人可能想到了NUnit单元测试工具和NMock工具进行处理,其实微软VS里面也已经为我们提供了类似的单元测试工具了,可以不需要使用这个第三方的单元测试工具,经试

Entity Framework 实体框架的形成之旅--基于泛型的仓储模式的实体框架(1)

很久没有写博客了,一些读者也经常问问一些问题,不过最近我确实也很忙,除了处理日常工作外,平常主要的时间也花在了继续研究微软的实体框架(EntityFramework)方面了.这个实体框架加入了很多特性(例如LINQ等),目前也已经应用的比较成熟了,之所以一直没有整理成一个符合自己开发模式的实体框架,是因为这个框架和原来我的基于EnterpriseLibrary的模式还是有很大的不同,不过实体框架推出来也很久了,目前也去到了EntityFramework6了,听说7也快出来了. 随着我自己参考阅读

Entity Framework 实体框架的形成之旅--基类接口的统一和异步操作的实现(3)

在本系列的第一篇随笔<Entity Framework 实体框架的形成之旅--基于泛型的仓储模式的实体框架(1)>中介绍了Entity Framework 实体框架的一些基础知识,以及构建了一个简单的基于泛型的仓储模式的框架:在随笔<Entity Framework 实体框架的形成之旅--利用Unity对象依赖注入优化实体框架(2)>则持续优化这个仓储模式的实体框架,主要介绍业务逻辑层的构建,以及利用Unity和反射进行动态的对象注册.本篇主要介绍基类接口的统一和异步操作的实现等方

Entity Framework 实体框架的形成之旅--Code First模式中使用 Fluent API 配置(6)

在前面的随笔<Entity Framework 实体框架的形成之旅--Code First的框架设计(5)>里介绍了基于Code First模式的实体框架的经验,这种方式自动处理出来的模式是通过在实体类(POCO类)里面添加相应的特性说明来实现的,但是有时候我们可能需要考虑基于多种数据库的方式,那这种方式可能就不合适.本篇主要介绍使用 Fluent API 配置实现Code First模式的实体框架构造方式. 使用实体框架 Code First 时,默认行为是使用一组 EF 中内嵌的约定将 P

Entity Framework 实体框架的形成之旅--利用Unity对象依赖注入优化实体框架(2)

在本系列的第一篇随笔<Entity Framework 实体框架的形成之旅--基于泛型的仓储模式的实体框架(1)>中介绍了Entity Framework 实体框架的一些基础知识,以及构建了一个简单的基于泛型的仓储模式的框架,例子也呈现了一个实体框架应用的雏形,本篇继续介绍这个主题,继续深化介绍Entity Framework 实体框架的知识,以及持续优化这个仓储模式的实体框架,主要介绍业务逻辑层的构建,以及利用Unity和反射进行动态的对象注册. 1.EDMX文件位置的调整 我们从上篇例子,