zoukankan      html  css  js  c++  java
  • [转]Entity Framework走马观花之把握全局

    学习Entity Framework技术期间查阅的优秀文章,出于以后方便查阅的缘故,转载至Blog,可查阅原文:http://blog.csdn.net/bitfan/article/details/12887007

    在深入学习某项技术之前,应该努力形成对此技术的总体印象,并了解其基本原理,本文的目的就在于此。

    一、理解EF数据模型

             EF本质上是一个ORM框架,它需要把对象映射到底层数据库中的表,为此,它使用了三个模型来描述这种映射关系。

             (1)概念模型(Conceptual Model):主要体现为一组可以被应用程序直接使用的类。这些类也是我们在程序中直接使用的类,通常称之为“实体(Entity)”

             (2)存储模型(Storage Model):主要体现为一组与底层数据存储介质(比如数据库系统)直接对应的类。

             (3)概念-存储模型映射(Conceptual- Storage Mapping),解决“概念模型”中的类如何与“存储模型”中的类相互对应的问题。

             (2)和(3)中的类型由EF内部使用,在实际开发中通常触及不到。

             所有这三种模型都集中放在名为edmx文件中,以XML方式表达。

             VisualStudio提供了一个向导,完成从现有数据库到EF数据模型间的映射转换工作:

     

    当此向导完成之后,EF成功地在数据库与程序中使用的对象之间建立了以下对应关系:

    关系数据库的世界

    数据库应用程序的世界

    数据库

    DbContext类

    DbContext中的DbSet<实体类名>

    表间的关联

    实体类之间的关联

    表中的字段

    实体类的公有属性

    表中的单条记录

    单个实体类的对象

    视图

    DbContext中的DbSet<视图名称>

    存储过程

    DbContext中的公有方法

     

    可以在Visual Studio 的模型浏览面板中看到EF数据模型的概念模型和存储模型,如果直接以xml格式打开.edmx文件,可以看到“原汁原味”的全部三大数据模型。

    Visual Studio提供的EF向导不仅生成了上述三大数据模型,还使用T4代码模板(其文件扩展名为.tt)直接生成了相应实体类,还有一个派生自DbContext的子类,在其中包容了所有实体集合属性和导入的存储过程等数据库元素。这一DbContext子类是我们使用EF开发程序的核心类型。

    在解决方案资源管理器中双击“edmx”文件将打开EF设计器,这是一个很强悍的工具,几乎所有的调整数据映射关系的工作都可以使用它来完成。

    如果基于Database First或Model First方式开发,那么EF设计器是天天要打交道的东西。其操作方式很简单:右击设计器,从弹出菜单中选择相应命令

    EF设计器提供的各种命令和具体使用方法有很多资料介绍,在此就不废话了。

    二、使用EF访问数据库的基本方式

    EF中可以使用以下四种方式访问数据库:

     

            

            Entity SQL是专门为EF设计的一种查询语言,非常类似于通用的关系型数据库查询语言SQL,但它返回的数据都是在EF“概念模型”中所定义的,而非数据库中数据的“真实模样”。

             LINQ to Entities可以看成是LINQ  to  Objects的一个“变种”,通过LINQ来查询EF数据模型。它在底层使用“对象服务(Object services)”来完成其功能。对象服务是一组用于查询实体数据模型的类,它可以将这些查询结果转换为强类型的CLR对象。

             不管是使用Entity SQL还是LINQ to Entities,最终都是依赖“Entity Client”来完成其工作的。

             Entity Client包容一组类,比如EntityConnection、EntityDataReader等,与ADO.NET对象模型非常类似,其功能也类似。Entity Client会将对数据的CRUD请求转发给ADO.NET数据提供者(ADO.NET Provider)组件,由其将相关SQL命令直接地发送给数据库。

           在实际开发中,大家都使用LINQ to Entities和针对IEnumerable<T>/IQueryable<T>的一组扩展方法完成数据查询工作,几乎不会有人直接使用Entity SQL和 Entity Client。

       下图展示了数应用程序运行过程中EF查询的内部处理流程:

     

     

        可以看到,使用EF的数据查询从发出到真正执行要经过两次“命令树(Command Tree)转换”,而从数据库中读取的数据,也要经历从DbDataReader-->EntityDataReader-->IEnmerable<T>的转换。虽然EF采用了缓存、预编译等手段提升性能,但与ADO.NET相比,由于中间处理环节更多,整个查询处理流程中参与的对象也更多,因此总体性能一般比不上ADO.NET,并且会占用更多的内存,这也我们是在享用EF带来的方便的同时,所需要付出的代价。

    另外,由于所有查询最终还是要转换为SQL命令,因此,有些LINQ to Objects可用的扩展方法,比如Last(),在EF中将不能用,因为EF不知道如何把它们翻译成底层数据库支持的SQL命令。

    三、三种开发模式的PK

    EF支持三种开发模式:

    Code First、Database First和Model First。

    到底应该用那一种模式是令人纠结的问题。

    方式一:Code First

    对于初次接触的人,EF的Code First实在很有点魔幻色彩。下面就让我们来体会一下。

    创建两个类:Book(书)和BookReview(书评)。一本书可以有多条书评,因此,它们是一对多的关系:

    public class Book
    
        {
    
           public virtual int Id {get;set;}
    
           public virtual string Name { get; set; }
    
           public virtual List<BookReview> Reviews { get; set; }
    
        }
    
       public class BookReview
    
        {
    
           public int Id{get;set;}
    
           public int BookId { get; set; }
    
           public virtual string Content { get; set; }
    
           public virtual Book AssoicationWithBook { get; set; }
    
        }

    好了,现在创建一个派生自DbContext的子类:

    public class BookDb : DbContext
    
        {
    
           public DbSet<Book> Books { get; set; }
    
           public DbSet<BookReview> Reviews { get; set; }
    
    }

    现在可以在程序中随意写几行代码从数据库中提取数据:

    static void Main(string[] args)
    
           {
    
               using (var context = new BookDb())
    
               {
    
                    Console.WriteLine("数据库中有{0}本书",context.Books.Count());
    
               }
    
           }

    运行一下,如果计算机上安装有SqlExpress,那么或者是在应用程序文件夹,或者是打开SQL Server Management Studio(SSME)查看本机SQLServer,你就会发现,数据库己经创建好,其中的表及表的关联也帮助你完成了:

     

    貌似我什么也没干,一切就OK了,神奇啊!

     现在修改Book类,给它添加一个Authors属性,代表书的作者:

    public class Book
    
        {
    
           public virtual int Id {get;set;}
    
           public virtual string Name { get; set; }
    
           public virtual string Authors { get; set; }
    
           public virtual List<BookReview> Reviews { get; set; }
    
    }

    有了前面良好的第一印象,你一定以为只要再次运行程序,底层数据库就会自动更新,然而,EF会给你当头一棒让你清醒:

     

        很明显,因为你修改了实体类,数据库结构也需要修改,比较郁闷的是,你不能打开创建好的数据库直接修改,而需要使用一个名为“数据库迁移(Database Migration)”的功能,采用两种方式(自动迁移和手动迁移)之一完成。

    以自动迁移为例:

        首先从从Tools菜单中打开Package Manager Console,然后键入:

    enable-migrations –EnableAutomaticMigrations

        上述命令会在项目中添加一个Migrations文件夹,其中会有一个Configuration类,为了方便,你需要在其构造函数中添加“AutomaticMigrationDataLossAllowed = true;”一句,让其自动重建数据库时不理会可能的数据丢失:

    internal sealed class Configuration :DbMigrationsConfiguration<EFCodeFirst.BookDb>
    
        {
    
           public Configuration()
    
           {
    
               AutomaticMigrationsEnabled = true;
    
               AutomaticMigrationDataLossAllowed = true;
    
           }
    
    ……
    
    }

            好了,现在运行update-database命令更新数据库:

     

            再次运行程序,现在将一切OK。

            以后每次更改实体类,都必须手动运行update-database命令更新数据库。

            手动迁移方式与自动迁移基本一致,不同之处在于它会记录每次更新的情况,从而允许回滚数据库到某个“较老”的版本。

            自动方式比较适合于单个人写的应用。而手动方式可以控制数据库结构的更新,比较适合于团队开发。

    Code First模式的优点。

    从上面的介绍,可以看到Code First有着突出的优点。

    (1)是代码清洁,添加自定义逻辑容易。

            这是使用Code First最大的好处

            特别是在诸如WPF这种可以保持长连接的桌面应用中,可以让实体类实现INotifyProperty接口,将它们放入ObservableCollection中作为UI界面的绑定数据源,便可以充分利用WPF的数据绑定和EF自动维持实体状态的特点,大幅度地削减代码。

    (2)易于实现继承

            Code First在实现继承上非常方便,如果数据实体中有大量的继承的情况,使用Code First很方便,但需要注意的是Code First在生成数据库表时,默认使用“TPH:Table Per Hierarchy”方式,把父类子类塞到同一张表中,并在表中添加一个Discriminator字段,表明此记录所属的具体类型。

            以下是CodeFirst为拥有继承关系的两个Parent/Child类生成的数据库表,可以看到,Discriminator字段保存了具体的数据类型。

          有些朋友问能否把Discriminator字段名给改掉,很遗憾,我没发现可以修改它的方法。

           很明显,Code First采用的这种实现策略违反了关系数据库设计范式,对大项目来说,这不易于维护数据的一致性。

           实现继承的另外一种方式是TPT(table pertype ),不管子类父类,每个类型一张表。

          比如有三个类,Instruct和Student派生自Person,若采用TPT策略,EF将生成三个表,并创建以下的关联:

            对于TPH,手写SQL代码很容易,性能高,但对于TPT,EF在生成SQL命令时会产生许多Inner Joint,性能低,另外,对于这种方式存储的数据,手写SQL代码比较麻烦。

             Database First和Model First默认情况下都是采用TPH的。

            我的建议:除非两实体间确实是IS_A关系,并且在中间层需要使用多态,在“数据存取层(DAL)”尽量少用继承,别自找麻烦

            (3)一些开发高手们还为EF提供了一个EntityFramework Power Tools,这一工具增强了Code First的不少功能,比如它可以从现有数据库直接逆向生成实体类代码,之后就可以修改这些代码,为Code First方式进行开发省去了不少编码工作。同时,它还能为编写的实体类代码生成只读的数据模型视图,以图形的方式展示出实体类间的关联。

    Code First存在的问题

            CodeFirst试图“用代码搞掂一切”,其问题在于以下几点:

           (1)当Model改变时,往往需要编写代码实现数据库的更改,远不如直接使用数据库所提供的工具修改数据库安全和直观,至少丢失数据的可能性小了很多。

           (2)当数据实体间有复杂的关联时,需要使用Fluent API手动编写不少代码定义类之间的关联,这实在麻烦。

           (3)对数据库表和关联属性的一些微调(比如改改字段名字,修改字段长度限制等),使用数据库设计工具能轻易实现,但Code First只能通过代码来完成,而且必须使用EF的数据迁移特性,这实在麻烦,整个过程还容易出错。

             简而言之,Code First应用的场景是:快速开发,迅速迭代

    方式二:Database First

            这是EF从1.0开始就支持的特性,其思路是:先设计并建好数据库,然后使用Visual Studio的向导创建EF数据模型并生成实体类代码。

           这是最成熟稳定的方式,其设计器相当地完善,基本上能满足实际开发中的各种需求。

           我个人认为这是开发正式项目最合适的方式。

           当然,DatabaseFirst也有一些问题,主要是需要定制时会有些麻烦。比如:

           (1)要想给实体类或生成的DbContext子类添加一些自定义的逻辑,需应用分部类,因为每次更新数据模型,这些代码都会被设计器覆盖并重写。

            (2)生成的实体类中不包括任何Data Annotation(所谓“Data Annotation”就是附在实体类代码上的诸如[Required]之类的东东),因此它需要被转换为另一个类,才能方便地在诸如ASP.NET MVC之类的项目中使用(比如ASP.NET MVC项目中的视图模型(ViewModel)类往往需要有Data Annotation,以配合jQuery Validation插件生成网页上的数据验证代码)。

            (3)默认情况下实体类与edmx文件放在同一个项目中,想将实体类分离到独立的项目,需要完成一些额外的配置工作(主要是把T4模板文件移到另一个项目,这需要适当地修改T4模板文件中的代码以保证文件路径引用正确)。

    方式三:Model First

          这种模式是先在可视化设计器中创建实体和它们间的关联,然后设计器生成SQL命令并保存于一个SQL文件中,通过执行这一SQL文件完成数据库的创建和修改工作

            我个人感觉:

            这种方式最适合于全新开发的项目,从系统分析开始,逐步分析建立和完善领域模型,之后可以立即创建数据库,如果模型有修改,重新生成一个SQL文件,再执行一次即可,非常适合于新项目OOAD阶段的需要。

           当进入OOP阶段时,由于数据库己经存在,就可以很方便地转用Database First方式,整个过程流畅自然。

    三种模式PK结果:

    Code First:对于小的或用于试验的项目,特别是像我经常要讲课的,教学实例使用Code First开发就比较合适,当程序运行时数据库自动生成,比较省事。

    DB First:最为成熟,是正规项目的首选方式,因为是由开发者自己(而不是通过一堆“不太可靠”的代码)直接操作数据库,整个过程高度可控,能很好地保证数据安全,贯彻了“数据比代码重要”的理念。

    Model First:当开始一个全新的项目,既没有DB,也没有代码时则非常好,是OOAD的好工具,需要时甚至可以直接生成创建各种不同类型数据库(比如MySQL)的SQL代码!

  • 相关阅读:
    密码 (pasuwado)
    bzoj 4131: 并行博弈 (parallel)
    Beads
    bzoj2338数矩形(rectangle)
    数树数
    最近公共祖先(lca)
    在python中遍历字典元素
    加载本地json文件,并利用批处理调用Chrome显示html
    numpy保存数据
    Echarts 地理信息可视化:基于地图显示坐标点信息
  • 原文地址:https://www.cnblogs.com/colder/p/4188619.html
Copyright © 2011-2022 走看看