• 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();

  • 相关阅读:
    kibana x-pack 更新license
    elastic search集群请求超时
    angular4 angular/cli 版本升级
    windows 编写shell 脚本转化成unix
    spring boot 自定义HandlerMethodArgumentResolver做参数校验
    Fiddler 重定向BUG
    scoop
    acm每日刷题记录
    ccpc 2016 changchun 长春(4.12训练)
    cf 338E
  • 原文地址:https://www.cnblogs.com/dudu/p/entity_framework_one_to_one_shared_primary_key.html
Copyright © 2020-2023  润新知