代码改变世界

Programming Entity Framework CodeFirst--数据库约定和配置

2015-03-12 08:01  stoneniqiu  阅读(988)  评论(3编辑  收藏

   这一章主要主要讲的是我们的模型如何映射到数据库,而不影响模型,以及不同的映射场景。

一、表名和列名

  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 };
}
}
View Code

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();
            }
        }
View Code

同样我们可以定义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最佳。