public class Destination { public int DestinationId {get; set;} //以Id结尾,默认key。否则会报nokey的异常 public string Name {get; set;} public string Country {get;set;} public string Description {get; set;} public byte[] Photo {get; set;} public List<Lodging> Lodgings {get; set;} } public class Lodging { public int LodgingId { get; set; } public string Name { get; set; } public string Owner { get; set; } public bool IsResort { get; set; } public Destination Destination { get; set; } }
一个目的地(Destination)包含了一组借宿(Lodging);而一个借宿(Lodging)属于某个目的地(Destination)。这是上面的Domain Model所展现的。
当一个继承自DbContext(推荐使用DbContext,比ObjectContext更轻量级)实现如下:
public class BreakAwayContext : DbContext { public DbSet<Destination> Destinations { get; set; } public DbSet<Lodging> Lodgings { get; set; } }
当有一个BreakAwayContext的实例被使用时,如果没有存在对应的数据库,会创建数据库和根据类型推测出数据库表结构;
DbContext还包含了Validation的功能
观察数据库中生成的表结构,会发现:
- Destinations表中,DestinationId是主键。而Lodgings表中有名为Destination_DestinationId的外键,指向DestionationId。
- 仔细看会发现Destination_DestinationId是可空的
- 字段类型都为默认长度 e.g. string -> nvarchar(max)
- Photo字段的类型为varbinary(max)
- 有一个表叫做dbo.EdmMetadata???not sure it is right?? 包含来一个快照
所以,通过默认的方式,将有很多不好的地方。有两种方式改变:Attribute-based Data Annotations or strongly typed Fluent API.
Attribute-based Data Annotation:
public class Destination { 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; } }
根据上面的改动,Name在数据库中变成必须的,Description最大长度为500,而Photo的类型为image。
当改动完成后,程序运行不了了。因为跟第一次运行程序有一个区别,数据库已经存在了。:(
首先为什么会报错。当第一次运行一个Model时,这个Model会通过Building Process。然后用它与内存中最新的Model(in-memory model last version-which it can see by reading the EdmMetadata table。EdmMetadata里面包含了一个快照)进行比较。在我们遇到这种情况下,Code First意识到新Model跟之前的Metadata不匹配,不能保证Model能否正确的Mapping,所以,报错来。
然后我们如何处理:
- 可以删掉已有的数据库,重新让Code First根据它自己的规则重新创建。
- 通过设置策略,告诉应用删除数据库当Model改变:Database.SetInitializer(new DropCreateDatabaseIfModelChanges<T>());
Fluent API:
protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Destination>() .Property(d => d.Name).IsRequired(); modelBuilder.Entity<Destination>() .Property(d => d.Description).HasMaxLength(500); modelBuilder.Entity<Destination>() .Property(d => d.Photo).HasColumnType("image"); modelBuilder.Entity<Lodging>() .Property(l => l.Name).IsRequired().HasMaxLength(200); }
如果有很多配置,也可以把他们放在一起。通过继承EntityTypeConfigration<T>
public class DestinationConfiguration : EntityTypeConfiguration<Destination> { public DestinationConfiguration() { Property(d => d.Name).IsRequired(); Property(d => d.Description).HasMaxLength(500); Property(d => d.Photo).HasColumnType("image"); } } public class LodgingConfiguration : EntityTypeConfiguration<Lodging> { public LodgingConfiguration() { Property(l => l.Name).IsRequired().HasMaxLength(200); } }
但别忘记在OnModelCreating方法里面加下面代码:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new DestinationConfiguration());
modelBuilder.Configurations.Add(new LodgingConfiguration());
}
Note:
- DataAnnotation是.NET4用来做验证用的,并不是EF独有的。MVC也可以用Annotation来做前端验证。
- DataAnnotation和Fluent API各有独特的一些地方。通常来讲,Fluent API比DataAnnotation拥有更多的配置。
What Is a Fluent API?
The concept of a fluent API isn’t specific to Code First or the Entity Framework. The
fundamental idea behind a fluent API involves using chained method calls to produce
code that is easy for the developer to read. The return type of each call then defines the
valid methods for the next call. For example, in the Code First Fluent API, you can use
the Entity method to select an entity to configure. IntelliSense will then show you all
the methods that can be used to configure an Entity. If you then use the Property
method to select a property to configure, you will see all the methods available for
configuring that particular property.