【翻译】EF特性CTP5版: 代码优先示范

【原文地址】EF Feature CTP5: Code First Walkthrough
【原文发表日期】 6 Dec 2010 9:00 PM

我们刚发布了实体框架特性第五个社区技术预览版 (简称CTP5)。 特性CTP5版包括了我们计划在2011年第一个季度以独立的软件包形式发布的新特性的预览内容,希望得到大家的反馈。特性CTP5版建立在随.NET框架4.0和Visual Studio 2010发布的现有实体框架4(简称EF4)的功能之上,是我们先前CTP版本的更新版。

本贴将对代码优先(Code First)开发作一个简介。代码优先允许你使用 C# 或 VB.Net类定义你的模型,可以使用特性(attributes)在你的类和属性上,或者使用流畅(Fluent)API,附加额外的配置。你的模型可以用来生成数据库定义或者映射到现有的数据库上。

映射到现有数据库

在CTP5版本中,我们去除了映射到现有数据库时要做额外配置的需要。如果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 生成数据库定义,但同样的原理适用于映射到现有的数据库上,除了下面的“第8步: 设置起始化策略”并不适用于现有数据库外。

 

1. 安装 EF CTP5版

如果你还没安装的话,那么你需要安装实体框架特性CTP5

 

2. 创建应用

为简单起见,我们将建造一个基本的console应用,使用Code First来做数据访问:

  • 打开 Visual Studio 2010
  • 文件 -> 新 -> 项目…
  • 从左边菜单中选择 “Windows”,然后 “Console应用”
  • 输入“CodeFirstSample”作其名称
  • 选择 “OK”

 

3. 创建模型

让我们使用类来定义一个非常简单的模型,我就在Program.cs文件里定义这些类,但在实际应用中,你应该将你的类们分离成单独的文件,甚至单独的项目。

在Program.cs文件中的Program类定义下面,我定义了下面2个类:

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

 

4. 创建一个上下文(Context)

开始使用类来做数据访问最简洁的方法是,定义一个继承自System.Data.Entity.DbContex的上下文类,对我的模型中的每个类呈示一个类型为DbSet<TEntity>的属性。

现在我们将开始使用CTP5版中的类型,所以我们需要加一个对CTP5程序集的引用:

  • 项目 -> 添加引用…
  • 选择 “.NET” 页
  • 从列表中选择“EntityFramework”
  • 点击 “OK”

你还需要引用现有的实体框架程序集:

  • 项目 -> 添加引用…
  • 选择 “.NET” 页
  • 从列表中选择 “System.Data.Entity”
  • 点击 “OK”

在Program.cs文件的顶部加一个using System.Data.Entity的语句:

using System.Data.Entity;

在我们刚定义的类下面加一个上下文继承类:

public class ProductContext : DbContext
{
    public DbSet<Category> Categories { get; set; }
    public DbSet<Product> Products { get; set; }
}

这些就是我们开始保存和获取数据所需的全部代码,很明显,在幕后发生了很多事情,等一会儿我们来看一下其中细节,但首先我们来用用看。

 

5. 读写数据

在我的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();
        }
    }
}

你现在可以运行应用,会看到插入了新的Category。

我的数据在哪里?

按约定,DbContext会在localhost\SQLEXPRESS中为你创建一个数据库。数据库的名称是根据你的上下文继承类的全名来定的,在我们的情况下,数据库名为“CodeFirstSample.ProductContext”,我们将在本示范的稍后看一下改变这个行为的方法。

模型的发现

DbContext通过查看我们定义的DbSet属性,推断出包括在模型中的类。然后它会使用默认的Code Firs约定,找出主键,外键等。在CTP5版中实现的全套约定在这个约定设计博客贴子中有详细的讨论。

 

6. 读写更多的数据

让我们在刚编写的程序中再多加一些代码,来展示更多的功能。我们将利用DbSet类中的Find方法,根据主键查询一个实体。如果没找到匹配对象,那么Find方法会返回null。我们还将利用LINQ对在Food分类中的所有产品作查询,并将结果按name的字母顺序排列。查询使用现有的LINQ to Entities提供器,所以它支持EF4中ObjectSet/ObjectQuery同样的查询。

将我们刚编写的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();
        }
    }
}

 

7. 改变数据库名称

如果你想要改变为你自动生成的数据库的名称,有一个方法是改动你的DbContext继承的构造器,指定新的数据库的名称。

譬如说,我们想要把数据库名称改为“MyProductDatabase”,我们可以给我们的上下文继承类加一个默认构造器,将这个名字传给DbContext:

public class ProductContext : DbContext
{
    public ProductContext()
        : base("MyProductDatabase")
    { }

    public DbSet<Category> Categories { get; set; }
    public DbSet<Product> Products { get; set; }
}
改变数据库的其他方法

还有若干种方法可以指定应该连接哪个数据库,在将来我们会在单独的贴子中做详述。

  • App.config 文件中的连接字符串
    在App.Config文件中创建与你的上下文同名的连接字符串。
  • DbConnection
    有一个构造器接受DbConnection。
  • 替换默认的约定
    基于上下文名称来定位数据库的这个约定是AppDomain范围的设置,你可以通过System.Data.Entity.Database.DbDatabase.DefaultConnectionFactory的静态属性来做改变。

 

8. 设置起始化策略

在下一节里,我们讲开始改变我们的模型,即意味着数据库定义也需要改变。目前还没有开箱即可用(‘out of the box’)的方案可以原地演变你现有的数据库定义。数据库演变是我们正研究的东西,最近一篇设计博客贴子提供了我们目前的趋向的一个例子。

但是,有机会在上下文第一次在一个AppDomain里运行时,运行一些定制的代码来初始化数据库。这在你想要插入初始数据用于做测试运行时会非常方便,在模型改变后重新创建数据库时也会非常有用。在CTP5版本中,我们包含了几个策略,你可以插接使用,但你也可以编写定制的策略。

在Program.cs的顶部,加一个 using System.Data.Entity.Database 语句:

using System.Data.Entity.Database;

在本示范中,我们只想要在模型改变后,删除和重建数据库,所以在Program类的Main方法的顶部,我加了下述代码:

DbDatabase.SetInitializer<ProductContext>(    new DropCreateDatabaseIfModelChanges<ProductContext>());

 

9. 数据注释(Data Annotations)

到目前为止,我们仅是让EF通过默认的约定去发现模型,但有时候当我们的类并不遵循约定时,我们需要能够做进一步的配置。对此,有2个选项,在本节中,我们将看一下数据注释,Code First的流畅API将在另外一篇贴子中讨论

让我们在模型中加一个Supplier类:

public class Supplier
{
    public string SupplierCode { get; set; }
    public string Name { get; set; }
}

我们还需在上下文继承类中加一个集合:

public class ProductContext : DbContext
{
    public ProductContext()
        : base("MyProductDatabase")
    { }

    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.” (实体类型 ‘Supplier’ 没有定义主键,请为此实体类定义主键),因为EF无法知道SupplierCode应该是Supplier的主键。

我们将使用数据注释,先需要加一个引用:

  • 项目 -> 添加引用…
  • 选择“.NET” 页
  • 从列表中选择“System.ComponentModel.DataAnnotations”
  • 点击 “OK

在Program.cs的顶部加一个using语句:

using System.ComponentModel.DataAnnotations;

现在,我们可以给SupplierCode属性加一个注释,表示它是主键:

public class Supplier
{
    [Key]
    public string SupplierCode { get; set; }
    public string Name { get; set; }
}

CTP5版支持的注释的完整列表如下:

  • KeyAttribute
  • StringLengthAttribute
  • MaxLengthAttribute
  • ConcurrencyCheckAttribute
  • RequiredAttribute
  • TimestampAttribute
  • ComplexTypeAttribute
  • ColumnAttribute
    置于一个属性上,指定其字段名,序数和数据类型
  • TableAttribute
    置于一个类上,指定其数据表名和定义
  • InversePropertyAttribute
    置于一个导航属性上,指定代表关系另一端的属性
  • ForeignKeyAttribute
    置于一个导航属性上,指定代表关系外键的属性
  • DatabaseGeneratedAttribute
    置于一个属性上,指定数据库该如何为该属性生成值(Identity, Computed 或者 None)
  • NotMappedAttribute
    置于一个属性或类上,将其排除在数据库外

 

10. 验证

在CTP5版本中,我们引进了一个新的特性,在尝试保存到数据库前,会验证实例数据是否满足数据注释。

让我们加一个注释,指定Supplier.Name的长度必须在5到20个字符之间:

public class Supplier
{
    [Key]
    public string SupplierCode { get; set; }

    [MinLength(5)]
    [MaxLength(20)]
    public string Name { get; set; }
}

在Program.cs的顶部加一个using语句:

using System.Data.Entity.Validation;

让我们改变Main方法,插入一些不合法的数据,捕捉住异常,显示任何错误的信息:

class Program
{
    static void Main(string[] args)
    {
        DbDatabase.SetInitializer<ProductContext>(            new DropCreateDatabaseIfModelChanges<ProductContext>());

        using (var db = new ProductContext())
        {
            var supplier = new Supplier { Name = "123" };
            db.Suppliers.Add(supplier);

            try
            {
                db.SaveChanges();
            }
            catch (DbEntityValidationException ex)
            {
                foreach (var failure in ex.EntityValidationErrors)
                {
                    Console.WriteLine(                        "{0} failed validation",                         failure.Entry.Entity.GetType());
                    foreach (var error in failure.ValidationErrors)
                    {
                        Console.WriteLine(                            "- {0} : {1}",                             error.PropertyName,                             error.ErrorMessage);
                    }
                }
            }

            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }
}

如果运行应用,我们将看到显示了下列信息:

CodeFirstSample.Supplier failed validation
- Name : The field Name must be a string or array type with a minimum length of ’5′.

Press any key to exit.(CodeFirstSample.Supplier验证失败,Name : 字段Name必须是个字符串或数组类型,最少长度为5,点击任何一键退出)

结语

在本示范中,我们看了如何使用EF特性CTP5版做代码优先(Code First)开发,我们看了如何定义和配置模型,保存和获取数据,配置数据库连接,随着模型的演变更新数据库定义,在写入数据库前做数据验证。

流畅API

除了数据注释外,Code First还呈示了一个用于做配置的流畅API,相关内容在另一个博客贴子里讨论。

反馈和支持

一如以往,我们非常希望你在这个博客贴子上做评论,给我们提供任何关于代码优先(Code First)的反馈。

想得到支持的话,请使用实体框架预览产品论坛

 

Rowan Miller
Program Manager
ADO.NET实体框架开发团队

This entry was posted in 未分类. Bookmark the permalink.

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

*

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>