OurDotNetLogo

ADO.NET,EF,EntityFramework,OurDotNet,ADO.NET,EF,EntityFramework,OurDotNet

EF Code First表映射
作者:Gyoung  |   2017/12/31 17:56:30   |  阅读:334

多个实体映射到一张表

CodeFirst允许将多个实体映射到同一张表上,实体必须遵循如下规则:

实体必须是一对一关系

实体必须共享一个公共键

观察下面两个实体:

    public class Person
    {
        [Key]
        public int PersonId { get; set; }
        public int SocialSecurityNumber { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        [Timestamp]
        public byte[] RowVersion { get; set; }
        public PersonPhoto Photo { get; set; }
    }
 
    public class PersonPhoto
    {
        [Key, ForeignKey("PhotoOf")]
        public int PersonId { get; set; }
        public byte[] Photo { get; set; }
        public string Caption { get; set; }
        public Person PhotoOf { get; set; }
    }

它们之间是一对一的关系,并且主键数据类型相同,所以我们可以将它们映射到同数据库的同一个表中,只需指定表名即可:

    [Table("People")]
    public class Person
 
    [Table("People")]
    public class PersonPhoto

PS:我按照上面的模型映射,但生成数据库的时候会报错:

实体类型“PersonPhoto”“Person”无法共享表“People”,因为它们不在同一类型层次结构中,或者它们之间的匹配的主键没有有效的一对一外键关系。

然后我又把模型改了一下:

    [Table("People")]
    public class Person
    {
        [Key, ForeignKey("Photo")]
        public int PersonId { get; set; }
        public int SocialSecurityNumber { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        [Timestamp]
        public byte[] RowVersion { get; set; }
        public PersonPhoto Photo { get; set; }
    }
 
    [Table("People")]
    public class PersonPhoto
    {
        [Key, ForeignKey("PhotoOf")]
        public int PersonId { get; set; }
        public byte[] Photo { get; set; }
        public string Caption { get; set; }
        public Person PhotoOf { get; set; }
    }

映射可以成功,成功映射后的表结构如图:

但是在插入数据的时候Person类中的Photo属性不能为空,否则会报错:

遇到了无效数据。缺少必要的关系。请检查 StateEntries 以确定违反约束的源。

PersonPhoto ph = new PersonPhoto() { Caption = "个人照片",Photo=new byte[8]};
//可以插入成功
Person p1 = new Person() { FirstName = "Jhon", LastName = "Micheal",SocialSecurityNumber=123,Photo=ph};
 
//没有给Photo赋值,插入失败
Person p2 = new Person() { FirstName = "Jhon", LastName = "Micheal",SocialSecurityNumber=123};

将一个实体映射到多张表

现在我们反转一下,将一个实体映射到多张表,这可以用Fluent APIMap方法来实现,不能使用使用Data Annotations ,因为Data Annotations 没有属性子集的概念。

我们就将People表映射到数据库中A,B两表

    public class PersonInfo
    {
        [Key]
        public int PersonId { get; set; }
        public int SocialSecurityNumber { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        [Timestamp]
        public byte[] RowVersion { get; set; }
        public byte[] Photo { get; set; }
        public string Caption { get; set; }
    }
 modelBuilder.Entity<PersonInfo>().Map(m =>
                {
                    m.ToTable("A");
                    m.Properties(p => p.FirstName);
                    m.Properties(p => p.LastName);
                    m.Properties(p => p.RowVersion);
                    m.Properties(p => p.SocialSecurityNumber);
                }).Map(m =>
                {
                    m.ToTable("B");
                    m.Properties(p => p.Photo);
                    m.Properties(p => p.Caption);
                });

生成的表结构如图:

可以看到,Code First自动的为这两张表创建了主键和外键。在生成的表中,只有主表(A)的主键是自增长的。

注意:用Map映射的时候务必不要跳过任何属性!不然Code First还会自动的创建第三张表,保存那些你遗漏的属性。

上面的PersonInfo11就是Code First自动创建的第三张表,因为我Map的时候遗漏了SocialSecurityNumber属性。

继承类的映射

TPH(TablePer Hierarchy)

TPH:基类和派生类都映射到同一张表中,通过使用鉴别列来识别是否为子类型。这是Code First默认规则使用的表映射方法。

 public class Lodging
    {
        public int LodgingId { get; set; }
        [Required]
        [MaxLength(200)]
        [MinLength(10)]
        public string Name { get; set; }
        public string Owner { get; set; }
        public decimal MilesFromNearestAirport { get; set; }
        public int DestinationId { get; set; }
    }
    public class Resort : Lodging
    {
        public string Entertainment { get; set; }
        public string Activities { get; set; }
    }

生成的数据结构如图:

所以的属性都映射到同一张表中,包括派生类中的EntertainmentActivities,而且还多了一列:DiscriminatorEF正是通过这一列来识别数据来源。 我们可以插入数据测试一下:

            var lodging = new Lodging
            {
                Name = "Rainy Day Motel",
            };
            
            var resort = new Resort
            {
                Name = "Top Notch Resort and Spa",
                MilesFromNearestAirport = 30,
                Activities = "Spa, Hiking, Skiing, Ballooning",
            };
            using (var context = new BreakAwayContext())
            {
                context.Lodgings.Add(lodging);
                context.Lodgings.Add(resort);
                context.SaveChanges();
            }


可以看到EF通过Discriminator来区分LodgingResort

使用Fluent API定制TPH区分符字段

如果你觉得默认的鉴别列(discriminator)列名不够直观的话,我们可以通过Fluent API来配置discriminator列的类型和命名(Data Annotations 没有标记可用于定制TPH)。

 modelBuilder.Entity<Lodging>().Map(m =>
            {
                m.ToTable("Lodgings");
                m.Requires("LodgingType").HasValue("Standard");
            }).Map<Resort>(m =>
            {
                m.Requires("LodgingType").HasValue("Resort");
            });

Requires的参数即是你要的列名,HasValue用来指定鉴别列中的值。

如果基类只有一个派生类,我们也可以将鉴别列的数据类型设置为bool值:

 modelBuilder.Entity<Lodging>().Map(m =>
      {
       m.ToTable("Lodging");
       m.Requires("IsResort").HasValue(false);
      })
      .Map<Resort>(m =>
      {
       m.Requires("IsResort").HasValue(true);
      });

TPT(Table Per Type)

TPH将所有层次的类都放在了一个表里,而TPT在一个单独的表中储存来自基类的属性,在派生类定义的附加属性储存在另一个表里,并使用外键与主表相连接。

 我们显示的指定派生类生成的表名即可:

    [Table("Resorts")]
    public class Resort : Lodging
    {
        public string Entertainment { get; set; }
        public string Activities { get; set; }
    }

我们可以看到生成了两张表,模型中的派生类Resort映射的表中只包括它自已的两个属性:EntertainmentActivities,基类映射的表中也只包含它自己的属性。并且ResortsLodgingId即是主键也作为外键关联到表Lodgings.

TPC(Table Per ConcreteType)

TPC类似TPT,基类与派生类都映射在不同的表中,不同的是派生类中还包括了基类的字段。TPC只能用Fluent API来配置。

 modelBuilder.Entity<Lodging>().Map(m =>
            {
                m.ToTable("Lodgings");
            }).Map<Resort>(m =>
            {
                m.ToTable("Resorts");
                m.MapInheritedProperties();
            });

生成的数据库中派生类Resort映射的表中Resorts也包括了Lodging中的字段。


评论:

发表评论

最新评论:


圈内热点

  • 怎么轻松学习JavaScript

    js给初学者的印象总是那么的“杂而乱”,相信很多初学者都在找轻松学习js的途径。我试着总结自己学习多年js的经验,希望能给后来的学习者探索出一条“轻松学习js之路”。

  • 我心目中的ASP.NET核心对象

    在我的眼里,Asp.net有三大核心对象:HttpContext, HttpRequest, HttpResponse。除此之外,还有二个对象虽然称不上核心,但仍然比较重要:HttpRuntime,HttpServerUtility

  • IIS内部运行机制

    这篇文章的资料收集整理自各种微软公开的文档,通过比较 IIS5、IIS6、IIS7 这三代 IIS 对请求的处理过程, 让我们熟悉 ASP.NET的底层机制并对请求(request)是怎么从Web服务器传送到ASP.NET运行时有所了解

  • 改善程序员生活质量的3+10习惯

    2017年的一天,代码伴随着手指极具节奏感地输出在IDE上,突然某Chrome插件弹出一封邮件提示:“今天是我在ThoughtWorks的最后一天”。

  • 编程的一些伟大真理

    编程学习中的一些伟大真理,初级程序员都知道吗?

回到顶部