这篇文章介绍Code First开发以及如何与DbContext API一起使用。Code First允许使用C#或VB.NET类定义模型,在类或属性上有选择性的执行额外的配置或者使用Fluent API。模型可用于生成数据库架构或映射现有的数据库。
本次演示需要安装Visual Studio 2010。
映射现有数据库
本次演示将展示Code First生成数据库架构,但同样适用于映射现有数据库,除了"六、设置初始化策略"。如果Code First指向一个没有创建的现有数据库,那么它会尝试使用指定的配置访问数据库。将Code First指向现有数据库最简单的方法是添加一个App/Web.config连接字符串,和你派生的DbContext类同名:
<connectionStrings> <add name="MyProductContext" providerName="System.Data.SqlClient" connectionString="Server=.\SQLEXPRESS;Database=Products;Trusted_Connection=true;"/> </connectionStrings>
一、创建应用程序
为了使问题简单,我们建立一个基本的控制台应用程序,使用Code First执行数据访问:
1. 打开Visual Studio 2010
2. File -> New -> Project…
3. 从左侧菜单中选择"Windows",然后选择"Console Application"
4. 输入"CodeFirstSample"作为名字
5. 选择"OK"
二、创建模型
让我们使用类定义一个非常简单的模型。我只在Program文件中定义它们,但是在真正的应用程序中,应该拆分类到单独的文件中或者单独的项目中。
在Program类下面定义以下两个类:
public class Category { public string CategoryId { get; set; } public string Name { get; set; } public virtual ICollection<Product> Products { get; set; } } public class Product { public int ProductId { get; set; } public string Name { get; set; } public string CategoryId { get; set; } public virtual Category Category { get; set; } }
三、创建上下文
使用类进行数据访问最简单的方式是在我的模型中定义一个继承自System.Data.Entity.DbContext的上下文以及为每个类公开一个类型化的DbSet<TEntity>。
现在我们开始使用Entity Framework的类型,所以需要添加EntityFramework NuGet包:
1. Project –> Add Library Package Reference…
2. 选择"Onlie"选项卡
3. 搜索"EntityFramework"并选择该包
4. 点击"Install"
在Program.cs的顶部添加一个using语句:
using System.Data.Entity;
在现有类的下面添加一个继承自DbContext的上下文:
public class ProductContext : DbContext { public DbSet<Category> Categories { get; set; } public DbSet<Product> Products { get; set; } }
在项目中添加一个App.config文件,添加如下配置代码:
<connectionStrings> <add name="ProductContext" providerName="System.Data.SqlClient" connectionString="Server=.;Database=Products;Trusted_Connection=true;"/> </connectionStrings>
存储和检索数据需要写的代码就这些。
四、读写数据
代码在Program文件中的Main方法中,如下:
class Program { static void Main(string[] args) { using (var db = new ProductContext()) { // Add a food category var food = new Category { CategoryId = "FOOD", Name = "Foods" }; db.Categories.Add(food); int recordsAffected = db.SaveChanges(); Console.WriteLine("Saved {0} entities to the database, press any key to exit.", recordsAffected); Console.ReadKey(); } } }
我的数据在哪里?
如果不添加上面的App.config,根据约定DbContext已经localhost\SQLEXPRESS上创建了一个数据库。数据库根据你的派生上下文的完全限定名命名,在我们的例子中是"CodeFirstSample.ProductContext"。添加了App.config,那么就根据配置生成数据库了。
模型发现
DbContext通过查看DbSet属性可以知道什么类包含在模型中。然后它使用默认的Code First约定寻找主键、外键等等。
五、读写更多数据
让我们展示更多的功能。我们在DbSet上使用Find方法,根据主键来查找实体。如果没找到匹配的实体,Find就返回null。我们还可以使用LINQ查询Food类别下的所有产品,根据字母顺序排序。
修改上面Main方法的代码:
class Program { static void Main(string[] args) { using (var db = new ProductContext()) { // Use Find to locate the Food category var food = db.Categories.Find("FOOD"); if (food == null) { food = new Category { CategoryId = "FOOD", Name = "Foods" }; db.Categories.Add(food); } // Create a new Food product Console.Write("Please enter a name for a new food: "); var productName = Console.ReadLine(); var product = new Product { Name = productName, Category = food }; db.Products.Add(product); int recordsAffected = db.SaveChanges(); Console.WriteLine("Saved {0} entities to the database.", recordsAffected); // Query for all Food products using LINQ var allFoods = from p in db.Products where p.CategoryId == "FOOD" orderby p.Name select p; Console.WriteLine("All foods in database:"); foreach (var item in allFoods) { Console.WriteLine(" - {0}", item.Name); } Console.WriteLine("Press any key to exit."); Console.ReadKey(); } } }
六、设置初始化策略
在下面的部分,将开始改变我们的模型,从而意味着数据库架构也需要改变。目前,还没有"out of box"的解决方案可以迁移现有架构。数据库迁移是我们目前正在处理的事情,在这方面Code First Migratioins Alpha3已经可用了。
虽然没有RTM的迁移方案,但是当上下文在应用程序域中第一次使用时有机会运行一些自定义的逻辑来初始化数据库。如果你想为了测试插入一些种子数据非常方便,同时如果模型发生了变化重新生成数据库也非常有用。在EF中包含几个初始化策略,当然你也可以自定义你自己的策略。
本演示仅仅想模型发生变化时,移除并重新创建数据库,所以在Main方法的顶部添加如下代码:
Database.SetInitializer<ProductContext>(new DropCreateDatabaseIfModelChanges<ProductContext>());
七、数据批注
目前为止,我们仅仅让EF使用默认的约定发现模型,但是有些时候我们的类不遵循约定,我们需要执行进一步的配置。有两个选项,这一部分看一下数据批注,然后下一部分是Fluent API。
在我们的模型中添加一个supplier类:
public class Supplier { public string SupplierCode { get; set; } public string Name { get; set; } }
还需要添加一个集合到派生的上下文:
public class ProductContext : DbContext { public DbSet<Category> Categories { get; set; } public DbSet<Product> Products { get; set; } public DbSet<Supplier> Suppliers { get; set; } }
如果现在运行我们的程序会得到一个InvalidOperationException异常,说是"EntityType 'Supplier' has no key defined. Define the key for this EntityType."因为EF没办法知道SupplierCode是Supplier的主键。
现在我们使用数据批注,所以需要在Program.cs的顶部添加一个using语句:
using System.ComponentModel.DataAnnotations;
现在我们就可以为SupplierCode属性添加批注指定它为主键:
public class ProductContext : DbContext { public DbSet<Category> Categories { get; set; } public DbSet<Product> Products { get; set; } public DbSet<Supplier> Suppliers { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Supplier>() .Property(s => s.Name) .IsRequired(); } }
EF支持的批注:
- KeyAttribute
- StringLengthAttribute
- MaxLengthAttribute
- ConcurrencyCheckAttribute
- RequiredAttribute
- TimestampAttribute
- ComplexTypeAttribute
- ColumnAttribute
Placed on a property to specify the column name, ordinal & data type - TableAttribute
Placed on a class to specify the table name and schema - InversePropertyAttribute
Placed on a navigation property to specify the property that represents the other end of a relationship - ForeignKeyAttribute
Placed on a navigation property to specify the property that represents the foreign key of the relationship - DatabaseGeneratedAttribute
Placed on a property to specify how the database generates a value for the property (Identity, Computed or None) - NotMappedAttribute
Placed on a property or class to exclude it from the database
八、Fluent API
Fluent API被认为是更高级的功能,我们推荐使用数据批注,除非你的需求要求你使用Fluent API。
访问fluent API,我们重写DbContext中的OnModelCreating方法,下面的代码中,我们使用fluentAPI配置Supplier的Name属性为必须:
public class ProductContext : DbContext { public DbSet<Category> Categories { get; set; } public DbSet<Product> Products { get; set; } public DbSet<Supplier> Suppliers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Supplier>() .Property(s => s.Name) .IsRequired(); }
}
总结
在这个演示中我们使用EF的Code First开发。我们定义和配置了模型,存储和检索数据,配置数据库连接,当我们的模型发生变化时更新数据库架构以及验证数据。