实体框架

实体框架的使用分为Model First,Code First(代码生成模型),Database First。Model First和Database First会使用实体设计器(edmx文件)来创建实体数据模型。

DbContext所使用的连接字符串如果是常规连接字符串在使用Code First,如果使用的是特殊的实体框架连接字符串,则使用Database First或Model First。

Database First:

从现有数据库构建edmx文件,然后edmx会生成DbContext和poco模型(model类),并将实体框架专用的连接字符串保存到所在项目的app.config中,但配置文件中的连接字符串并不能直接使用,连接字符串中包含有指向csdl,ssdl,msl的路径,自动生成的连接字符串中这3条路径包含一个“*”号,需将edmx所在类库的程序集名称替换“*”号。

Model First:

建立空的edmx文件,手动构建数据库模型,再由根据模型创建数据库和生成DbContext以及poco模型(model类)

Code First:

手写POCO,再由DbContext生成对应的数据库。

POCO中外键和导航属性应用virtual修饰(详情见导航属性的加载),以便于重写。集合类型的导航属性应在构造方法中初始化为HashSet集合。

IDatabaseInitializer<TContext>(TContext是DbContext类型的实例),用于指定DbContext初次使用时用于初始化数据库的策略,使用策略徐调用DbContext的DataBase属性(System.Data.Entity.Database实例)的SetInitializer方法,或者在Global.asax的Application_Start方法中直接调用Database.SetInitializer静态方法,或者在DbContext的构造方法中执行Database.SetInitializer静态方法,或者在配置文件的entityFramwork/contexts的子节点配置,示例如下:

<entityFramework>

<contexts>

<context type="[DbContext类],[程序集名称]">

<databaseInitializer type="[IDatabaseInitializer实现类],[程序集名称]" />

</context>

</contexts>

</entityFramework>

可使用的初始化策略有DropCreateDatabaseIfModelChanged<TContext>,表示如果模型改变,则删除并重建数据库,Database的CompatibleWithModel方法可用于检查模型是否发生改变;DropCreateDatabaseAlways<TContext>,表示每次使用DbContext都重建数据库;CreateDatabaseIfNotExists<TContext>,表示如果数据库不存在则创建数据库。用户选择使用策略时最好继承者三个类中的一个并重写Seed方法,该方法会在数据库创建后执行,此方法成为种子方法。

System.Data.Entity.Database类,可通过DbContext的Database属性获取Database类的实例,该实例可用于检查数据库的状态,如数据库是否存在,模型是否改变,创建或删除数据库操作,执行sql脚本等操作。

对模型的定义可通过System.Component.DataAnnotations命名空间的特性,也可以通过Fluent API,Fluent API 的使用工具是DbModelBuilder类,此类可在DbContext的OnModelCreating方法的参数获得,通过Fluent API对模型映射所做的修改如同使用Data Annotation对模型所作的修改一样都会在数据迁移DbMigration中做出相应的反应,即数据迁移支持Fluent API和Data Annotation对模型的映射设置和修改。

Code First约定:

注:

主表(主体):具有主键的表。

从表(依赖实体):使用主表的主键作为本表的外键。

主键约定:类的“ID”名称的属性或“<类名>ID”的属性默认为模型的主键。

关系约定:模型的外键关系通过导航属性来推断。建议显示指定外键属性,外键的属性名称为“<导航属性名称>_<主体主键属性>”(如果外键属性是自动生成的,则导航属性名称与主体主键属性之间有下划线,如果是手写的可有可无)、“<主体类名><主体主键名称>”、“<主体模型名称>”。外键属性可以为null,则外键关系是可选的,并设置为级联删除,否则为必选的,并设置为非级联删除。

对于无法推断出主键的引用类型的属性将作为复杂数据类型,在数据库中的体现是<复杂数据类型的属性名>_<复杂数据类型的类属性>。

当模型的导航属性无法在导航属性对应的另一个模型中找到合适的或应该的导航属性时,可使用InversePropertyAttribute手动指定导航属性对应的另一个模型中的导航属性。

当需要手动指定导航属性在模型中对应的外键属性时,使用ForeignKeyAttribute。

注意:

通过DbContext的访问类集合,返回的结果包括指定的类以及派生类,此结果在TPT策略下,通过执行外联接实现,在TPC下通过union操作实现,而正因此,导致在同时使用数据库的标识列和TPC策略下,派生类(可能多个)对应的表和基类对应的表中的记录不能具有相同的主键值,否则会导致DbContext内部调用的ObjectContext会创建重复的实体键,详情见TPC说明。

Fluent API的表映射策略:

每个层次结构一张表(TPH)(Fluent API的默认策略),每个类型一张表(TPT),每个具体类一张表(TPC);

单个实体对应多个表(实体拆分),将多个实体映射到一个表(表拆分)

TPH  Table per Hierarachy:每个层次结构一张表,表示继承乘此结构中的所有类型使用同一张表,这张表使用鉴别器列区分每行对应的模型,这个鉴别器列是对应鉴别器表“Discrimination”的外键,鉴别器表中具有鉴别器值,此值即为模型的类型名称。

modelBuilder.Entity<FatherClass>()

.Map<SonClass1>(m=>m.Requires(“Type”).HasValue(“SonClass1”))

.Map<SonClass2>(m=>m.Requires(“Type”).HasValue(“SonClass2”))

如果要求多态关系或查询,并且子类声明了很少的属性甚至子类的不同在于行为的话,建议使用TPH。目标是尽可能的减少列,并且在长期需求中这种非标准的结构不会引发问题。

TPH策略中,基础类型的字段会被用设为可为空,这会导致不可为空的外键属性因成为可为空而引发问题。

TPT  Table per Type:每个类型一张表,表示继承结构中的基类和派生类分别对应一张表,表中只映射对应类中定义的属性,不映射继承的属性,派生类对应的表和基类对应的表通过外键进行关联,以表示模型的继承的关系。

modelBuilder.Entity<SonClass1>().ToTable(“SonClass1”);

modelBuilder.Entity<SonClass2>().ToTable(“SonClass2”);

如果要求多态关系或查询,并且子类定义了较多的属性的话,建议使用TPT,或者如果在继承结构较复杂的情况下,join查询比union查询较节省资源的话,使用TPT,反之使用TPC。

TPC  Table per Concrete Type:每个具体类一张表,表示继承层级中的基类和派生类各自对应一张表,基类对应的表与派生类对应的表没有外键关系,派生类的属性包括继承的属性均映射到数据表中。使用MapInheritedProperties方法指示将继承的属性映射到数据表。此策略与数据库的标识列存在矛盾的情况,这个表映射策略会因为多个派生类以及基类具有同名称的标识列类型的主键属性,导致DbContext内部使用的ObjectContext的跟踪机制创建了重复的实体键(EntityKey)从而导致了数据插入失败,可以关闭标识列或设置为其他类型主键来手动设置主键属性来规避这个异常。

modelBuilder.Entity<FatherClass>()

.Property(c => c.ID)

.HasDatabaeGeneratedOption(DatabaseGeneratedOption.None)//在同时保存父类和子类时虽数据表没有外键关联,但因为继承关系会引发主键相关的异常,修改为手动设置主键则不会有此问题。

modelBuilder.Entity<SonClass1>().Map(m => {

m.MapInheritedProperties();

m.ToTable(“SonClass1”);

})

modelBuilder.Entity<SonClass2>().Map(m=>{

m.MapInheritedProperties();

m.ToTable(“SonClass2”);

})

如果不要求多态关系或查询,建议使用TPC,当很少查询基类或者很少有其他类类与基类关联,并且基类很少改动的情况下建议对继承树的高层使用TPC。

注意:使用TPC的模型所继承的属性不能来自这样的基类,这个基类进行了实体拆分,或所处的表进行了表拆分。

实体拆分:将单个实体映射到多个表,通过多次调用Map方法,将不同的属性分配到不同的表中。

modelBulder.Entity<Department>()

.Map(m=>{

m.Properties(t=>new{t.DepartmentID,t.Name});

m.ToTable(“Department”);’

})

.Map(m=>{

m.Properties(t=>new{t.DepartmentID,t.Administrator,t.StartDate,t.Budget});

m.ToTable(“DepartmentDetails”);

});

表拆分:将多个实体映射到一个表,通过指定相同的主键将两个实体映射到同一个表

modelBuilder.Entity<OfficeAssignment>().HasKey(t => t.InstructorID);

modelBuilder.Entity<Instructor>().HasRequired(t=>t.OfficeAssignment).WithRequriedPrincipal(t=>t.Instructor);

modelBuilder.Entity<OfficeAssignment>().ToTable(“Instructor”);

modelBuilder.Entity<Instructor>().ToTable(“Instructor”);

表关系映射与导航属性:

使用HasMany、HasRequired、HasOptional方法设置设置模型的导航属性的类型为多、单个必须、单个可选,这些方法返回的OptionalNavigationPropertyConfiguration类中With开头的方法用于设置关系类中导航属性和主体与依赖对象的关系。

略:普通的一对一与一对多关系示例代码

多对多关系:

modelBuilder.Entity<Model1>().HasMany(t1=>t1.NaviPropertyCollection).WithMany(t2=>t2.NaviPropertiyCollection)

多对多关系的表之间会自动增加一个关系表。

modelBuilder.Entity<Model1>()

.HasMany(m1=>m1.Model2Collection)

.WithMany(m2=>m2.Model1Collection)

.Map(m=>{

m.ToTable(“Many1ToMany2”)//指定关系表的表名

m.MapLeftKey(“Many1ID”);//指定关系表中对应设置表的外键列名

m.MapRightKey(“Many2ID”);//指定关系表中对应设置表相关联的关系表的外键列名

});

级联删除:

如果外键不可为空,则默认级联删除,否则,主体删除后,外键置为null

modelBuilder.Convertions.Remove<OneToManyCascadeDeleteConvertion>();//移除当一对多必选关系时默认的级联删除选项

modelBuilder.Convertions.Remove<ManyToManyCascadeDeleteConvertion>();//移除当多对多必选关系时默认的级联删除

使用WillCascadeOnDelete方法手动设置是否级联删除

modelBuilder.Entity<TModel>()

.HasRquired(t=>t.NaviProperty)

.WithMany(anotherT=>anotherT.NaviPropertyCollection)

.HasForeighKey(t=>t.ForeighId)

.WillCascadeOnDelete(false)//设置为不级联删除

DbContext编写:

添加公共的DbSet类型或IDbSet类型的属性会由DbContext默认调用设置DbSet实例,如:

public IDbSet<TModel> TModels{get;set;}

public DbSet<TModel> TModels{get;set;}

访问实例也可通过Set方法,下面两行代码等效:

DbSet<TModel> result = dbContext.TModels;

DbSet<TModel> result = dbContext.Set<TModel>();

在访问DbSet属性但又仅仅进行只读操作时,为了提高性能可禁用跟踪,通过DbSet实例的AsNoTracking方法禁用追踪。

实体关系的修改:

实体关系可通过外键属性或导航属性进行修改,但只有执行DbContext的SaveChange后,外键属性以及导航属性才会反应真是的情况,如,将实体的导航属性进行了设置,这是对应的外键属性(如果有的话)可能为0(或其他表示未指定的值),只有执行SaveChange方法后,导航属性对应的外键值才为关联的模型的主键值。外键属性与导航属性的同步会在DetectChanged方法来完成,以下方法会自动调用DetectChanges方法:

DbSet.Add,DbSet.Find,DbSet.Remove,DbSet.Local,DbContext.SaveChange,DbContext.Attach,DbContext.GetValidationErrors,DbContext.Entry,DbChagne.Tracher.Entries,DbSet执行的linq查询。

删除关系可通过将导航属性设置为null的方式进行,亦或执行如下代码:

dbContext.Entry(tModel).Reference(c=>c.NaviProperty).CurrentValue=null;

通过ObjectContext的ObjectStateManager.ChangeRelationshipState方法也可修改关系:

((IObjectContextAdapter)dbContext).ObjectContext.ObjectStateManager.ChangeRelationshipState(model,anotherModel,m=>m.AnotherModel,EntityState.Added);//EntityState.Deleted表示删除,如果是更新关系的话需要Entity.Added新关系并Deleted旧关系。

DbSet.Local属性说明:该属性包含所有上下文中的对象,包括哪些Add的但未保存到数据库的对象;但不包括Remove的但仍在数据库中的对象。

导航属性数据的加载:

导航属性对应的数据默认是惰性加载(在访问时才加载),如果希望查询数据时一同加载关联属性,须调用DbSet的Include方法并指定要求预加载的导航属性数据。

Var result = dbContext.Models.Include(b=>b.NaviProperty);//单级加载

Var result = dbContext.Models.Include(“NaviProperty.SecondNaviProperty”);//多级加载,加载Model的导航属性NaviProperty以及导航属性的导航属性SecondNaviProperty。

导航属性的惰性加载在首次访问时自动进行加载,加载通过继承自模型类的代理类进行,该代理类会重写导航属性,因此,模型类的导航属性必须用virtual修饰,不适用virtual修饰意味着惰性加载的关闭,在DbContext的构造方法中调用Configuration属性(DbContextConfiguration实例)的LazyLoadingEnabled=false也为这惰性加载的关闭。

显示加载通过DbReferenceEntry的Load方法执行:

dbContext.Entry(model).Reference(m=>m.NavProperty).Load();

在更改外键属性后,可通过Load的方法显示加载同步的导航属性数据。通过dbContext的Entry访问代表集合类型的引导属性的DbCollectionEntry的Query方法获取可操作的IQueryable,来实现对部分试题的导航数据进行加载的要求。

代理类:

在为POCO实体类型实例化是,实体框架会动态生成派生词POCO实体的类,成为代理类,这些代理类通过重写POCO实体的virtual成员来实现一些自动操作,如之前提到的惰性加载。DbContext的Configuration属性(DbContextConfiguration实例)的ProxyCreationEnabled=false可将关闭代理类的生成。

实体的管理:

添加实体可通过DbSet的Add方法,也可通过dbContext的Attach方法并更改返回的Entry的状态为Added的方式。

如果仅仅希望保存实体的某个字段的修改到数据库的话,可通过Entry方法获取的DbEntityEntry实例并通过Property方法获取DbPropertyEntry对象实例并修改IsModified属性为true,这样就可以在保存数据库时,仅仅将IsModified=true的属性保存到数据库。

在调用SaveChange时,可能会引发DbUpdateConcurrentException,这表示更新时的并发异常,可通过dbContext.Entry(model)获取修改实体的DbEntityEntry实例,修改器OriginValue和CurrentValue的方式来尝试消除引发并发的数据,DbEntityEntry的GetDatabaseValue方法可用来获取当前数据库中的数据,在重写原始值(OriginValue)或当前值(CurrentValue)时可能会使用到。

>>编辑时间2015-04-25

时间: 2024-10-07 06:13:21

实体框架的相关文章

011.EF实体框架(入门)

1>ASP.NET MVC开发中模型的实现方法 模型的实现方法 两种方法1手动2自动(工具 EF)1.1手动创建模型(写好模型元数据)[练习过了 项目!] 前提条件:先手动创建数据库和表 (模型:实体类,实体访问类,Sqlhelper) (Sqlhelper使用ADO.NET传统方法实现) 1.2使用EF(Entity Framework:实体框架)创建模型 2>EF的概念2.1什么是EF***EF是 实体框架 (Entity Framework)的简称,***EF 可以用来定义模型类并操作数

实体框架高级应用之动态过滤 EntityFramework DynamicFilters

实体框架高级应用之动态过滤 EntityFramework DynamicFilters 我们开门见山,直奔主题. 一.EntityFramework DynamicFilters 是什么,它能做什么? EntityFramework DynamicFilters是一个开源项目.你可以到这里去下载它的源码.顾名思义,它为我们做的事,就是帮我们动态过滤数据.为了照顾初学者,我们从头道来. 1.何为数据过滤? 数据过滤说简单点,就是去掉我们不想要的数据.SQL语句中的where从句,Linq中的wh

实体框架6.0(Recipes)翻译系列 1 -----第一章 开始使用实体框架1

微软的Entity Framework 受到越来越多人的关注和使用,Entity Framework7.0版本也即将发行.虽然已经开源,可遗憾的是,国内没有关于它的书籍,更不用说好书了,可能是因为EF版本更新太快,没人愿意去花时间翻译国外关于EF的书籍.使用Entity Framework开发已经有3年多了,但用得很肤浅,最近想深入学习,只好找来英文书<Entity Framework 6 Recipes>慢慢啃.首先需要说明的是,我英文不好,只是为了学习EF.把学习的过程写成博客,一是督促自

在快速自定义的NopCommerce中使用实体框架(EF)代码优先迁移

我看到很多nopCommerce论坛的用户问他们如何使用Entity Framework(EF)代码优先迁移来自定义nopCommerce,添加新的字段和entites核心.我实际上在做nopCommerce定制项目时使用了很多EF Migrations,我必须说它在开发中有很大帮助. 今天,我将与大家分享如何在nopCommerce项目中做到这一点!我将使用nopCommerce 3.20作为例子,但你可以很容易地应用这个概念到其他的vesions! 原文链接:http://www.nopcn

C# 6 与 .NET Core 1.0 高级编程 - 38 章 实体框架核心(上)

译文,个人原创,转载请注明出处(C# 6 与 .NET Core 1.0 高级编程 - 38 章 实体框架核心(上)),不对的地方欢迎指出与交流. 章节出自<Professional C# 6 and .NET Core 1.0>.水平有限,各位阅读时仔细分辨,唯望莫误人子弟. 附英文版原文:Professional C# 6 and .NET Core 1.0 - 38 Entity Framework Core ------------------------------- 本章内容 En

《Entity Framework 6 Recipes》翻译系列(2) -----第一章 开始使用实体框架之使用介绍 (转)

Visual Studio 我们在Windows平台上开发应用程序使用的工具主要是Visual Studio.这个集成开发环境已经演化了很多年,从一个简单的C++编辑器和编译器到一个高度集成.支持软件开发整个生命周期的多语言环境. Visual Studio以及它发布的工具和服务提供了:设计.开发.单元测试.调试.软件配置和管理.构建管理和持续集成等等.很少有开发人员因为还没有使用它而担心(注:作者应该是表达不用担心VS的能力),Visual Studio是一个完整的工具集.Visual Stu

《Entity Framework 6 Recipes》翻译系列 (1) -----第一章 开始使用实体框架之历史和框架简述 (转)

微软的Entity Framework 受到越来越多人的关注和使用,Entity Framework7.0版本也即将发行.虽然已经开源,可遗憾的是,国内没有关于它的书籍,更不用说好书了,可能是因为EF版本更新太快,没人愿意去花时间翻译国外关于EF的书籍.使用Entity Framework开发已经有3年多了,但用得很肤浅,最近想深入学习,只好找来英文书<Entity Framework 6 Recipes>第二版,慢慢啃.首先需要说明的是,我英文不好,只是为了学习EF.把学习的过程写成博客,一

EF实体框架之CodeFirst一

对于SQL Server.MySql.Oracle等这些传统的数据库,基本都是关系型数据库,都是体现实体与实体之间的联系,在以前开发时,可能先根据需求设计数据库,然后在写Model和业务逻辑,对于Model类基本都是和表的字段对应着,而表中存的每条记录又和类的实例对象对应着,有了这个对照关系,就是能不能只在一边设计,在数据库设计表或在VS中设计Model,然后直接生成另一边,这样就省了好多时间成本.于是有了ORM,Object Relation Mapping,对象关系映射.既然可以根据Mode

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

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