精进不休 .NET 4.0 (9) - ADO.NET Entity Framework 4.1 之 Code First
作者:webabcd
介绍
ADO.NET Entity Framework 4.1 的新增功能:Code First
示例
Web.config
<?xml version="1.0"?> <configuration> <connectionStrings> <!-- 需要将 Persist Security Info 设置为 True,以便保存密码信息 因为 Database.SetInitializer<MyContext>(new DropCreateDatabaseIfModelChanges<MyContext>()); 在判断 Code First 与数据库结构是否一致时需要连接 master 库 --> <add name="MyConnection" providerName="System.Data.SqlClient" connectionString="server=.;database=MyDB;uid=sa;pwd=111111;Persist Security Info=True" /> </connectionStrings> </configuration>
Global.asax.cs
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Security; using System.Web.SessionState; using System.Data.Entity; using EF41.CodeFirst; namespace EF41 { public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e) { // 当 Code First 与数据库结构不一致时,删除原数据库,根据 Code First 重新生成新的数据库结构 Database.SetInitializer<MyContext>(new DropCreateDatabaseIfModelChanges<MyContext>()); // 什么都不做 // Database.SetInitializer<MyContext>(null); } } }
Category.cs
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace EF41.CodeFirst { public class Category { public string CategoryId { get; set; } public string Name { get; set; } public string Comment { get; set; } public virtual ICollection<Product> Products { get; set; } } }
Product.cs
/* * 实体到数据库结构的映射是通过默认的约定来进行的,如果需要修改的话,有两种方式,分别是:Data Annotations 和 Fluent API * 以下介绍通过 Data Annotations 来修改实体到数据库结构的映射(通过 Fluent API 来修改实体到数据库结构的映射的方法参见 MyContext.cs 文件) * Table - 指定实体所对应的数据库的表名,不指定则对应的表名为类名的复数形式 * Key - 指定是否是主键,不指定则 Code First 会将名为“Id”或“<类名>Id”的字段推断为主键,且如果它的类型是"int"或"long"或"short"的话,则会在数据库中默认注册为 identity 字段。注:主键的推断与大小写无关 * DatabaseGenerated - 指定字段的值在数据库中的生成方式 * DatabaseGeneratedOption.None - 不做任何处理 * DatabaseGeneratedOption.Identity - 标识列 * DatabaseGeneratedOption.Computed - 计算列 * Required - 指定为必填字段,即指定数据库对应的列不允许为 null 值 * MaxLength - 指定字段的最大长度,未指定则为 max * StringLength - 指定字段的长度范围 * Column - 指定字段所对应的数据库中的列名,默认情况下数据库中的列名同 Code First 中的字段名 * NotMapped - 没有对应关系,即此字段不会在数据库中生成对应的列 * Timestamp - 指定对应的数据库中的列的类型为 datetime * ForeignKey - 指定外键的名称,默认情况下与导航属性的主键名称相同的字段会自动被标记为外键 * InverseProperty - 指定导航属性的反转属性,默认情况下按实体的互相引用自行推断 * 所谓“反转属性”,按本例看就是 Product.Category 的反转就是 Category.Products * ComplexType - 复杂类型,如果字段类型为一个实体类,则此字段会被自动标记为复杂类型,被标记为复杂类型的字段为必填字段,参见 Price.cs 文件 * Timestamp - 将 Code First 中的类型为 byte[] 的字段对应到数据库中的类型为 timestamp 的列 * ConcurrencyCheck - 指定字段为用于乐观并发检查的字段,为了简单,建议同时将此字段也标记为 Timestamp */ using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.ComponentModel.DataAnnotations; namespace EF41.CodeFirst { [Table("CategoryProduct")] public class Product { [Key] [DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.Identity)] public int ProductId { get; set; } [Required] [MaxLength(128)] // [StringLength(128, MinimumLength = 16)] [Column("ProductName")] public string Name { get; set; } [NotMapped] public string Comment { get; set; } public DateTime CreateTime { get; set; } public Price Price { get; set; } [ConcurrencyCheck] [Timestamp] public byte[] TimeStamp { get; set; } public string CategoryId { get; set; } [ForeignKey("CategoryId")] [InverseProperty("Products")] public virtual Category Category { get; set; } } }
Price.cs
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.ComponentModel.DataAnnotations; namespace EF41.CodeFirst { // 以本例来说,对应到数据库的列将被拆为两个,分别是:Price_Dollar 和 Price_RMB,且均为必填列 [ComplexType] public class Price { public Price() { Dollar = null; RMB = null; } public decimal? Dollar { get; set; } public decimal? RMB { get; set; } } }
MyContext.cs
/* * 需要引用 EntityFramework(版本 4.1) * 需要引用 System.Data.Entity * 需要引用 System.ComponentModel.DataAnnotations */ using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Data.Entity; namespace EF41.CodeFirst { // 创建的 Context 要继承自 DbContext public class MyContext : DbContext { // 构造函数中的参数用于指定 connectionStrings 的 name // 默认值为 Context 类的类全名,本例为 EF41.CodeFirst.MyContext public MyContext(string connString) : base(connString) { // DbContextConfiguration.LazyLoadingEnabled - 是否启用延迟加载,默认值为 true // true - 延迟加载(Lazy Loading):获取实体时不会加载其导航属性,一旦用到导航属性就会自动加载 // false - 直接加载(Eager loading):通过 Include 之类的方法显示加载导航属性,获取实体时会即时加载通过 Include 指定的导航属性 this.Configuration.LazyLoadingEnabled = true; // DbContextConfiguration.AutoDetectChangesEnabled - 是否自动监测变化,默认值为 true this.Configuration.AutoDetectChangesEnabled = true; } // 所有需要关联到 Context 的类都要类似如下代码这样定义 public DbSet<Category> Categories { get; set; } public DbSet<Product> Products { get; set; } // 实体到数据库结构的映射是通过默认的约定来进行的,如果需要修改的话,有两种方式,分别是:Data Annotations 和 Fluent API // 以下介绍通过 Fluent API 来修改实体到数据库结构的映射(通过 Data Annotations 来修改实体到数据库结构的映射的方法参见 Product.cs 文件) protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Category>() .Property(s => s.Name) .IsUnicode(false) // 指定对应到数据库的类型为 varchar,IsUnicode(true) 为 nvarchar,默认为 nvarchar .IsRequired() // 指定对应到数据库的列为必填列 .HasMaxLength(64); // 指定对应到数据库的列的最大长度为 64 } } }
Demo.aspx.cs
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Data.Entity; namespace EF41.CodeFirst { public partial class Demo : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { // Code First 中通过 DbContext API 实现增删改查的 Demo using (var db = new MyContext("MyConnection")) { Random random = new Random(); var category = new Category { CategoryId = Guid.NewGuid().ToString(), Name = "software " + random.Next(1, int.MaxValue) }; db.Categories.Add(category); var product = new Product { Name = "windows " + random.Next(1, int.MaxValue), Category = category, CreateTime = DateTime.Now, Price = new Price() }; var product2 = new Product { Name = "windows " + random.Next(1, int.MaxValue), Category = category, CreateTime = DateTime.Now, Price = new Price() }; db.Products.Add(product); db.Products.Add(product2); int recordsAffected = db.SaveChanges(); Response.Write("影响到数据库的行数:" + recordsAffected.ToString()); /* * DbContext API 的一些关键点 * * db.Categories.Find() - 通过传递主键值作为参数查找实体,复合主键就传多个参数 * db.Categories.Add() - 把一个新增的实体添加到上下文 * db.Categories.Attach() - 把一个已存在的实体添加到上下文 * db.Entry(entity).State = System.Data.EntityState.Modified - 修改实体状态 * db.Categories.AsNoTracking() - 不被 Context 跟踪,通过 NoTracking 获取的实体,其状态是 Detached 状态。当仅仅是获取数据的时候可以用,有助于提高效率 * 属性的相关操作,当属性改变时,会自动监测实体状态,即 IsModified = true * db.Entry(product).Property(p => p.Name).CurrentValue * db.Entry(product).Property("Name").CurrentValue * 直接加载(Eager loading)的方法 * db.Categories.Include(p => p.Products.First()) * db.Categories.Include(p => p.Products) * db.Entry(product).Reference(p => p.Category).Load() * 使用 sql * db.Categories.SqlQuery("select * from Categories").ToList() // 有实体的情况 * db.Database.SqlQuery<string>("select Name from Categories").ToList(); // 无实体的情况 * db.Database.ExecuteSqlCommand(sql); // 直接执行 sql */ } } } }
OK
[源码下载]