Entity Framework - 理清关系 - 基于共享主键的单向一对一关系

在上篇文章中,我们理了一下基于外键关联的单向一对一关系。在这篇文章中,我们理一理“基于共享主键的单向一对一关系”,找出Entity Framework中正确的映射关系。

一、明确需求,也就是Entity Framework正确映射之后要达到的效果

1)数据库结构要符合要求——共享主键。所下图所示,数据库中表A与表B的主键都是AID。

2)实体关系要符合要求——单向一对一关系。我们通过下面的UML类图来表达:

上图中只有A到B的关联箭头,这就是“单向”,这个箭头也表示A依赖B,在代码中的表现就是A有一个导航属性A.B。

上图中箭头两头的两个1就是“一对一”,存在一个A,必然存在一个对应的B;存在一个B,必然存在一个对应的A。

EDM中的实体关系要与UML类图中的关系一致。

实体A的定义:

public class A
{
public int AID { get; set; }
public string Title { get; set; }
public B B { get; set; }
}

实体B的定义:

public class B
{
public int AID { get; set; }
public string Body { get; set; }
}

3)持久化操作要符合要求

只允许A与B一起进行持久化,测试代码如下:

var a = new A();
a.Title = "title test";
a.B = new B();
a.B.Body = "body test";
using (EfUnitOfWork ef = new EfUnitOfWork())
{
ef.Set<A>().Add(a);
ef.SaveChanges();
}

不允许A与B各自单独的持久化,测试代码如下:

//不允许的持久化操作
var a = new A();
a.Title = "title a";
using (EfUnitOfWork ef = new EfUnitOfWork())
{
ef.Set<A>().Add(a);
ef.SaveChanges();
}

var b = new B();
b.Body = "body b";
using (EfUnitOfWork ef = new EfUnitOfWork())
{
ef.Set<B>().Add(b);
ef.SaveChanges();
}

4)生成的SQL查询语句要符合要求

比如这样的查询:

using (EfUnitOfWork ef = new EfUnitOfWork())
{
ef.Set<A>()
.Include(a => a.B)
.Where(a => a.AID == 1)
.ToList();
}

生成的SQL查询语句应该是:

SELECT 
[Extent1].[AID] AS [AID],
[Extent1].[Title] AS [Title],
[Extent2].[AID] AS [AID1],
[Extent2].[Body] AS [Body]
FROM [dbo].[A] AS [Extent1]
INNER JOIN [dbo].[B] AS [Extent2] ON [Extent1].[B_AID] = [Extent2].[AID]
WHERE 1 = [Extent1].[AID]

 

二、用最笨的方法找出答案

这个最笨的方法是,对四种映射关系逐一进行测试,看哪个与我们想要的效果最一致。

下面我们分别来看看在不同的映射关系配置下Entity Framework的行为。

1).HasRequired(a => a.B).WithMany();

FluentAPI:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<B>().HasKey(b => b.AID);
modelBuilder.Entity<A>().HasRequired(a => a.B).WithMany();
}

a) EF生成的数据库结构:

表A多了一个字段B_AID,并通过B_AID关联至表B的主键AID。数据库结构不一致,不符合要求。

b) EF生成的EDM图:

EDM与UML中的关系定义不一致,这里是一对多关系,我要的是一对一关系,不符合要求。

c) 不允许实体A的单独持久化,但允许实体B的单独持久化,不符合要求。

d) 生成的SQL查询语句符合要求。

【小结】数据库结构不符合要求,实体关系不符合要求,持久化不符合要求,生成的SQL查询语句符合要求。

2).HasRequired(a => a.B).WithOptional();

FluentAPI:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<B>().HasKey(b => b.AID);
modelBuilder.Entity<A>().HasRequired(a => a.B).WithOptional();
}

a) EF生成的数据库结构:

数据库结构符合要求。

b) EF生成的EDM图:

关联的一端是0..1,也就是允许“存在一个B,不存在一个A”的情况,实体关系不符合要求。

c) 不允许实体A的单独持久化,但允许实体B的单独持久化,不符合要求。

d) 生成的SQL查询语句符合要求。

【小结】数据库结构符合要求,实体关系不符合要求,持久化不符合要求,SQL查询语句符合要求。

3).HasRequired(a => a.B).WithRequiredDependent();

FluentAPI:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<B>().HasKey(b => b.AID);
modelBuilder.Entity<A>().HasRequired(a => a.B).WithRequiredDependent();
}

a) 生成的数据库结构与.WithOptional();一样,符合要求。

b) EF生成的EDM图:

实体关系是“单向一对一”关系,与UML类图一致。但类的摆放位置不一致,在EDM中,B在A的前面,也就是B是Principal,我们希望A是Pricipal,有点不一致。

c) 不允许实体A的单独持久化,但允许实体B的单独持久化,不符合要求。

d) 生成的SQL查询语句符合要求。

【小结】数据库结构符合要求,实体关系有点不符合要求(Pricipal不一致),持久化不符合要求,SQL查询语句符合要求。

4).HasRequired(a => a.B).WithRequiredPrincipal();

FluentAPI:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<B>().HasKey(b => b.AID);
modelBuilder.Entity<A>().HasRequired(a => a.B).WithRequiredPrincipal();
}

a) 生成的数据库结构与.WithOptional();一样,符合要求。

b) EF生成的EDM图:

实体关系是“单向一对一”关系,与UML类图一致,A是Pricipal,符合要求。

c) 允许实体A的单独持久化,不允许实体B的单独持久化,不符合要求。

d) 生成的SQL查询语句:

SELECT 
[Extent1].[AID] AS [AID],
[Extent1].[Title] AS [Title],
[Extent3].[AID] AS [AID1],
[Extent3].[Body] AS [Body]
FROM [dbo].[A] AS [Extent1]
LEFT OUTER JOIN [dbo].[B] AS [Extent2] ON [Extent1].[AID] = [Extent2].[AID]
LEFT OUTER JOIN [dbo].[B] AS [Extent3] ON [Extent2].[AID] = [Extent3].[AID]
WHERE 1 = [Extent1].[AID]

我们想要的是INNER JOIN,这里却是两个LEFT OUTER JOIN,不符合要求。

等等。。。我们改一下查询的LINQ语句试试,改为:

using (EfUnitOfWork ef = new EfUnitOfWork())
{
ef.Set<A>()
.Include(a => a.B)
.Where(a => a.B.AID == 1)//原来是a => a.AID == 1
.ToList();
}

改过之后,生成的SQL查询语句符合要求:

[Extent1].[AID] AS [AID], 
[Extent1].[Title] AS [Title],
[Extent2].[AID] AS [AID1],
[Extent2].[Body] AS [Body]
FROM [dbo].[A] AS [Extent1]
INNER JOIN [dbo].[B] AS [Extent2] ON [Extent1].[AID] = [Extent2].[AID]
WHERE 1 = [Extent2].[AID]

【小结】数据库结构符合要求,实体关系符合要求,持久化不符合要求,SQL查询语句符合要求。

 

三、总结与分析

  数据库结构 实体关系 持久化 SQL查询语句
WithMany() 不符合 不符合 不符合 符合
WithOptional() 符合 不符合 不符合 符合
WithRequiredDependent() 符合 不符合 不符合 符合
WithRequiredPrincipal() 符合 符合 不符合 符合


从上面的表中可以出,成绩最好的是WithRequiredPrincipal(),但它有一个地方不符合要求, 就是允许实体A的单独持久化。

为什么实体B不能单独持久化?看数据库的外键关系就知道答案(A_B外键约束的功劳):

那我们只要解决“不允许实体A的单独持久化”的问题,就能完成“基于共享主键的单向一对一关系”的完美映射。

既然数据库中不好下手,那就从实体类下手吧。给实体A的导航属性A.B加一个[Required]属性,在实体验证时就要求A.B必须有一个对应的实体B的实例。修改后的实体A的代码如下:

public class A
{
public int AID { get; set; }
public string Title { get; set; }
[Required]
public B B { get; set; }
}

经过努力,我们终于找到了最佳答案——

对于“基于共享主键的单向一对一”关系,Entity Framework中正确的映射关系定义是:

modelBuilder.Entity<A>().HasRequired(a => a.B).WithRequiredPrincipal();

posted @ 2012-01-05 13:43  dudu  阅读(11388)  评论(5编辑  收藏  举报