前言

此篇文章我将深入去摸索edmx中一些不为人知的东西,有时候我们需要知道Code  First模型中一些存储以及映射的原理,个人觉得那是必要的也是有用的,因为很有可能SQL会出现一些其他问题,只有掌握了一些必备的原理,这样当报错时才会不知所措。

原理

我们知道实体数据模型(EDM)是应用程序和数据存储之间的沟通桥梁,同时我们通过属性映射的API与数据存储之间的交互都是基于EDX,所以一切我们从EDM开始谈起。

我们不用操之过急,我们先了解原理之后,再通过实际来操作你就明白为什么要先了解原理的至关重要性了(可能会有点枯燥,but please take it easy!)。

先给出数据库表对应的类(下面会用到):

    public partial class Student
{
public int Key { get; set; }
public string Name { get; set; }
public int FlowerId { get; set; } public virtual Flower Flower { get; set; }
}
    public partial class Flower
{
public Flower()
{
this.Students = new HashSet<Student>();
} public int Id { get; set; }
public string Remark { get; set; }
public virtual ICollection<Student> Students { get; set; }
}

下面我们一步步来进行,我们新建一个ADO.NET实体数据模型,如下:

因为之前用的Code->Database,现在我们反其道为之,通过Database->Code来获得生成的设计架构,于是我们需要来自数据库的EF设计器,如下:

生成了基本的架构之后,我们看到了最重要的edmx,也就是EDM中的XML文件,但是此时看到的只是类图,我们需要通过指定XML工具来打开如下:

此时你将清楚的看到edmx中架构:

里面最重要的就是这三部分,不用说大家也能明白:概念模型定义语言(CSDL)即概念层、存储模型定义语言(SSDL)即存储层、概念与存储之间的映射语言(MSL)即映射层。

接下来我们就存储模型具体来看看里面到底有什么东西,我们看几个重要的部分:

EntityContainer和EntitySet

        <EntityContainer Name="DBByConnectionStringModelStoreContainer">
<EntitySet Name="Flower" EntityType="Self.Flower" Schema="dbo" store:Type="Tables" />
<EntitySet Name="Student" EntityType="Self.Student" Schema="dbo" store:Type="Tables" />
<AssociationSet Name="FK_dbo_Student_dbo_Flower_FlowerId" Association="Self.FK_dbo_Student_dbo_Flower_FlowerId">
<End Role="Flower" EntitySet="Flower" />
<End Role="Student" EntitySet="Student" />
</AssociationSet>
</EntityContainer>

由上我们知道:EntityContainer容器的名称是由数据库名称+ModelStoreContainer,并且它是 EntitySet 和 AssociationSet 的容器,而EntityContainer的作用是查询的重要入口,通过暴露EntitySet,来使得我们查询到EntitySet,而EntitySet是实体的集合,所以通过它访问到实体。

EntityType

        <EntityType Name="Flower">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="int" StoreGeneratedPattern="Identity" Nullable="false" />
<Property Name="Remark" Type="nvarchar(max)" />
</EntityType>
<EntityType Name="Student">
<Key>
<PropertyRef Name="Key" />
</Key>
<Property Name="Key" Type="int" StoreGeneratedPattern="Identity" Nullable="false" />
<Property Name="Name" Type="nvarchar(max)" />
<Property Name="FlowerId" Type="int" Nullable="false" />
</EntityType>

实体类型是模型中的数据类型,我们可以看到里面有我们定义的Flower和Student类,并在其节点下列出了其所有属性。

C-S Mapping

    <edmx:Mappings>
<Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2009/11/mapping/cs">
<EntityContainerMapping StorageEntityContainer="DBByConnectionStringModelStoreContainer" CdmEntityContainer="DBByConnectionStringEntities">
<EntitySetMapping Name="Flowers">
<EntityTypeMapping TypeName="DBByConnectionStringModel.Flower">
<MappingFragment StoreEntitySet="Flower">
<ScalarProperty Name="Id" ColumnName="Id" />
<ScalarProperty Name="Remark" ColumnName="Remark" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
<EntitySetMapping Name="Students">
<EntityTypeMapping TypeName="DBByConnectionStringModel.Student">
<MappingFragment StoreEntitySet="Student">
<ScalarProperty Name="Key" ColumnName="Key" />
<ScalarProperty Name="Name" ColumnName="Name" />
<ScalarProperty Name="FlowerId" ColumnName="FlowerId" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
</EntityContainerMapping>
</Mapping>
</edmx:Mappings>

上述EntityContainerMapping中的StorageEntityContainer(存储实体容器)对应存储模型中EntityContainer中的Name,而EntitySetMapping下的EntityTypeMapping中的TypeName则对应概念模型的中EntityType下的Name。也就是说通过StorageEntityContainer来获得存储模型中EntityContainer,接着根据下面EntityType对应的值去获得概念模型中对应的实体,此时映射层中用TypeName来获得概念模型中的实体,接着开始进行一一对应映射,此时MappingFragment(就字面意思暂且叫映射片段),将名称为Name的值映射为对应的列名ColumnName的值。上述大概就是整个映射过程。

这就是我简短的介绍,更多详细内容请参考园友三思而后行翻译的早期EF系列。下面进入实战。

实战

在进入之前,我们先得了解 DataSpace 枚举中的几个概念:

  • CSpace:概念模型的默认名称。
  • CSSpace:概念模型和存储模型之间的映射的默认名称。
  • OCSpace:对象模型和概念模型之间的映射的默认名称。
  • OSpace:对象模型的默认名称。
  • SSpace:存储模型的默认名称。

在EF中我们是无法用DbContext上下文来直接获得表名以及各种属性等等,但是对于这所有我们可以通过上下文ObjectContext中的MetadataWrokSpace属性来操作,因为该属性暴露了GetItems方法来使我们很容易获得我们想要的数据。

那问题来了,如果我们要获得实体的所有键的名称,该如何操作呢?

我们通过给上下文添加一个扩展方法 GetKeyNames 来获取实体所有的键名称,如下:

        static string[] GetKeyNames(this DbContext ctx, Type entity)
{
var metadata = ((IObjectContextAdapter)ctx).ObjectContext.MetadataWorkspace; /*获得CLR type集合和medata OSpace之间的映射*/
var objItemCollection = (ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace); /*根据所给的CLR type获得实体的元数据即metadata*/
var entitydata = metadata.GetItems<EntityType>(DataSpace.OSpace).Single(e => objItemCollection.GetClrType(e) == entity);
/*返回实体的所有键的集合*/
return entitydata.KeyProperties.Select(p => p.Name).ToArray(); }

接下来我们调用如下就获得该实体对应的所有键的集合

 var keyNames = ctx.GetKeyNames(typeof(Student));

通过上述想必你也明白了为什么要讲 EntityType、EntityContainer以及EntitySet 了吧。我们不禁猜想如果要获得CLR types与存储模型之间的映射的集合应该如何操作呢?显然,如下:

var storeItemCollection = (StoreItemCollection)metadata.GetItemCollection(DataSpace.SSpace);

我们查看我们通过映射获得数据库表Flower和Student的情况如下:

那问题又来了,我们该如何获得实体对应的表名呢?

通过上述原理的介绍,既然是表名肯定此时DataSpace枚举必须是存储模型即SSpace,同时我们要获得EntitySet集合中对应的Name,同时表名我们知道默认是dbo,即上述Schema对应的值再加上Table所对应的值,基于此,我们尝试写出相应的代码:

        static string GetTableName(this DbContext ctx, Type entity)
{
var metadata = ((IObjectContextAdapter)ctx).ObjectContext.MetadataWorkspace; var storeItemCollection = metadata.GetItemCollection(DataSpace.SSpace); var entitySetBase = storeItemCollection.GetItems<EntityContainer>().Single().BaseEntitySets.Where(e => e.Name == entity.Name).Single(); string tableName = entitySetBase.MetadataProperties["Schema"].Value + "." + entitySetBase.MetadataProperties["Table"].Value; return tableName;
}

上述代码就不一一解释了,对照edmx就很清楚了,接下来我们进行测试来获得Student的表名:

var tableName = ctx.GetTableName(typeof(Student));

测试通过,如下:

那问题又来了,我们该如何获得实体的所有导航属性呢?

既然是实体的导航属性必定是要在获取到实体的前提下再去获取导航属性,主要通过此 metadata.GetItems(DataSpace.OSpace) 来筛选出实体类型最后查到需要一个内置类型种类 BuiltInTypeKind 。所以我们给出完整代码:

        static IEnumerable<PropertyInfo> GetNavigationPrpoperties(this DbContext ctx, Type entity)
{
var metadata = ((IObjectContextAdapter)ctx).ObjectContext.MetadataWorkspace; var entityType = metadata.GetItems(DataSpace.OSpace).Where(d => d.BuiltInTypeKind == BuiltInTypeKind.EntityType).OfType<EntityType>().Where(d => d.Name == entity.Name).Single(); return (entityType.NavigationProperties.Select(d => entity.GetProperty(d.Name)).ToList()); }

我们依然进行调用,试试能否取到:

var navigationProperties = ctx.GetNavigationPrpoperties(typeof(Student));

结果成功取到Student的导航属性Flower,如下:

总结

上述也就简单的摸索了下底层为edmx的EF,个人感觉了解了基本原理再去写代码以及当代码出现问题时去解决会得心应手,同时了解这些基本原理对于我们在EF实体框架上构建建模工具也是非常有帮助的。

EntityFramework之摸索EF底层(八)的更多相关文章

  1. EF 底层封装方法(供参考)

    闲暇之余,整理了一下EF底层的一些基础方法,供查看,只有接口,具体实现需要你们自己写了. 建议:接口的实现定义为虚方法,当父类的方法不满住子类需求时,可以重写此方法 此接口都为公用方法,基本上满足小系 ...

  2. 采用EntityFramework.Extended 对EF进行扩展(Entity Framework 延伸系列2)

    前言 Entity Framework 延伸系列目录 今天我们来讲讲EntityFramework.Extended 首先科普一下这个EntityFramework.Extended是什么,如下: 这 ...

  3. EF 底层基础方法

    1 using System; 2 using System.Data; 3 using System.Collections.Generic; 4 using System.Data.Entity; ...

  4. 开发笔记:基于EntityFramework.Extended用EF实现指定字段的更新

    今天在将一个项目中使用存储过程的遗留代码迁移至新的架构时,遇到了一个问题——如何用EF实现数据库中指定字段的更新(根据UserId更新Users表中的FaceUrl与AvatarUrl字段)? 原先调 ...

  5. EntityFramework.Extended 对EF进行扩展

    前言 Entity Framework 延伸系列目录 今天我们来讲讲EntityFramework.Extended 首先科普一下这个EntityFramework.Extended是什么,如下: 这 ...

  6. 采用EntityFramework.Extended 对EF进行扩展

    今天我们来讲讲EntityFramework.Extended 首先科普一下这个EntityFramework.Extended是什么,如下: 这是一个对Entity Framework进行扩展的类库 ...

  7. EntityFramework安装和EF升级方法

    例如:如何在vs2010上安装EntityFramework5.0? 首先,需要安装一个vs插件,名称为NuGet Package Manager,微软官方发布的,其作用就是为vs工程项目自动下载.安 ...

  8. EntityFrameWork简单操作 EF数据上下文对象操作数据增删改差及批处理

    /// <summary> /// EF针对 留言数据库 的 数据上下文对象!!!! /// </summary> static LeaveWordBoradEntities ...

  9. EntityFramework Code-First 简易教程(八)-------一对一

    配置一对一(One-to-One)关系: 两个实体中,如果一个实体的一个实例与另一个实体相关,则我们就叫做一对一关系 查看如下代码: public class Student { public Stu ...

随机推荐

  1. 移动端Web页面问题(转载)

    1.安卓浏览器看背景图片,有些设备会模糊.   用同等比例的图片在PC机上很清楚,但是手机上很模糊,原因是什么呢? 经过研究,是devicePixelRatio作怪,因为手机分辨率太小,如果按照分辨率 ...

  2. 安装zabbix-3.0.3+nginx-1.10.1+php-5.6.22

    好久没有接触监控类的软件了,今天抽空搭建了下最新的版本 首先系统环境 zabbix-server-1 192.168.11.11   centos6.7 mysql-server    192.168 ...

  3. perl学习之路3

    Perl编程之路3 标签: perl 列表与数组   Perl里面代表复数的就是列表和数组 列表(list)指的是标量的有序集合, 而数组(array)则是存储列表的变量. 在Perl这两个属于尝尝混 ...

  4. chrome浏览器js 导出excel

    <table id="table"> <tr> <th>ID</th> <th>姓名</th> <th ...

  5. Ubuntu系统下lnmp环境搭建和Nginx多站点配置

    最近需要使用Ubuntu作为服务器搭建Lnmp环境,顺便将操作过程写下来,与大家分享.如有不足之处,欢迎大家提出不同意见.(本文默认读者已经熟悉相关linux命令的使用,比如创建文件和文件夹,编辑文件 ...

  6. 注册GitHub和源程序版本管理软件和项目管理软件的优缺点

    目前市面上主要源程序管理软件主要有:Microsoft TFS(Team Foundation Server).GitHub.Trac.BUGZILLA.Apple XCode.SVN Microso ...

  7. 关于Node.js的总结

    Node是个啥? 1.Node 是一个服务器端 JavaScript 解释器,可是真的以为JavaScript不错的同学学习Node就能轻松拿下,那么你就错了,总结:水深不深我还不知道,不过确实不浅. ...

  8. 思科交换机配置DHCP的四个方面

    这里我们主要讲解了思科交换机配置DHCP的相关内容.我们对网络拓扑先进行一下了解,然后对于其在进行一下说明,之后对于配置的代码和命令再进行一下解析. 思科交换机配置DHCP一.网络拓扑 思科交换机配置 ...

  9. Linux 部署ASP.NET SQLite 应用 的坎坷之旅 附demo及源码

    Linux 部署ASP.NET SQLite 应用 的坎坷之旅.文章底部 附示例代码. 有一台闲置的Linux VPS,尝试着部署一下.NET 程序,结果就踏上了坑之路,不过最后算是完美解决问题,遂记 ...

  10. Apache许可协议Open RIA Services

    Jeff Handley's进行了多年的项目--基于一份开源许可发布WCF RIA Services.遵循Apache 2许可,捐赠给Outercurve基金会的ASP.NET Open Source ...