Entity Framwork CodeFirst 学习笔记五:数据库映射的默认配置和设置

数据库的映射指的就是对数据库进行配置,包括生成表名,架构名,列名。这些内容前面的笔记已经涉及到了,还包括的复杂类型的设置,这里就不在赘述。

本次主要学习和掌握如何将单个类映射到多个表中,多个类如何映射到一个通用表中和各种类继承架构的配置。

让多个实体映射到同一个表:AKA表切分

通常一个数据库表中虽然有很多列,但在很多场景只需要使用其中的一部分,其他的只是一些附加的数据。当我们映射一个实体到这样的表以后,你会发现要浪费资源来处理一些无用的数据。表切分技术可以解决这个问题,这种技术允许在一个单独表中访问多个实体。

为了将多个实体映射到一个通用的表中,实体必须遵循如下规则:

  • 实体必须是一对一关系
  • 实体必须共享一个通用键

看一下书中的例子:

   [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; }        
        public PersonPhoto Photo { get; set; }
    }
    
    [Table("People")]
    public class PersonPhoto
    {
        //共享通用键
        [Key, ForeignKey("PhotoOf")]
        public int PersonId { get; set; }
        [Column(TypeName="image")]
        public byte[] Photo { get; set; }
        public string Caption { get; set; }
        public Person PhotoOf { get; set; }
    }

 两类的确满足我们上面的关系,我们在程序运行的时候插入一条数据:

 static void Main(string[] args)
 {
    Database.SetInitializer(new DropCreateDatabaseIfModelChanges<LxContext>());
    InsertPerson();
 }
 private static void InsertPerson()
 {
    PersonPhoto ph = new PersonPhoto() { Caption = "个人照片", Photo = new byte[8] };
    //可以插入成功
    Person p1 = new Person() { FirstName = "Jhon", LastName = "Micheal", SocialSecurityNumber = 123, Photo = ph };
    using (var context = new LxContext())
    {
       context.Persons.Add(p1);
       context.SaveChanges();
    }
 }

 数据库生成后如下图:

注意:当我们这样做的时候,当我们插入数据的时候,两个类必须都拥有数据。否则会产生“遇到了无效数据。缺少必要的关系” 的异常!

 将一个单独的实体映射到多个表

翻转一下上面的例子,我们可以把 一个单独的实体映射的到多个表中,这种称之为实体分割。实现这个功能 不能使用Data Annotations ,因为Data Annotations 没有子属性的概念。

我们再增加一个类:

    //将PersonInfo 分割成两个表
    public class PersonInfo
    {
        [Key]
        public int PersonId { get; set; }
        public int SocialSecurityNumber { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public byte[] Photo { get; set; }
        public string Caption { get; set; }
    }

 然后用FluntAPI进行配置:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
   modelBuilder.Entity<PersonInfo>().Map(m =>
   {
     m.ToTable("t_PersonInfo");
     m.Properties(p => p.FirstName);
     m.Properties(p => p.LastName);                
     m.Properties(p => p.SocialSecurityNumber);
   }).Map(m =>
          {
            m.ToTable("t_PersonPhoto");
            m.Properties(p => p.Photo);
            m.Properties(p => p.Caption);
          });
}

  结果如下图:

我们发现PersonInfo这个类的确被我们拆分成了两个表,t_PersonInfo 和 t_PersonPhoto,而且还进行了外键关联。

但这里有几个需要注意的地方:
1、虽然生成了两表,但是只有t_PersonInfo 的主键是自增的,t_PersonPhoto 的主键 PersonId 是非自增的,并且是外键关联到了 t_PersonInfo的主键
2、虽然建立了外键但是不会建立级联删除。
3、在使用FluntAPI 进行实体分割的时候,务必不要跳过任何属性。否则Code First还会自动的创建第三张表,保存那些你遗漏的属性。

虽然没有建立级联删除,但是 EF框架知道,如果对t_PersonInfo 操作,它必须建立一个跨越两个表的执行命令,我们来实验一下:

增加一个Person

var p2 = new PersonInfo
{
    FirstName = "Monroe",
    LastName = "Marilyn",
    SocialSecurityNumber = 456
};
using (var context = new LxContext())
{
    context.PersonInfos.Add(p2);
    context.SaveChanges();
}

我们利用Sql Profiler监视,会发现数据库执行了两条SQL分别是:

exec sp_executesql N'insert [dbo].[t_PersonInfo]([SocialSecurityNumber], [FirstName], [LastName])
values (@0, @1, @2)
select [PersonId]
from [dbo].[t_PersonInfo]
where @@ROWCOUNT > 0 and [PersonId] = scope_identity()',N'@0 int,@1 nvarchar(max) ,@2 nvarchar(max) ',@0=456,@1=N'Monroe',@2=N'Marilyn'

exec sp_executesql N'insert [dbo].[t_PersonPhoto]([PersonId], [Photo], [Caption])
values (@0, null, null)
',N'@0 int',@0=1

 修改一个Person:

using (var context = new LxContext())
{
    var PersonList = context.PersonInfos.ToList();
    var p = PersonList[0];
    p.Caption = "my photo";
    p.Photo = new byte[8];
    context.SaveChanges();
}

  监视执行的SQL 为:

--查询
SELECT
[Extent1].[PersonId] AS [PersonId], [Extent2].[SocialSecurityNumber] AS [SocialSecurityNumber], [Extent2].[FirstName] AS [FirstName], [Extent2].[LastName] AS [LastName], [Extent1].[Photo] AS [Photo], [Extent1].[Caption] AS [Caption] FROM [dbo].[t_PersonPhoto] AS [Extent1] INNER JOIN [dbo].[t_PersonInfo] AS [Extent2] ON [Extent1].[PersonId] = [Extent2].[PersonId] --修改 exec sp_executesql N'update [dbo].[t_PersonPhoto] set [Photo] = @0, [Caption] = @1 where ([PersonId] = @2) ',N'@0 varbinary(max) ,@1 nvarchar(max) ,@2 int',@0=0x0000000000000000,@1=N'my photo',@2=1

 删除Person:

using (var context = new LxContext())
{
     var PersonList = context.PersonInfos.ToList();
     var p = PersonList[0];
     context.PersonInfos.Remove(p);
     context.SaveChanges();
}

 监视生成的SQL为:

--查询
SELECT 
[Extent1].[PersonId] AS [PersonId], 
[Extent2].[SocialSecurityNumber] AS [SocialSecurityNumber], 
[Extent2].[FirstName] AS [FirstName], 
[Extent2].[LastName] AS [LastName], 
[Extent1].[Photo] AS [Photo], 
[Extent1].[Caption] AS [Caption]
FROM  [dbo].[t_PersonPhoto] AS [Extent1]
INNER JOIN [dbo].[t_PersonInfo] AS [Extent2] ON [Extent1].[PersonId] = [Extent2].[PersonId]

--删除
exec sp_executesql N'delete [dbo].[t_PersonPhoto]
where ([PersonId] = @0)',N'@0 int',@0=1
exec sp_executesql N'delete [dbo].[t_PersonInfo]
where ([PersonId] = @0)',N'@0 int',@0=1

 通过这些操作,我们已经知道虽然没有生成级联删除或者修改,但是EF框架会知道如何执行,不用我们去手动管理。

类继承的映射与配置:

EF框架支持各种模型中的继承层次结构。无论你使用Code First,Model First还是Database First来定义模型都不用担心继承的类型问题,也不用考虑EF框架如何使用这些类型进行查询,跟踪变更和更新数据。

TPH(Table Per Hierarchy):

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

public class Pet
{
    public int PetId { get; set; }        
    public string PetName { get; set; }
 }

public class Dog : Pet
{
    public int Age { get; set; }
    public string Des { get; set; }
}

 然后插入一些数据:

var pet = new Pet
{
     PetName = "兔宝宝",
};
 var dog = new Dog
{
   PetName="狗宝宝",
   Age=1,
   Des="1岁狗宝宝"
};
using (var context = new LxContext()) { context.Pets.Add(pet); context.Pets.Add(dog); context.SaveChanges(); }

 运行程序会看到生成的表结构和插入的数据:

我们会发现,生成了一个表,其列名就是父类和子类属性的和,还有一鉴别列 Discriminator,看数据我们就能知道,鉴别列的数值是通过类名来进行区分的,Pet :为父类数据,Dog:为子类数据。

以上是默认的配置规则,当然也可以利用FluntAPI方式进行配置,修改鉴别列的列名和值,EF 会自动识别值的类型。

modelBuilder.Entity<Pet>().Map(m =>
{
     m.ToTable("t_Pet");
     m.Requires("IsFather").HasValue(true);
}).Map<Dog>(m =>
{
     m.Requires("IsFather").HasValue(false);
});

TPT(Table Per Type):

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

我们只需要为派生类指定表名即可,可以使用Data Annotations 也可以使用Fluent API来完成这项工作。

DataAnnotation方式:

[Table("t_Pet")]
public class Pet
{
   public int PetId { get; set; }        
   public string PetName { get; set; }
}
[Table("t_Dog")]
public class Dog : Pet
{
   public int Age { get; set; }
   public string Des { get; set; }
}

 结果如下:

FluntAPI 方式: 

modelBuilder.Entity<Pet>().ToTable("t_Pet");
modelBuilder.Entity<Dog>().ToTable("t_Dog");

 也可以更具体指定关系:

 modelBuilder.Entity<Pet>().Map(m =>
 {
       m.ToTable("t_Pet");
  }).Map<Dog>(m =>
 {
       m.ToTable("t_Dog");
 });

 TPC(Table Per Concrete Type)

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

modelBuilder.Entity<Pet>()
.Map(m => {
         m.ToTable("t_Pet");
       })
.Map<Dog>(m => {
          m.ToTable("t_Dog");
          m.MapInheritedProperties();
});

生成表结构如下:

虽然生成了表,但是两个表的主键PetId 均不是自动增长,我们插入数据的时候,需要指定PetId:

var pet = new Pet
{
      PetId=1,
      PetName = "兔宝宝",
 };

//虽然生成了两个表,但是PetId 不能=1,否则出现重复键异常
 var dog = new Dog
 {
       PetId=2,
       PetName="狗宝宝",
       Age=1,
       Des="1岁狗宝宝"
};
 using (var context = new LxContext())
{
       context.Pets.Add(pet);
       context.Pets.Add(dog);
       context.SaveChanges();
 }

 结果如下:

注意:TPH 、TPT、TPC 三种种方式的 其他数据操作比如查询,删除,修改均没有试验,需要待考察!

--=源码下载=--

posted @ 2013-05-23 17:31  Rising_Sun  阅读(3790)  评论(1编辑  收藏  举报