EF Code First 配置的相关内容

I.实体间一对一的关系
添加一个PersonPhoto类,表示用户照片类


 1 /// <summary>
2 /// 用户照片类
3 /// </summary>
4 public class PersonPhoto
5 {
6   [Key]
7   public int PersonId { get ; set ; }
8   public byte [] Photo { get ; set ; }
9   public string Caption { get ; set ; } // 标题
10   public Person PhotoOf { get ; set ; }
11 }

当然,也需要给Person类添加PersonPhoto的导航属性,表示和PersonPhoto一对一的关系:

public PersonPhoto Photo { get ; set ; } 

直接运行程序会报一个错:
Unable to determine the principal end of an
association between the types ‘Model.Per-sonPhoto’ and ‘Model.Person’. The
principal end of this association must be explicitly configured using either the
relationship fluent API or data annotations.

思考:为何第一节的Destination和Lodging类直接在类里加上导航属性就可以生成主外键关系,现在的这个不行呢?

解答:之前文章里的Destination和Lodging是一对多关系,既然是一对多,EF自然就知道设置Destination类的DestinationId为主键,同时设置Lodging类里的DestinationId为外键;但是现在的这个Person类和PersonPhoto类是一对一的关系,如果不手动指定,那么EF肯定不知道设置哪个为主键哪个为外键了,这个其实不难理解。按照逻辑Person类的PersonId肯定是主键了,直接标注[ForeignKey("PhotoOf")]即可,这是Data
Annotation方式配置,自然也可以Fluent
API一下,博主个人更喜欢这个方式。
在演示Fluent
API如何配置Person类和PersonPhoto的一对一关系之前,先系统的学习下EF里实体关系配置的方法。EF里的实体关系配置分为Has和With系列的方法:Optional
可选的、Required 必须的、Many 多个。举例:

 1 A.HasRequired(a =>
a.B).WithOptional(b => b.A);  


这里的a=>a.B是lambda表示写法,就是找到A类里的导航属性B。命名a不固定,可以随意,q=>q.B也是可以的。但是B是A类的属性,故习惯用小写a。

Has方法:

HasOptional:前者包含后者一个实例或者为null
HasRequired:前者(A)包含后者(B)一个不为null的实例 HasMany:前者包含后者实例的集合
With方法:


WithOptional:后者(B)可以包含前者(A)一个实例或者null WithRequired:后者包含前者一个不为null的实例
WithMany:后者包含前者实例的集合
摘自 这里
这是较为好的理解方式。上面一句配置意思就是A类包含B类一个不为null的实例,B类包含A类一个实例,也可以不包含。最标准的一对一配置。ok,现在试着写下上面Person类和PersonPhoto类的一对一的关系如何配置:

1 this .HasRequired(p =>
p.PhotoOf).WithOptional(p => p.Photo);  


再跑下程序,数据库就生成了,是一对一的关系。Person表可以没有对应的PersonPhoto表数据,但是PersonPhoto表每一条数据都必须对应一条Person表数据。意思就是人可以没有照片,但是有的照片必须属于某个人。关系配置是这样的效果,其实可以随便改,也可以配置成每个人都必须有对应的照片。把上面的WithOptional改成WithRequired,对应到数据库里就是null变成了not
null。

思考:这里并没有像之前一样添加一个实体类就同时添加到BreakAwayContext类中,但是为何照样能在数据库中生成PersonPhotos表?

解答:添加到BreakAwayContext类中是让数据库上下文能跟踪到这个类,方便进行CRUD(增查改删)。这里不把PersonPhoto类添加到BreakAwayContext类中是因为程序中一般并不会去单独增删改查PersonPhoto类,对PersonPhoto类的操作都是先找Person类,然后通过一对一的关系找到PersonPhoto类,这个比较符合实际情况。数据库中能生成PersonPhotos就更好理解了,因为有这个实体类嘛。

思考:如果只需要加入主表类到BreakAwayContext类中,那么其他什么一对多,多对多的关系是不是都只要加主表类到BreakAwayContext类中呢?

解答:还是需要根据实际情况考虑,上面的PersonPhoto类已经解释过了,实际情况中不太可能单独操作PersonPhoto类。一对多关系里Logding住宿类是从表类,Destination是其主表。这个想想也知道必须要让数据库上下文跟踪到Lodging住宿类,因为太可能直接操作Lodging了。比如前台添加一个搜索住宿的功能,那是不是需要直接操作此从表了呢?肯定需要了。所以还是需要根据实际情况考虑。这里仅是个人观点,如有瑕疵,恳请指正。

 
II.实体间一对多的关系

之前的文章里,景点类Destination和住宿类Lodging是一对多的关系,这个很好理解:一个景点那有多个住宿的地方,而一个住宿的地方只属于一个景点。当然也可以没有,一个景点那一个住宿的地方就没有,一个住宿的地方不属于任何景点,这个也是可以的。之前的程序实现的就是互相不属于,全部可空。现在来配置下住宿的地方必须属于某个景点:

Data Annotations
直接在住宿类Lodging的导航属性上添加[Required]标注即可:


1 [Required]
2 public Destination Destination { get ; set ; }
3
4 Fluent API
5
6 this .HasMany(d => d.Lodgings).WithRequired(l => l.Destination).Map(l => l.MapKey("DestinationId"));

这行是在DestinationMap类里写的,对应到上面的描述,前者就是Destination,后者是Lodging。整句的意思就是:Destination类包含多个(HasMany)Lodging类实例的集合,Lodging类包含前者一个不为null(WithRequired)的实例。.MapKey是指定外键名的。此处如果住宿类不必须属于某个景点,那么直接把WithRequired换成WithOptional即可。查询的时候前者使用Inner
join,后者使用Left join。不懂Inner、Left和Cross
Join区别的点 这里

上面是以Destination为前者的,当然也可以以Lodging为前者,去LodgingMap里写下如下配置,其实是一个意思:

this
.HasRequired(d => d.Destination).WithMany(l => l.Lodgings).Map(l
=> l.MapKey( " DestinationId " ));


重跑下程序,生成的数据库Lodging表的外键已经设置成为了不可空,并外键名是指定的“DestinationId”:

 官方给出的一对多的解释是这样的,其实还没我解释的通俗易懂,发个图你们感受下吧:

ok,上面说了一对多的关系,是标准的一对多关系,两个表里分别有导航属性。但是如果有列不遵循这个规则呢?

继续添加一个新类InternetSpecial,记录一些跟平常住宿价格不一样的类,节假日等。这个类不仅有导航属性Accommodation,还有主键列AccommodationId:


 1 /// <summary>
2 /// 住宿特殊价格类(节假日等)
3 /// </summary>
4 public class InternetSpecial
5 {
6 public int InternetSpecialId { get ; set ; }
7 public int Nights { get ; set ; } //几晚
8 public decimal CostUSD { get ; set ; } //价钱
9 public DateTime FromDate { get ; set ; }
10 public DateTime ToDate { get ; set ; }
11
12 public int AccommodationId { get ; set ; }
13 public Lodging Accommodation { get ; set ; }
14 }

同时给住宿类Lodging添加一个InternetSpecial类的导航属性:

public
List<InternetSpecial> InternetSpecials { get ; set ; }


配置好了跑下程序,生成的数据库表:

由表可见,不仅有AccommodationId列,还有个外键列Accommodation_LodgingId,明显这个是因为没有设置外键的原因,EF不知道要给哪个属性当外键。现在分别使用Data
Annotation和Fluent API设置试试
Data Annotation:

[ForeignKey( "
Accommodation " )]
public int AccommodationId { get ; set ; }


或者这样:

[ForeignKey( " AccommodationId " )]
public Lodging
Accommodation { get ; set ; }

Fluent API:

this
.HasRequired(s => s.Accommodation)
.WithMany(l =>
l.InternetSpecials)
.HasForeignKey(s => s.AccommodationId); //
外键
// 如果实体类没定义AccommodationId,那么可以使用Map方法直接指定外键名:.Map(s =>
s.MapKey("AccommodationId"))


这个就不详细解释了,如果还看不懂,看看文章开头我分析的Has和With系列方法。配置好重新跑下程序,外键就是AccommodationId了,没有多余的Accommodation_LodgingId列了。

 
III.实体间多对多的关系

添加一个活动类Activity,跟旅行类Trip是多对多的关系。这个也不难理解:一个旅行有多个活动,一个活动可以属于多个旅行。


///
<summary>
/// 活动类
/// </summary>

public class Activity
{
public int ActivityId
{ get ; set ; }
// [Required, MaxLength(50)]
public
string Name { get ; set ; }

public List<Trip> Trips { get ; set ; } //
和Trip类是多对多关系
}



跟之前的一样在BreakAwayContext类里添加Activity类,让数据库上下文知道Activity类:

public
DbSet<CodeFirst.Model.Activity> Activitys { get ; set ; }


同时在Trip旅行类里添加上导航属性,形成跟Activity活动类的多对多关系

public
List<Activity> Activitys { get ; set ; }


ok,已经可以了,跑下程序得到如下数据库:

可以看出,EF里的多对多关系是由第三张表来连接两个表的。ActivityTrips表连接了Activityes表和Trips表。表名列名都是默认命名,都可以自己配置。文章的开头已经说了那么多了,多对多肯定是用HasMany和WithMany方法,在ActivityMap类里写下如下Fluent
API:


this .HasMany(a => a.Trips).WithMany(t =>
t.Activitys).Map(m =>
{

m.ToTable( " TripActivities " ); // 中间关系表表名
m.MapLeftKey( "
ActivityId " ); //设置 Activity表在中间表主键名
m.MapRightKey( "
TripIdentifier " ); //设置 Trip表在中间表主键名
});



同样也可以在TripMap里配置,顺序不一样罢了:


this .HasMany(t =>
t.Activities).WithMany(a => a.Trips).Map(m =>
{

m.ToTable( " TripActivities " ); // 中间关系表表名

m.MapLeftKey( " TripIdentifier " ); //设置 Activity表在中间表的主键名

m.MapRightKey( " ActivityId " ); //设置 Trip表在中间表的主键名
});


两种配置任选其一就可以了,重新跑下程序就可以了。都配置好了在程序里如何读取这个对多对的数据呢,简单写一句:

var
tripWithActivities = context.Trips.Include( " Activities " ).FirstOrDefault();


很明显,用到了Include贪婪加载把相关的外键表数据(如果有)也拿到了内存中:

是不是也需要考虑性能的问题呢?如果只需要修改主表的某个列,那贪婪加载出相关联的从表数据做什么?会发送很多冗余的sql到数据库。当然如果要根据主表找从表数据的话,这么加载也是好事,超级方便。EF小组的原话是:Entity
Framework took care of the joins to get across the join table without you having
to be aware of its presence. In the same way, any time you do inserts,

updates, or deletes within this many-to-many relationship, Entity Framework will
work out the proper SQL for the join without you having to worry about it in
your code.

意思就是如果你配置好了主外键关系,EF会帮你生成合适的连表查询(join)sql,不会你再多费心。关于一对多、多对多的EF查询和效率问题,后续会有专门系列文章讲解。

 
IV.级联删除

EF配置的外键关系除了配置为Optional(可选的,也就是可空),其他默认都是级联删除的,意思就是删除主表的某个数据,相关联的从表数据都自动删除:


为了演示添加一个方法:


// 级联删除(服务端延迟加载)
private static void
DeleteDestinaInMemoryAndDbCascade()
{
int
destinationId;
using ( var context = new
CodeFirst.DataAccess.BreakAwayContext())
{

var destination = new CodeFirst.Model.Destination
{

Name = " Sample Destination " ,

Lodgings = new List<CodeFirst.Model.Lodging>

{
new CodeFirst.Model.Lodging {Name= " Lodging
One " },
new CodeFirst.Model.Lodging {Name= "
Lodging Two " }
}
};

context.Destinations.Add(destination); //添加测试数据
context.SaveChanges();

destinationId = destination.DestinationId; //记住主键id

}
using ( var context = new
CodeFirst.DataAccess.BreakAwayContext())
{
//
这里用了贪婪加载,把主键和相关的外键记录都加载到内存中了
var destination =
context.Destinations.Include( " Lodgings " ).Single(d =>
d.DestinationId == destinationId);
var aLodging =
destination.Lodgings.FirstOrDefault();

context.Destinations.Remove(destination);

context.SaveChanges();
}
}



很简单,添加了一条主键数据Sample Destination,同时添加了以此主键为基础的两条外键数据:Lodging One和Lodging
Two,即:添加了一个旅游景点,又添加了此旅游景点下的两个住宿的地方。之后延迟加载出主表数据和相关联的两条从表数据并删除,使用sql
profiler能监测到如下sql:

第一条是删除主表的数据,后两条是删除相关联从表数据的sql。这种级联删除稍显麻烦,同时加载了相关联从表的数据到内存中再发送删除命令到数据库。其实只需要加载要删除的主表记录到内存中就可以了,因为数据库已经打开了级联删除,只需要发送删除主表数据的指令到数据库,数据库会自动删除相关联的从表记录。可以监控到如下sql:




直接复制到数据库执行查询,发现它会返回一条主表数据和两条相关联的从表数据。除非必须查出外键记录才使用Include贪婪加载,否则千万不要,EF中跟手写ado不一样,很容易生成很冗余的sql。这里其实只需要主键的记录就可以了,修改下方法:



     // 级联删除(仅加载主键记录)
private static void
DeleteDestinationInMemeryAndDbCascade()
{
int
destinationId;
using ( var context = new
CodeFirst.DataAccess.BreakAwayContext())
{

var destination = new CodeFirst.Model.Destination
{

Name = " Sample Destination " ,

Lodgings = new List<CodeFirst.Model.Lodging>

{
new CodeFirst.Model.Lodging {Name= " Lodging
One " },
new CodeFirst.Model.Lodging {Name= "
Lodging Two " }
}
};

context.Destinations.Add(destination);

context.SaveChanges();
destinationId =
destination.DestinationId;
}
using ( var
context = new CodeFirst.DataAccess.BreakAwayContext())
{

var destination = context.Destinations

.Single(d => d.DestinationId == destinationId); // 只取一条主键记录

context.Destinations.Remove(destination);
//然后移除主键记录,外键记录又数据库级联删除
context.SaveChanges();
}
}



监控的sql干干净净,只会查出主表数据。


exec sp_executesql N ‘ SELECT
TOP (2)
[Extent1].[DestinationId] AS [DestinationId],
[Extent1].[Name]
AS [Name],
[Extent1].[Country] AS [Country],
[Extent1].[Description] AS
[Description],
[Extent1].[image] AS [image]
FROM [dbo].[Destinations] AS
[Extent1]
WHERE [Extent1].[DestinationId] = @p__linq__0 ‘ ,N ‘ @p__linq__0
int ‘ , @p__linq__0 = 1


补充:这里只查一条记录却使用SELECT TOP (2)...
是保证能查到记录。
删除sql更干净,只删除主表数据,相关联的从表数据删除由数据库级联删除完成:

exec
sp_executesql N ‘ delete [dbo].[Destinations]
where ([DestinationId] = @0)
‘ ,N ‘ @0 int ‘ , @0 = 1


级联删除虽然方便,但是并不常用。试想我们在博客园写了很多随笔,为不同随笔加了不同的标签好区分和管理。某一天突然发现之前定的某个标签并不合理,但是这个标签已经在很多随笔里用了,如果此时删除标签,数据库级联的把标注此标签的随笔都删了,这个肯定不合适。应该是标签删了,之前贴过此标签的文章没了这个标签,这个才符合逻辑。

数据库里可以可视化的设置不级联删除,Fluent API配置此外键关系时可以设置不级联删除:

this .HasMany(d
=> d.Lodgings).WithRequired(l => l.Destination)
.Map(l
=> l.MapKey( " DestinationId " )) // 一对多并指定外键名

.WillCascadeOnDelete( false ); // 关闭级联删除


再跑下程序,去看下数据库本外键自然就没了级联删除。
园友 郭明锋
提供了一个很好的建议:考虑到EF中的级联删除并不常用,所以可以在全局里关掉所有主外键关系的级联删除,如果需要可以打开某个主外键的级联删除。


EF默认开启级联删除,确实是挺操蛋的设置,所以我的做法是在上下文的OnModelCreating方法中

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

移除这个默认约定,再在需要开启级联删除的FluentAPI关系映射中用. WillCascadeOnDelete(true) 单独开启

ok,本文就到此结束,后续还有更通俗易懂的文章介绍EF,请保持关

EF Code First 配置的相关内容,布布扣,bubuko.com

时间: 2024-12-28 14:20:50

EF Code First 配置的相关内容的相关文章

vim 配置 部分相关内容

1.vim添加插件 GitHub上面yangyangwithgnu写的很好,推荐:https://github.com/yangyangwithgnu/use_vim_as_ide 但是,好像有的也不能用,当然时极少的一丢丢. 2.yangyangwithgnu用的vim-powerline感觉很漂亮,他的文章中也有项目主页:https://github.com/Lokaltog/vim-powerline 可是,我的安装完插件后,箭头显示乱码: 解决方法:安装powerline字体: http

EF Code First自定义数据库(服务器及数据库名)连接配置

EF Code First数据库连接配置 通过EF Code First创建新的数据库,默认的都是: 命名空间.Models.MovieDbContext之类的数据库文件,如果想用自定义的数据库名称,则可以用下面的方法: 1.1.首先必须建立好Model文件 Models中Movie.cs 1 using System; 2 using System.ComponentModel.DataAnnotations; 3 using System.Data.Entity; 4 5 namespace

EF Code First数据库连接配置

前面几节,使用的都是通过EF Code First创建的新数据库,接下来,将开始使用已存在的数据库. 1.使用配置文件设置数据库连接 App.config 数据库连接字符串的name与Data中NorthwindContext.cs类名相同 <?xml version="1.0" encoding="utf-8"?><configuration>  <configSections>    <!-- For more info

EF Code First数据库映射规则及配置

EF Code First数据库映射规则主要包括以下方面: 1.表名及所有者映射 Data Annotation: 指定表名 1 using System.ComponentModel.DataAnnotations;2 3 [Table("Product")]4 public class Product指定表名及用户 using System.ComponentModel.DataAnnotations;[Table("Product", Schema = &qu

ASP.NET Web API实践系列02,在MVC4下的一个实例, 包含EF Code First,依赖注入, Bootstrap等

本篇体验在MVC4下,实现一个对Book信息的管理,包括增删查等,用到了EF Code First, 使用Unity进行依赖注入,前端使用Bootstrap美化.先上最终效果: →创建一个MVC4项目,选择Web API模版. →在Models文件夹创建一个Book.cs类. namespace MyMvcAndWebApi.Models { public class Book { public int Id { get; set; } public string Name { get; set

从零开始,搭建博客系统MVC5+EF6搭建框架(1),EF Code frist、实现泛型数据仓储以及业务逻辑

前言      从上篇30岁找份程序员的工作(伪程序员的独白),文章开始,我说过我要用我自学的技术,来搭建一个博客系统,也希望大家给点意见,另外我很感谢博客园的各位朋友们,对我那篇算是自我阶段总结文章的评论,在里面能看出有很多种声音,有支持的我的朋友给我加油打气,有分享自己工作经历的朋友,有提出忠肯意见的朋友,有对记事本写代码吐槽的朋友,也有希望让我换个行业的,觉得我可能不适合这个行业朋友,不管怎样,我都接受,都是大家同行的一些忠告,谢谢大家. 首先我要在这里感谢很多博客园里面的大牛,写了很多系

Asp.Net EF Code First 简单入门

今天在上班期间花了点时间学习了一下微软的EntityFramework Code First技术,这篇文章只是简单的入门,不多废话,下面直入主题. 一.首先添加一个解决方案,接着添加一个web网站,DataAccess类库(用于数据访问),Model类库(实体层),结构如下: 二.安装EntityFramework.通过Nuget Package Manager进行安装(如果还没有安装Nuget的,可以通过tools-extension manager进行安装),在tools-Nuget Pac

EF Code First更新数据库时报错:provider: SQL Network Interfaces, error: 26

在使用EF Code First更新数据库时报如下错误: 在与 SQL Server 建立连接时出现与网络相关的或特定于实例的错误.未找到或无法访问服务器.请验证实例名称是否正确并且 SQL Server 已配置为允许远程连接. (provider: SQL Network Interfaces, error: 26 - 定位指定的服务器/实例时出错) 以为是Sql Server的配置问题,作如下尝试: →打开Sql Server配置管理器,即"Sql Server Configuraiotn

EF Code First:实体映射,数据迁移,重构(1)

一.前言 经过EF的<第一篇>,我们已经把数据访问层基本搭建起来了,但并没有涉及实体关系.实体关系对于一个数据库系统来说至关重要,而且EF的各个实体之间的联系,实体之间的协作,联合查询等也都依赖于这些实体关系. 二.实体映射 实体与数据库的映射可以通过DataAnnotation与FluentAPI两种方式来进行映射: (一) DataAnnotation DataAnnotation 特性由.NET 3.5中引进,给.NET中的类提供了一种添加验证的方式.DataAnnotation由命名空