EntityFramework之摸索EF底层(八)

简介:

前言

此篇文章我将深入去摸索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实体框架上构建建模工具也是非常有帮助的。













本文转自Jeffcky博客园博客,原文链接:http://www.cnblogs.com/CreateMyself/p/4783027.html,如需转载请自行联系原作者

目录
相关文章
|
存储 Dragonfly 弹性计算
2023年阿里云服务器4核8G配置收费标准与活动价格参考,价格1450.08元1年起
4核8G配置的云服务器通常是普通企业用户的首选配置,2023年新用户租用阿里云轻量应用服务器低至108元首年,4核8G配置目前活动价格仅需1450.08元1年起,不过阿里云不同实例类型的4核8G云服务器配置,产品价格也各不相同。而在平时购买和活动期间买价格也是不一样的。本文主要为大家介绍目前阿里云服务器4核8G配置收费标准与活动价格,以供参考。
872 0
2023年阿里云服务器4核8G配置收费标准与活动价格参考,价格1450.08元1年起
|
存储 弹性计算 运维
阿里云经济型e系列云服务器测评,专为中小应用打造
2023年9月,阿里云推出了一款全新云服务器实例,经济型e实例,基于“飞天+CIPU”黄金技术架构设计,可轻松满足网站建设、开发测试和小型应用构建等场景需求,使用成本最低可降至每天0.5元,告别复杂的选型和高昂的成本,进一步降低了学生群体、个人开发者和小微企业的上云门槛。
2737 0
阿里云经济型e系列云服务器测评,专为中小应用打造
|
10月前
|
缓存 前端开发 UED
React 侧边栏组件 Sidebar
本文介绍了如何使用React创建交互式侧边栏组件,涵盖基础结构、状态管理、样式设计等方面。通过`useState`钩子控制侧边栏的展开与收起,并利用CSS实现动画效果。同时,文章还探讨了响应式设计、性能优化、可访问性和路由集成等常见问题及解决方案,帮助开发者构建高效、美观且易于维护的侧边栏组件,提升Web应用的用户体验。
309 8
|
存储
【机组期末速成】指令系统|机器指令概述|操作数类型与操作类型|寻址方式|指令格式
【机组期末速成】指令系统|机器指令概述|操作数类型与操作类型|寻址方式|指令格式
327 1
|
机器学习/深度学习 人工智能 监控
低代码平台的崛起:开发的未来还是过渡阶段?
低代码平台通过可视化界面和预构建模块,让非技术用户也能快速开发应用,引起广泛关注。其兴起源于快速应用开发需求、技术人才短缺及业务与IT融合。然而,定制化限制、性能问题和依赖性是主要挑战。未来,低代码平台将提升技术成熟度,集成更多先进技术,并提供个性化服务,在软件开发中扮演更重要角色。
|
人工智能 自动驾驶 算法
《人工智能伦理:机器的自主性与人类的责任》
随着人工智能(AI)技术的飞速发展,其应用已渗透到社会的各个领域。然而,AI技术的快速发展也带来了一系列伦理问题,特别是关于机器自主性与人类责任的界定。本文将探讨AI的伦理挑战,分析机器自主性的发展趋势,以及在AI决策过程中人类应承担的责任。通过案例分析和数据支持,我们将深入讨论如何在保障技术进步的同时确保伦理标准的制定和遵守,以实现人机和谐共存的未来。
389 27
|
移动开发 监控 前端开发
如何从0-1的建设云上稳定性?
本文将从前后端的视角整体看下我们在云上稳定性治理的一些路径和经验。首先从平台的系统架构模型出发,站在全局视角看下整个平台的风险。
|
存储 JavaScript 前端开发
Vue中通过集成Quill富文本编辑器实现公告的发布。Vue项目中vue-quill-editor的安装与使用【实战开发应用】
文章展示了在Vue项目中通过集成Quill富文本编辑器实现公告功能的完整开发过程,包括前端的公告发布、修改、删除操作以及后端的数据存储和处理逻辑。
Vue中通过集成Quill富文本编辑器实现公告的发布。Vue项目中vue-quill-editor的安装与使用【实战开发应用】
|
SQL Java 数据库连接
MyBatis关联关系映射详解
MyBatis关联关系映射详解
500 0
|
运维 网络安全 网络架构
资深网工是如何发现大型网络中网络环路问题的?
资深网工是如何发现大型网络中网络环路问题的?
214 0