这一章主要主要讲的是我们的模型如何映射到数据库,而不影响模型,以及不同的映射场景。
一、表名和列名
1.指定表名
[Table("PersonPhotos")] public class PersonPhoto
或
[Table("Locations", Schema="baga")] public class Destination
Schema修改数据库架构,默认是dbo。
API:
modelBuilder.Entity<Destination>().ToTable("Locations", "baga");
2.列名
[Column("LocationID")] public int DestinationId { get; set; } [Required, Column("LocationName")] public string Name { get; set; }
API:
Property(d => d.Nam .IsRequired().HasColumnName("LocationName"); Property(d => d.DestinationId).HasColumnName("LocationID");
二、表分割
将一个模型拆成两张表,比如Destination。
public class Destination { [Key] public int DestinationId { get; set; } [Required] public string Name { get; set; } public string Country { get; set; } [MaxLength(500)] public string Description { get; set; } [Column(TypeName = "image")] public byte[] Photo { get; set; } public List<Lodging> Lodgings { get; set; } }
API:(DataAnnotations不能处理子对象)
modelBuilder.Entity<Destination>() .Map(m => { m.Properties(d => new {d.Name, d.Country, d.Description}); m.ToTable("Locations"); }) .Map(m => { m.Properties(d => new {d.Photo}); m.ToTable("LocationPhotos"); });
运行后,Destination 拆分成了Locations和LocationPhotos
当Destination添加数据的时候,这个两个表的主键都会增加。
三、数据库映射控制
1.模型要映射到数据库中有三种方式。
1).将对象加入到Dbset中。
2).在别的类型中,引用当前类型。(Person包含PersonPhoto,PersonPhoto是不需要加人Dbset的。)
3).通过API在DbModelBuilder方法中配置。
前面两种我们前面都已经尝试过,对于第三种,不使用Dbset 就需要使用EntityTypeConfiguration。 可以建立一个空的:
public class ReservationConfiguration :EntityTypeConfiguration<Reservation> {}
再加入modelBuider
modelBuilder.Configurations.Add(new ReservationConfiguration());
2.忽略类型映射。
如果不想数据库映射某个类型,我们可以将其忽略掉。
[NotMapped] public class MyInMemoryOnlyClass //API: modelBuilder.Ignore<MyInMemoryOnlyClass>();
3.属性映射类型
1)只能是EDM支持的类型。
Binary, Boolean, Byte, DateTime, DateTimeOffset, Decimal, Double, Guid, Int16, Int32, Int64, SByte, Single, String, Time
枚举类型现在已经支持了。MyType是个枚举类型,Flag是Uint型,不支持EF就自动忽略了。
2)可获取的属性
.Public属性会自动映射。
.Setter可以是限制访问,但Getter必须是Public。
.如果想非Public的属性也映射,就需要通过API来配置。
如果想配置私有属性,这样就需要将Config类置于内部。如下,Name是private的,PersonConfig想要获取这个类型就需要置于Person内部了:
public class Person { public int PersonId { get; set; } private string Name { get; set; } public class PersonConfig : EntityTypeConfiguration<Person> { public PersonConfig() { Property(b => b.Name); } } public string GetName() { return this.Name; } public static Person CreatePerson(string name) { return new Person { Name = name }; } }
3)属性忽略
.没有setter
如果People包含FullName属性,EF是不会映射这个属性的。
public string FullName { get { return String.Format("{0} {1}", FirstName.Trim(), LastName); } }
.直接忽略
[NotMapped] public string TodayForecast{get;set;} //API: Ignore(d => d.TodayForecast);
四、继承类型映射
1)默认继承 Table Per Hierarchy (TPH) 子类父类在一张表中。
public class Lodging { public int LodgingId { get; set; } [Required] [MaxLength(200)] [MinLength(10)] public string Name { get; set; } [StringLength(200, MinimumLength = 2)] public string Owner { get; set; } public bool IsResort { get; set; } public Destination Destination { get; set; } public int DestinationId { get; set; } public List<InternetSpecial> InternetSpecials { get; set; } [InverseProperty("PrimaryContactFor")] public Person PrimaryContact { get; set; } [InverseProperty("SecondaryContactFor")] public Person SecondaryContact { get; set; } } public class Resort : Lodging { public string Entertainment { get; set; } public string Activities { get; set; } }
没有创建Resort表,而是Lodgings表中多了Restort的字段
而且还多了个Discriminator (辨别者)列,nvarchar(128) ,专门用来识别是哪个类型,插入2组数据。
private static void InsertLodging() { var lodging = new Lodging { Name = "Rainy Day Motel", Destination = new Destination { Name = "Seattle, Washington", Country = "USA" } }; using (var context = new BreakAwayContext()) { context.Lodgings.Add(lodging); context.SaveChanges(); } } private static void InsertResort() { var resort = new Resort { Name = "Top Notch Resort and Spa", Activities = "Spa, Hiking, Skiing, Ballooning", Destination = new Destination { Name = "Stowe, Vermont", Country = "USA" } }; using (var context = new BreakAwayContext()) { context.Lodgings.Add(resort); //没有去添加Dbset<Resort> context.SaveChanges(); } }
同样我们可以定义discriminator的列名和类型的值。
modelBuilder.Entity<Lodging>() .Map(m => { m.ToTable("Lodgings"); m.Requires("LodgingType").HasValue("Standard"); }) .Map<Resort>(m => m.Requires("LodgingType").HasValue("Resort"));
这里Requires和Hasvalue都是来定义discriminator 列的。
再次运行,列名和类型值也都已经改变。
也可以指定为bool类型。将这个任务交给IsResort
modelBuilder.Entity<Lodging>() .Map(m => { m.ToTable("Lodgings"); m.Requires("IsResort").HasValue(false); }) .Map<Resort>(m => m.Requires("IsResort").HasValue(true));
要注意的是Lodging模型中不能再包含IsResort属性,在模型验证的时候就出错,EF它分不清这个IsResort和识别类型IsResort是不是同一个。不然会引发异常。
2)Table Per Type (TPT) Hierarchy (分开存储,派生类只存储自己独有的属性)
给派生类加上表名就是TPT了。
[Table("Resorts")] public class Resort : Lodging { public string Entertainment { get; set; } public string Activities { get; set; } }
这样EF会创建一个新表,并拥有Lodging的key。
是插入两条数据:
Lodgings
Resorts:
API:
modelBuilder.Entity<Lodging>() .Map<Resort>(m => { m.ToTable("Resorts"); } );
可以写在一起。
modelBuilder.Entity<Lodging>().Map(m => { m.ToTable("Lodgings"); }).Map<Resort>(m => { m.ToTable("Resorts"); });
3)Table Per Concrete Type (TPC) Inheritance 父类和派生各自拥有全部属性
好比Resorts作为一个表拥有所有的属性。只能通过API的MapInheritedProperties来实现。且ToTable方法不能少。
modelBuilder.Entity<Lodging>() .Map(m => { m.ToTable("Lodgings"); }) .Map<Resort>(m => { m.ToTable("Resorts"); m.MapInheritedProperties(); });
这个时候运行会出错:
TPC要求使用TPC的类如果被引用必须有一个显示的外键属性。就像Lodging中的DestinationId对于Destination
public class Lodging { public int LodgingId { get; set; } [Required] [MaxLength(200)] [MinLength(10)] public string Name { get; set; } [StringLength(200, MinimumLength = 2)] public string Owner { get; set; } // public bool IsResort { get; set; } public Destination Destination { get; set; } public int DestinationId { get; set; } public List<InternetSpecial> InternetSpecials { get; set; } public Person PrimaryContact { get; set; } public Person SecondaryContact { get; set; } }
这里的PrimaryContact 和 SecondaryContact 没有指明外键。需要强制给它加上外键。这里会让有的人难受,因为EF的规则而去要改变自己的领域模型。
public class Lodging { //..... public int? PrimaryContactId { get; set; } public Person PrimaryContact { get; set; } public int? SecondaryContactId { get; set; } public Person SecondaryContact { get; set; } }
这个时候还没完,EF并不清楚这些外键。需要再配置。
modelBuilder.Entity<Lodging>() .Map(m => m.ToTable("Lodgings")) .Map<Resort>(m => { m.ToTable("Resorts"); m.MapInheritedProperties(); }); modelBuilder.Entity<Lodging>().HasOptional(l => l.PrimaryContact) .WithMany(p => p.PrimaryContactFor) .HasForeignKey(p => p.PrimaryContactId); modelBuilder.Entity<Lodging>().HasOptional(l => l.SecondaryContact) .WithMany(p => p.SecondaryContactFor) .HasForeignKey(p => p.SecondaryContactId);
生成的表如下。都带有三个外键。
对于基类是抽象类型,对EF来说没有多大的区别。至于这三种该怎么用。这里有博客:http://blogs.msdn.com/b/alexj/archive/2009/04/15/tip-12-choosing-an-inheritance-strategy.aspx
各种表现上面还是TPT最佳。