Entity Framework 实践系列 —— 搞好关系 - 单相思(单向一对一,one-to-one)

小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03



原以为躲入代码世界,就可以不用搞关系,哪知“关系无处不在”。写代码多年之后,终于明白“面向对象的关键是搞好对象之间的关系”。而Entity Framework作为ORM中的明日之星,首当其冲的使命就是配合对象搞好关系。

博客园开发团队在使用Entit Framework的过程中,被困扰最多的就是实体类之间的关系处理以及这种关系与数据库之间的映射。所以,希望通过这个系列文章将我们的困惑、理解与实践经验拿出来与大家分享。知识与经验只有拿出来分享与传播,才会变得更有价值;藏在那,只会慢慢变质,最终随风而去。

关系分三种:一对一、一对多、多对多。我们就从“一对一”开始吧。“一对一”说简单也简单,说复杂也复杂;在现实世界,关键在于是否找对了那个“一”,“找一个人,过一辈子”;而在代码世界,又该如何“一对一”,让我们一起探索吧。

“一对一”也分两种情况:一种是单相思(单向),一种是两情相悦(双向)。单相思容易,两情相悦难啊;但一见就能两情相悦毕竟可遇而不可求,多数时候两情相悦来自于一方的单相思。

那我们就从最简单的单相思开始吧 —— “我的眼中只有你,你的眼中会不会有我”。

场景描述:园子里的每一个“博客”(BlogSite)都对应着一个对技术充满激情的“人”(BlogUser)。我想参观一下园子,看看所有这些博客,看看这些博客背后的主人(GetAllBlogSites)。

类图如下:

数据库表结构如下:

VS2010中的项目文件结构(基于博客园最新架构):

开始我们的旅程:

1. 第一步当然是测试,TDD可不是用来炫耀的专业名词,它是一个轮子,可以让你在开发的道路上走得更快,所以叫测试驱动。测试代码如下:

[TestMethod]

public

void
GetAllBlogSites_Test()
{
_aggBlogSiteService.GetAllBlogSites().ToList()
.ForEach(
b
=>
{ Console.WriteLine(
"
BlogApp:
"

+
b.BlogApp
+

"
, Author:
"

+
b.BlogUser.Author); }
);
}

测试代码很简单,就是调用应用层的服务接口IAggBlogSiteService.GetAllBlogSites();

通过测试代码我们可以明确需求:获取一个包含所有BlogSite的列表,每个BlogSite包含BlogUser信息。这也是一个很常见的查询需求。

2. 看一下应用层的服务实现:

public
IEnumerable
<
BlogSite
>
GetAllBlogSites()
{

return
_blogSiteReposiotry.Entities
.Include(b
=>
b.BlogUser)
.Where(b
=>
b.IsActive
==

true
);
}

很简单的LINQ查询,需要注意的地方是Include,只有用了Include,在执行时Entity Framework才会生成BlogSite与BlogUser两张表的INNER JOIN查询。否则,就会使用延迟加载(LazyLoading),在每次访问BlogSite.BlogUser属性时进行查询;如果返回100个BlogSite,就会产生100次对BlogUser表的查询。

3. 进入关键环节,如何针对这种单向的一对一关系对Entity Framework进行设置(这里用的是Code First)。

有两种方式可以实现:一种是通过FluentAPI,在OnModelCreating中实现;一种是通过DataAnnotations直接在实体类上设置。

a) FluentAPI设置的具体代码如下:

protected

override

void
OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity
<
BlogUser
>
().ToTable(
"
BlogUser
"
);
modelBuilder.Entity
<
BlogUser
>
().HasKey(u
=>
u.UserID);

modelBuilder.Entity
<
BlogSite
>
().ToTable(
"
BlogSite
"
);
modelBuilder.Entity
<
BlogSite
>
().HasKey(b
=>
b.BlogID);

//
针对“一对一”关系的设置

modelBuilder.Entity
<
BlogSite
>
().HasRequired(b
=>
b.BlogUser).
WithMany().HasForeignKey(b
=>
b.UserID);

modelBuilder.Conventions.Remove
<
IncludeMetadataConvention
>
();

base
.OnModelCreating(modelBuilder);
}

只需看“一对一”关系设置部分的代码。HasRequired(b => b.BlogUser)很好理解就是定义“一对一”的关系;WithMany()目前是个迷,Many与One-to-One关系看起来驴头不对马嘴,但就得这样,这可是我们经过很多尝试,最后试出来的解决方法。否则会生成让人哭笑不得的SQL语句。下面我们来欣赏一下EF生成的让人哭笑不得的SQL。

开始我们是用FluentAPI这样进行定义的:

//
针对“一对一”关系的设置

modelBuilder.Entity
<
BlogSite
>
().HasRequired(b
=>
b.BlogUser).
WithRequiredDependent().Map(conf
=>
conf.MapKey(
"
UserID
"
));

注:这样定义时,需要注释掉BlogSite的UserID属性。

这种方式,能够得到正确的结果,但生成的SQL很奇怪:

SELECT

[
Extent1
]
.
[
BlogID
]

AS

[
BlogID
]
,

[
Extent1
]
.
[
BlogApp
]

AS

[
BlogApp
]
,

[
Extent1
]
.
[
IsActive
]

AS

[
IsActive
]
,

[
Join1
]
.
[
UserID1
]

AS

[
UserID
]
,

[
Join1
]
.
[
Author
]

AS

[
Author
]
,

[
Join3
]
.
[
BlogID
]

AS

[
BlogID1
]

FROM

[
dbo
]
.
[
BlogSite
]

AS

[
Extent1
]

LEFT

OUTER

JOIN
(
SELECT

[
Extent2
]
.
[
UserID
]

AS

[
UserID1
]
,
[
Extent2
]
.
[
Author
]

AS

[
Author
]

FROM

[
dbo
]
.
[
BlogUser
]

AS

[
Extent2
]

LEFT

OUTER

JOIN

[
dbo
]
.
[
BlogSite
]

AS

[
Extent3
]

ON

[
Extent2
]
.
[
UserID
]

=

[
Extent3
]
.
[
UserID
]
)
AS

[
Join1
]

ON

[
Extent1
]
.
[
UserID
]

=

[
Join1
]
.
[
UserID1
]

LEFT

OUTER

JOIN
(
SELECT

[
Extent4
]
.
[
UserID
]

AS

[
UserID2
]
,
[
Extent5
]
.
[
BlogID
]

AS

[
BlogID
]

FROM

[
dbo
]
.
[
BlogUser
]

AS

[
Extent4
]

LEFT

OUTER

JOIN

[
dbo
]
.
[
BlogSite
]

AS

[
Extent5
]

ON

[
Extent4
]
.
[
UserID
]

=

[
Extent5
]
.
[
UserID
]
)
AS

[
Join3
]

ON

[
Extent1
]
.
[
UserID
]

=

[
Join3
]
.
[
UserID2
]

WHERE

1

=

[
Extent1
]
.
[
IsActive
]

GetAllBlogSites()
f

无需多说,看一眼你就无法忍受这样的SQL。

再看看使用WithMany()生成的SQL:

SELECT

[
Extent1
]
.
[
BlogID
]

AS

[
BlogID
]
,

[
Extent1
]
.
[
BlogApp
]

AS

[
BlogApp
]
,

[
Extent1
]
.
[
IsActive
]

AS

[
IsActive
]
,

[
Extent1
]
.
[
UserID
]

AS

[
UserID
]
,

[
Extent2
]
.
[
UserID
]

AS

[
UserID1
]
,

[
Extent2
]
.
[
Author
]

AS

[
Author
]

FROM

[
dbo
]
.
[
BlogSite
]

AS

[
Extent1
]

INNER

JOIN

[
dbo
]
.
[
BlogUser
]

AS

[
Extent2
]

ON

[
Extent1
]
.
[
UserID
]

=

[
Extent2
]
.
[
UserID
]

WHERE

1

=

[
Extent1
]
.
[
IsActive
]

多干净,这才是我们想要的。

b) 还有一种简单的方法,通过DataAnnotations在BlogSite的UserID属性上设置[ForeignKey("BlogUser")],代码如下:

public

class
BlogSite
{

public

int
BlogID {
get
;
set
; }

public

string
BlogApp {
get
;
set
; }

public

bool
IsActive {
get
;
set
; }
[ForeignKey(
"
BlogUser
"
)]

public
Guid UserID {
get
;
set
; }

public
BlogUser BlogUser {
get
;
set
; }
}

这是Entity Framework 4.0开始引入的新特性,欲知详情,请看Foreign key vs. Independent associations in Entity Framework 4

一个ForeignKey可以达到WithMany同样的效果,够简单。

写到这,单向一对一关系搞好咧。接着干吗呢?这还用问,运行测试,收工!

回来一下,完整代码还没分享:

CNBlogsDemo代码下载

参考页面:http://qingqingquege.cnblogs.com/p/5933752.html

时间: 2024-10-21 22:03:05

Entity Framework 实践系列 —— 搞好关系 - 单相思(单向一对一,one-to-one)的相关文章

Entity Framework - 基于外键关联的单向一对一关系

代码的世界,原以为世界关系很简单,确道是关系无处不在.NET世界里ORM框架中EntityFramework作为其中翘楚,大大解放了搬砖工作的重复工作,着实提高了不少生产力,而也碰到过不少问题!比如关系的映射! 一对一关系的映射: 用户账户密码信息表:包含用户名 密码 邮箱等账户登录时的信息 public class SystemAccount { public SystemAccount() { Id = DateUtils.GeneratedNewGuid(); } public Guid

Entity Framework技术系列之8:使用Entity Framework技术实现RBAC模型

小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03 前言 RBAC(Role-Based Access Control,基于角色的访问控制),是继DAC(Discretionary Access Control,自主访问控制)和MAC(Mandatory Access Control,强制访问控

Entity Framework技术系列之7:LINQ to Entities

小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03 前言 LINQ(Language Integrated Query,语言集成查询)是一组用于C#和VB.NET语言的扩展,它允许编写C#或者VB.NET代码,以与查询数据库相同的方式操作内存数据.LINQ提Entity Framework技术系

Entity Framework技巧系列之六 - Tip 20 – 25

提示20. 怎样处理固定长度的主键 这是正在进行中的Entity Framework提示系列的第20篇. 固定长度字段填充: 如果你的数据库中有一个固定长度的列,例如像NCHAR(10)类型的列,当你进行一次插入时,填充会自动发生.所以例如如果你插入'12345',你将得到5个自动填充的空格,来创建一个10个字符长度的字符串. 大多数情况下,这种自动填充不会有问题.但是在使用Entity Framework时如果你使用这些列的一个作为你的主键,你可能会在进行标识识别(identity resol

【转】Entity Framework技术系列之7:LINQ to Entities

前言 LINQ(Language Integrated Query,语言集成查询)是一组用于C#和VB.NET语言的扩展,它允许编写C#或者VB.NET代码,以与查询数据库相同的方式操作内存数据. LINQ提Entity Framework技术系列之7:LINQ to Entities供了丰富的类似SQL的查询语法,功能强大且容易上手.下图汇总展示了LINQ技术的官方实现集合: 图1官方LINQ实现汇总图 正 如上图所示,LINQ to Entities 是LINQ技术在实体对象模型中的一种实现

采用MiniProfiler监控EF与.NET MVC项目(Entity Framework 延伸系列1)

小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03 前言 Entity Framework 延伸系列目录 今天来说说EF与MVC项目的性能检测和监控 首先,先介绍一下今天我们使用的工具吧. MiniProfiler~ 这个东西的介绍如下: MVC MiniProfiler是Stack Overf

在Entity Framework 中实现继承关系映射到数据库表

继承关系映射到数据库表中有多种方式: 第一种:TPH(table-per-hiaerachy) 每一层次一张表 (只有一张表) 仅使用名为父类的类型名的一张表,它包含了各个子类的所有属性信息,使用区分列(Disciriminator column)(通常内容为子类的类型名)来区分哪一行表示什么类型的数据. 第二种:TPT(Table-per-type) 每种类型都有一张表(父类及每个子类都有表) 父类.各子类各自都有一张表.父类的表中只有共同的数据,子类表中有子类特定的属性.TPT很像类的继承结

Entity Framework Plus 系列目录

Entity Framework Plus 系列文章计划的已经全部写完,可能还有其他功能没有写到,希望大家能够多动手,尝试一下使用,一定会给您带来一些帮助的.文章全部写完,也应该出一个目录方便查看,目录如下 第一篇 Entity Framework Plus 之 Audit 第二篇 Entity Framework Plus 之 Query Future 第三篇 Entity Framework Plus 之 Query Cache 第四篇 Entity Framework Plus 之 Bat

Entity Framework技巧系列之五 - Tip 16 – 19

提示16. 当前如何模拟.NET 4.0的ObjectSet<T> 背景: 当前要成为一名EF的高级用户,你确实需要熟悉EntitySet.例如,你需要理解EntitySet以便使用 AttachTo(-) 或创建EntityKey. 在大部分情况下,针对每个对象/clr类型只有一个可能的EntitySet.Tip 13正是利用这种想法来简化附加(Attach)对象并且你也可以对Add使用类似的技巧. 然而为了在.NET 4.0中解决这个问题,我们添加了一个叫做 ObjectSet<T&