【翻译】实体框架和Code First中的数据注释

【原文地址】Data Annotations in the Entity Framework and Code First
【原文发表日期】 30 Mar 2010 10:14 PM

数据注释(Data annotation)特性是在.NET 3.5中引进的,给ASP.NET web应用中的类提供了一种添加验证的方式。之后,RIA服务也开始使用数据注释,现在成了Silverlight的一部分。Code First允许你使用C# 或VB.NET代码来建立EDM实体框架模型,目前是第三个CTP版本(【译注:当前最新版本为CTP4,本文是在CTP4之前发表的】)。

从这些CTP版本得到的反馈表明,对Code First在类和属性定义中读取数据注释特性来配置象最大字符串长度,哪个属性是键,或者是用于存储实体类型的数据表名等方面,有很大的需求。

本贴对我们将采用的现有数据注释特性以及提议的新特性做个概述。

使用数据注释

在使用Code First建立一个模型时,你先编写一堆实体类。例如:

public class Book
{
    public string ISBN { get; set; }
    public string Title { get; set; }
    public string AuthorSSN { get; set; }
    public Person Author { get; set; }
}

public class Person
{
    public string SSN { get; set; }
    public string Name { get; set; }
    public ICollection<Book> Books { get; set; }
}

在CTP3版本的Code First中使用这个类模型时,如果你想要对类的属性有所约束的话,你需要使用流畅API来描述哪个属性是键,存在哪些关系,每个字符串属性的长度等。这些是非常常见的任务,我们想要Code First的用户不用流畅API描述模型,就能够做这类的配置。通过使用数据注释,上面的模型特征可以作为类定义的一部分用声明的方式表示出来:

public class Book
{
    [Key]
   
public string ISBN { get; set; }

    [StringLength(256)]
    public string Title { get; set; }

    public string AuthorSSN { get; set; }

    [RelatedTo(RelatedProperty=“Books”, Key=”AuthorSSN”, RelatedKey=”SSN”)]
    public Person Author { get; set; }
}

public class Person
{
    [Key]
    public string SSN { get; set; }

    [StringLength(512)]
    public string Name { get; set; }

    [RelatedTo(RelatedProperty=”Author”)]
    public ICollection<Book> Books { get; set; }
}

现有的数据注释特性

在System.ComponentModel.DataAnnotations命名空间中,几个现有的数据注释特性可为Code First所用。这些特性是:

KeyAttribute

KeyAttribute 用于指定一个属性或字段是实体主键的一部分,只可用于标量属性(scalar properties)。

StringLengthAttribute

StringLengthAttribute用于指定字符串的最大长度。因为在Code First 或实体框架中没有最小长度一说,该特性的MinimumLength属性会忽略不计。这个特性只可用于字符串类型。

在.NET 4.0中,负的MaximumValue是不允许的。但在下一个框架版本中,有人建议改变这个行为,将‘-1’ 视为“max”(最大值)。但这不是很理想,我们正考虑的另一个主意是使MaxStringLengthAttribute继承自StringLengthAttribute。MaxStringLengthAttribute不接受参数,但会采用最大可允值。

ConcurrencyCheckAttribute

ConcurrencyCheckAttribute用于指定一个属性或字段在EDM模型中拥有“fixed”(固定)的并发模式。固定的并发模式意味着这个属性是实体在做保存操作时所作的并发检查的一部分,只可用于标量属性。

RequiredAttribute

RequiredAttribute用于指定一个属性或字段不可是null值,可用于标量,复杂或导航属性:

  • 标量属性: 意味着该属性不能是null(即使CLR类型说它可以是null的)。
  • 复杂属性: 该特性在这里不允许使用,我们会抛出异常
  • 导航属性
    • 集合属性:该特性在这里不允许使用, 我们会抛出异常
    • 引用属性: 这意味着该外键属性不能是null,关系端头的基数性(cardinality)为“1”(而不是0..1)。

TimestampAttribute

TimestampAttribute用于指定一个byte[]属性或字段在模型中拥有“fixed”的并发模式,在存储模型中可当作时戳(timestamp)字段(在CLR中是不可null类型)。这个特性只可用于类型为byte[]的标量属性。一个实体只能有一个TimestampAttribute,这是大多数数据库平台所允许的。

DataMemberAttribute

DataMemberAttribute,虽然严格说不是数据注释集的一部分,但可用于帮助指定主键的序数(ordinal)。这在实体拥有复合主键,而且你想要对键在数据库中指定的顺序或在应用中使用的顺序有明确肯定时,是非常重要的。这个特性只可用于属于键的一部分的标量属性。序数不需要是连续的,它们只指定了元数据中键属性的相对顺序。

在理想情况下,我们想要能够作为KeyAttribute的一部分,通过一个 “Ordinal” 属性来指定键的顺序,但

Posted in 未分类 | Leave a comment

【翻译】Code First中的约定

【原文地址】Conventions for Code First
【原文发表日期】 1 Jun 2010 1:03 PM

 

最新的Code First预览版允许你使用C# 和 VB.Net类来描述模型。模型的基本形态是通过约定(convention)推断出来的,然后可以用流畅API(fluent API)来进一步改善你的模型。

最近我们在博客中提到 计划支持数据注释,将其作为另一种描述你的模型的方式。我们正在研究如何扩展和改进最初推断出模型形态的约定。本贴将描述我们计划包括的约定。

约定是设计来给模型提供一个起始点,然后数据注释或流畅API可以用来进一步描述模型,或者改变由约定推断出的东西。配置的优先权次序为,流畅API,然后是数据注释,然后是约定。

主键

在以前,Code First会将一个属性推断为主键,假如这个属性是叫‘Id’ 或者 ‘<类名>Id’的话。对这个约定的唯一变化是,一旦推出主键后,如果它们的类型是‘int’, ‘long’ 或r ‘short’,它们会在数据库中默认注册为identity字段。主键的检测是无关大小写的。

关系的倒转(Inverse)

在两个类型间定义关系时,通常会在这两个类型上同时包括导航(navigation)属性,譬如下面这个例子:

public class Product

{

    public int ProductId { get; set; }

    public string Name { get; set; }

    public Category Category { get; set; }

}

 

public class Category

{

    public int CategoryId { get; set; }

    public string Name { get; set; }

    public ICollection<Product> Products { get; set; }

}

 

 

在以前,Code First会在Product 和 Category之间创建2个单独的关系,但现在会推断出 Product.Category 和 Category.Products表示同一个关系的不同端。但这样的关系倒转的检测,只有在参与关系的两个类只定义了相对于对方的唯一一个导航属性(引用或集合)时才会发生。如果参与关系的一个类定义了2个或多个引用另一个类的导航属性的话,倒转关系的检测不会发生,象这样的关系需要使用 数据注释 或流畅 API手工配置。

外键

建立在以前的约定之上,通常也会在一个关系的依赖端包含一个外键属性,譬如下面这个例子中的BookReview.SubjectISBN:

 

 

public class BookReview

{

    public int Id { get; set; }

    public Book Subject { get; set; }

    public string SubjectISBN { get; set; }

}

 

public class Book

{

    [Key]

    public string ISBN { get; set; }

    public string Name { get; set; }

    public ICollection<BookReview> Reviews { get; set; }

}

 

Code First现在会推断出,任何属性,假如它的名称是 ‘<导航属性名><主键属性名>’ (即 SubjectISBN), ‘<主要类名><主键属性名>’ (即 BookISBN) 或 ‘<主键属性名>’ (即 ISBN), 而且与主键拥有同样的数据类型,那么就代表了某个关系的外键。如果多个属性符合这样的条件的话,那么其优先权的先后就按上面列出的次序。外键的检测也是无关大小写的。

当检测出一个外键属性时,Code First 也会根据外键的可null性(nullability),推断出关系的多重性(multiplicity)。如果关系是可null的,那么关系会被注册为可选的(optional),否则的话,关系就是必需的,同时还会启用级联删除(cascade delete)。通过约定检测出的多重性和级联删除行为可使用流畅API来改变。

类型发现

在以前,Code First只会包括声明在上下文继承类上的对象集的类型,或者通过流畅API手工注册的类型。给定下面这个例子,那么Product会包括在你的模型中,而Category则不会:

 

 

public class ProductContext : ObjectContext

{

    public ProductContext(EntityConnection connection)

        : base(connection)

    { }

 

    public ObjectSet<Product> Products

    {

        get { return base.CreateObjectSet<Product>(); }

    }

}

 

public class Product

{

    public int Id { get; set; }

    public string Name { get; set; }

    public Category Category { get; set; }

}

 

public class Category

{

    public int Id { get; set; }

    public string Name { get; set; }

    public ICollection<Product> Products { get; set; }

}

 

Code First现在会察觉到Product有一个引用Category的属性,会自动将Category包括在你的模型中。可达性(Reachability)是递归式的,所以,如果Category也引用了一个未注册的类型的话,该类型也会被包括在模型中。可达性也会追随引用,到定义在另一个程序集中的类型上。约定也许会包括不属于模型的类型,这些类型可以通过StoreIgnore数据注释或流畅API来去除,例如:

 

 

var builder = new ContextBuilder<ProductContext>();

builder.Ignore<Category>();

复杂类型(complex type)的发现

建立在可达性约定之上,如果Code First发现一个类定义中无法推断出主键,也没有通过数据注释或流畅API来注册主键,那这个类型就会自动注册为复杂类型。复杂类型的检测也会要求该类型没有引用实体类型的属性,也没有为另一个类型中的集合类型中所引用。给定下面的类定义,Code First 会推断出 Name 是一个复杂类型, 因为它没有主键:

 

 

public class Person

{

    public int PersonId { get; set; }

    public Name Name { get; set; }

}

 

public class Name

{

    public string Title { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

}

结语

Code First将提供一个扩展了的默认约定集来决定一个模型的形态。这些约定可以使用数据模型来重新定义,转而可以通过流畅API来否决。

我们希望听到你对这些约定以及对每个约定的相关规则的任何反馈。

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

Posted in 未分类 | Leave a comment

【翻译】实体框架特性CTP4全程示范:Code First

【译注】【时隔一年,Code First有了很大的变化, 期待着它的正式发布。】

【原文地址】EF Feature CTP4 Walkthrough: Code First
【原文发表日期】 14 Jul 2010 9:33 AM

简介

我们最近发布了实体框架特性社区技术预览版4(CTP4)。特性CTP4包含了我们考虑将来要加到核心框架中去的新特性的预览,希望得到社区的反馈。Feature CTP4是建立在随 .NET框架4.0 and Visual Studio 2010一起发布的实体框架4(EF4)的现有功能之上的.

这个贴子将提供对实体框架Code First(代码优先)特性的基本组件的示范,同时也包含在这个CTP4中的生产力改进方面的工作,则提供了一个流线型的Code First体验,通过提供一个建立在本贴中描述的构件块之上的外观(façade),将减小你所需编写的代码量。这个体验将在我们的CTP4生产力改进示范贴子中讨论。

如果你需要有关CTP4方面的帮助的话,请访问我们的实体框架预发布论坛

1. 安装EF CTP4

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

2. 创建应用

为了简单起见,我们将建造一个基本的console应用,使用Code First操作数据访问。

·        打开Open Visual Studio 2010

·        文件 -> 新 -> 项目…

·         从左边菜单中选择“Windows”,然后选择 “Console Application”

·         键入“EF.CodeFirst.Walkthrough” 作为其名称

·         点击“OK”

3. 创建模型

Code First说白了就是用类来描述我们的模型,因此我们一开始将在代码中构造出一个简单的模型。我们想要我们的模型是持久透明的( persistence ignorant),即,对实体框架没有任何的依赖性,因此我们将把我们的模型加到另一个项目中去。

·        给模型加一个新项目

o  文件 -> 添加 -> 新项目…

o  在左边菜单选择 “Windows”,然后选择“Class Library”

o  键入“EF.CodeFirst.Walkthrough.Model” 作为其名称

o  点击“OK”

·        在模型项目上右击,添加一个名为 “Book” 的类,其实现如下:

using System;

 

namespace EF.CodeFirst.Walkthrough.Model

{

    public class Book

    {

        public string ISBN { get; set; }

        public string Title { get; set; }

        public DateTime FirstPublished { get; set; }

        public bool IsFiction { get; set; }

 

        public virtual Publisher Publisher { get; set; }

        public virtual Author Author { get; set; }

    }

}

 

·        在模型项目上右击,添加一个名为 “Person” 的类,其实现如下:

 

namespace EF.CodeFirst.Walkthrough.Model
{

    public class Person

    {

        public int PersonId { get; set; }

        public string FirstName { get; set; }

        public string LastName { get; set; }

    }

}

 

·         在模型项目上右击,添加一个名为 “Author” 的类,其实现如下:

using System.Collections.Generic;

 

namespace EF.CodeFirst.Walkthrough.Model

{

    public class Author : Person

    {

        public int AuthorId { get; set; }

 

        public virtual ICollection<Book> Books { get; set; }

    }

}

 

·        在模型项目上右击,添加一个名为 “Publisher” 的类,其实现如下:

using System.Collections.Generic;

 

namespace EF.CodeFirst.Walkthrough.Model

{

    public class Publisher

    {

        public int PublisherId { get; set; }

        public string Name { get; set; }

 

        public virtual ICollection<Book> Books { get; set; }

    }

}

4. 模型构造器和流畅 API

如果你用过Code First的以前CTP版本的话,那
么从这里开始,会有点不一样了。就象ModelBuilder这名称蕴含的,ModelBuilder 是用来配置模型的,然后它会产生一个不可变的DbModel,该DbModel会被用来构建ObjectContext 或 DbContext实例。

一旦创建 DbModel 之后,该对象应该缓存起来,在你整个应用中重用,以避免多次创建模型会造成的性能问题。如果你采用在生产力改进示范一贴中描述的Code First方法的话,那么DbContext会为你负责这个缓存。

·         首先我们需要在我们的主应用中引用我们的模型

o   在console应用项目上右击 -> 添加引用…

o   选择“项目”页

o   从列表中选择模型项目

o   点击 “OK”

·         我们还需要引用CTP4程序集已经核心的实体框架程序集

o  在console应用项目上右击,-> 添加引用…

o   选择 “.NET” 页

o   从列表中选择S“Microsoft.Data.Entity.Ctp”

o   点击“OK”

o   重复上述步骤,添加对“System.Data.Entity”的引用

·         我们还需要在Program.cs的顶部加几个using语句

using System.Data.Entity.ModelConfiguration;

using System.Data.SqlClient;

using EF.CodeFirst.Walkthrough.Model;

 

·         接着在Program.cs的Main方法中加入下面的代码来配置你的模型

static void Main(string[] args)

{

    var builder = new ModelBuilder();

           

    builder.Entity<Book>().HasKey(b => b.ISBN);

    builder.Entity<Book>().Property(b => b.Title).IsRequired();

    builder.Entity<Book>().HasRequired(b => b.Author).WithMany(a => a.Books);

 

    builder.Entity<Person>();

 

    builder.Entity<Publisher>().Property(p => p.Name).IsRequired().HasMaxLength(50);

}

 

注:在最后一节中,我们将讨论创建一个继承的上下文,呈示你的模型中的类型集。一旦你有一个继承的上下文后,你可以使用ModelBuilder.DiscoverEntitiesFromContext(Type contextType) 方法将呈示的类型集的类型自动为你注册,这避免了对不需要任何额外配置的类型的手工注册,譬如上面代码中的Person类。

为什么引入DbModel?

在先前的CTP中,ContextBuilder 基本上做了ModelBuilder 和 DbModel两者的活。对最后的模型引进一个单独的表示(representation),确实在过程中添加了额外的步骤。但在将来,当我们寻找超越ModelBuilder的其他构造模型的方法时,DbModel对EF总的来说将变得更加的重要。DbModel允许我们对模型有一个单一的表示,可以用来构造ObjectContext和DbContext实例。

约定

你会注意到,我没有指定与模型相关的许多东西,例如,Person.PersonId是个主键或者Book.Publisher于Publisher.Books是同一个关系的倒转导航属性。这是因为Code First现在包含了广泛的约定,会为你处理常见的配置任务。这些约定在约定设计博客贴子中有详述。CTP4中包含的约

Posted in 未分类 | Leave a comment

【翻译】Code Only增强

【原文地址】Code Only Enhancements
【原文发表日期】 03 August 09 11:11

自从第一个预览版发布之后,我们一直在奋力增强Code Only功能。

在下一个版本中,你将能够指定

  1. 导航属性的倒转(Inverse)关系
  2. 属性的细节(Facets),象like Nullability(可null性), MaxLength(最大长度), Precision(精度)等等
  3. 属性与字段间的映射
  4. 类型与数据表间的映射
  5. 继承策略
  6. 配置的封装

本贴的余下部分将依次对这些特性进行详述。

注册导航属性的倒转关系:

你现在可以注册倒转关系,即一个导航属性到另一个导航属性的倒转(inverse)关系,象这样:

builder.RegisterInverse(
       (Customer c) => c.Orders,
       (Order o) => o.Customer)
);

这代码表示Customer.OrdersOrder.Customer关系的另一头。把order1 加到customer1.Orders集合中去,与把order1.Customer设置成customer1具有同等的效果。

指定属性细节(Facets):

你还可以指定属性的细节,即象Nullability(可null性), MaxLength(最大长度), Precision(精度)等等这样的东西,象这样:

var customerConfig = new EntityConfiguration<Customer>();
// 我们可以推断出ID是主键
// 但无法推断出它在插入时是在数据库生成的
customerConfig.ForProperty(c => c.ID)
              .Identity();
customerConfig.ForProperty(c => c.Name)
              .MaxLength(100)
              .NonUnicode();
customerConfig.ForProperty(c => c.Website)
              .MaxLength(200)
              .Nullable()

builder.Configure(customerConfig);

这把Customer类型配置成:

  • ID 属性是Identity字段,即在我们插入数据到数据库时,其值是由数据库计算出来的。
  • Name属性,其MaxLength100个字符,是NonUnicode(非Unicode),即在SQL Server中是VARCHAR而不是NVARCHAR
  • Website属性,其MaxLength200个字符串,是Nullable

这些细节是针对概念模型Conceptual Model,即CSDL)的,从那里,也传到数据库(即SSDL)。

封装细节配置

你可以创建一个EntityConfiguration<T>的继承类来封装所有这些配置。

例如:

public class CustomerConfig: EntityConfiguration<Customer>
{
    public CustomerConfig(){
       ForProperty(c => c.ID)
                 .Identity();
       ForProperty(c => c.Name)
                 .MaxLength(100)
                 .NonUnicode();
       ForProperty(c => c.Website)
                 .MaxLenght(200)
                 .Nullable();
    }
}

我们建议你创建象这样的类,而不是配置EntityConfiguration<>,因为封装的好处。

指定数据表名

在你使用Configure<T>(..) 时,实体框架为你推断出默认的映射,继承策略(TPH)和表名。

但如果你要指定表名,你可以这么做:

var customerConfig = new EntityConfiguration<Customer>();
// 象上面那样配置细节

// 用一个特定的表名注册配置
builder.Tables[“dbo.Custs”] = customerConfig;

指定映射:

如果你需要对映射更多的控制(例如,要映射到一个现有的数据库或者使用企业的命名规则),那么你可以象这样指定映射:

EntityMap<Customer> customerMap = 
    Map.OfType<Customer>(
        c => new {
            cid = c.ID,
            c.Name,
            csite = c.Website
        }
    );

映射解释

这个映射表明,ID是映射到‘cid’字段,Name属性是保存到‘Name’字段,而Website是映射到‘csite’ 字段的。

没有被引用的属性是不会被持久化的,就象使用实体框架默认的代码生成时生成的部分类上的属性一样。

LINQ内涵句法(Comprehension Syntax)

你甚至也可以使用LINQ内涵句法来指定同样的事情:

EntityMap<Customer> customerMap =
    from c in Map.OfType<Customer>()       
    select new {
            cid = c.ID,
            c.Name,
            csite = c.Website
        };

用映射指定细节(Facets)

在配置完映射后,你还可以

Posted in 未分类 | 1 Comment

【翻译】Entity Framework 特性CTP全程示范: Code Only

【原文地址】Feature CTP Walkthrough: Code Only for the Entity Framework
【原文发表日期】 22 June 09 02:54 PM

【译注】.NET 3.5 SP1中的Entity Framework着重于“数据库优先(Database First)”,即从数据库中逆工程生成实体数据模型。而Visual Studio 2010/.NET 4.0中的Entity Framework 4.0引进了“模型优先(Model First)”,即允许你先生成实体数据模型,然后生成映射到该模型的数据库。而Entity Framework Feature CTP1版则更进一步,推出了“只用代码(Code Only)”的预览版,不再需要EDMX文件。 相关的想法请参考这个贴子。你需要下列软件才能运行这个示范:
  1. Visual Studio 2010 Beta 1
  2. Entity Framework Feature CTP1 (同时参考Entity Framework Feature CTP 1一文,如果你对该CTP中其他特性,特别是POCO模板等,感兴趣的话)
  3. 本地的SQL Server 2008 Express或者SQL Server

Code Only全程示范

1) 创建一个名叫“"CodeOnlyWalkthru"”的控制台应用:

blog1

2) 往"CodeOnlyWalkThru"解决方案中加一个新的项目:

blog1

3) 选择“类库”,将其命名为"Entities":

blog1

 

4) 在你的Entities项目中加一个"Category"类:

右击Entities项目,加一个名叫“"Category"”的类,然后将下面的代码粘贴入该类:

public class Category{    private List<Product> _products;    public int ID { get; set; }    public string Name { get; set; }    public virtual List<Product> Products {         get         {            if (_products == null)                _products = new List<Product>();            return _products;        }         set         {             _products = value;         }     }}

 

5) 生成"Product"类:

在"Category.cs"类中,右击还不存在的Product类名,从上下文菜单上选择"Generate > Class(生成类)":

blog1

在新的Product类中,粘贴入下面这些代码:

public class Product    {        public int ID { get; set; }        public string Name { get; set; }    }

至此,你的项目应该象这样:

blog1

我们把这些实体类放进了一个单独的项目,这样它们会被编译进一个对Entity Framework无依赖的程序集中。因此,Entities程序集对持久性是透明的,这对一些开发人员来说,是非常重要的。对持久性有意识的代码存在于另外的程序集中,引用对持久性是透明的程序集。

6) 在"CodeOnlyWalkThru"项目中,添加对Entities项目的引用:

blog1

7) 在"CodeOnlyWalkThru"项目中,添加对"System.Data.Entity" 和 "Microsoft.Data.Entity.Ctp"的引用:

blog1

image

注: 从长远看来,我们计划将“Code Only”的功能并入核心的Entity Framework程序集。之后,你就只需第一个引用。

8) 在"CodeOnlyWalkThru" 项目中,加一个新的叫"ProductDBContext"的类:

 

blog1

9) 将下面的代码加到"ProductDBContext"类中:

public class ProductDBContext: ObjectContext{    public ProductDBContext(EntityConnection connection)        : base(connection, "ProductDBContext")    {        ContextOptions.DeferredLoadingEnabled = true;    }    public IObjectSet<Category> Categories    {        get { return CreateObjectSet<Category>(); }    }    public IObjectSet<Product> Products    {        get { return CreateObjectSet<Product>(); }    }}

因为这个类扩充了ObjectContext,它代表你的模型的形状,起通往你的数据库的入口的作用。注意,我们为上面两个实体类型创建了对应的ObjectSet集。 我们还加了一个构造器,它接受一个EntityConnection参数。ContextBuilder会制造一个EntityConnection (EntityConnection封装了实际的数据库连接和Entity Framework元数据(metadata)),在我们要求它生成一个ProductDBContext新实例时,会将该参数传给这个构造器。在构造器中,我们还配置Entity Framework启用DeferredLoading(即LazyLoading-懒式装载)。

10) 然后将下面的代码粘贴进"Program" 类:

SqlConnection connection = new SqlConnection(@"Data Source=.\SQLEXPRESS;Initial Catalog=ProductDB;Integrated Security=SSPI;");using (ProductDBContext context = ContextBuilder.Create<ProductDBContext>(connection)){    if (!context.DatabaseExists())        context.CreateDatabase();    Category food = new Category { CID = 1, Name = "Food" };    Product bovril = new Product { ID = 1, Name = "Bovril" };    Product marmite = new Product { ID = 2, Name = "Marmite" };    Product vegemite = new Product { ID = 3, Name = "Vegemite" };    food.Products.Add(bovril);    food.Products.Add(marmite);    food.Products.Add(vegemite);    context.Categories.AddObject(food);    context.SaveChanges();    // Query the database    food = context.Categories.Single();    foreach (var product in food.Products)        Console.WriteLine(product.Name);}

 

上面的代码按约定创建了一个上下文,连向本地安装的SQLExpress中的ProductDB数据库。如果ProductDB数据库还不存在,就创建该数据库。

最后,在上下文开始运行后,你就象平常一样使用它,在这个例子中,我们创建了一个Category实例,三个Product实例,两者互相关联,然后我们从数据库中获取Category对象,然后使用LazyLoading(即DeferredLoading),对它的产品进行循环。

就是这么简单!

11) 配置ContextBuilder

迄今为止,我们是按约定来做一切的,因为Code Only可以按约定推断出任何东西。但如果我们将Category类改成象这样的话:

public class Category{    private List<Product> _products;    public int CID { get; set; }    public string Name { get; set; }    public virtual List<Product> Products {         get         {            if (_products == null)                _products = new List<Product>();            return _products;        }         set         {             _products = value;         }     }}

CodeOnly不知道CID是键(key)【见注】,这意味着你需要创建一个ContextBuilder实例,用它来将CID注册成键,象这样:

var builder = new ContextBuilder<ProductDBContext>();builder.RegisterKey((Category c) => c.CID);

如果需要的话,你可以在同个Builder上注册许多个键,你配置完你的Builder之后,你可以使用它的Create方法来创建一个ProductDBContext实例:

using (ProductDBContext context = builder.Create(connection))

 

其它的代码没变。

一般来说,在需要创建ObjectContext时,你应该重用你配置的ContextBuilder,而不是每次都重新配置,这会对性能有帮助。最好的做法大概是通过一个静态变量来做。

【注】按约定,任何叫ID, Id, ClassNameID 或 ClassNameId的属性会被假定为实体键。如果找不到这些名称的属性的话,那么你需要调用RegisterKey(),来告诉Entity Framework哪个属性是键。

Beta1版的CTP中Code Only功能的已知限制:

这个版本是一个非常早期的Code Only预览版,因此它有许多已知的限制,我们计划在以后的版本中解决它们,这些限制包括:

  • 不支持复杂类型(ComplexType)
  • 不支持可配置的映射
  • 只支持Table per Hierarchy继承策略
  • 不支持Facet的指定,譬如,你无法指定数据库中一个字符串字段的最大长度,而只用默认值
  • 没有提供器模型(Provider Model),所以CodeOnly 目前只支持SqlServer
  • CodeOnly没有提供方式让你指定哪个属性参与了同个关系,但只是对方的倒转(inverse)。譬如,如果在上面的例子中,你往Product类中加了一个Category属性, 结果在Product和Category间,会有2个关系,因此在Products表中会生成2个外键,因为Entity Framework无法推断Product.Category和Category.Products只是对方的倒转。
  • 不支持多对多关系

- Alex James

Entity Framework的Program Manager

Posted in 未分类 | Leave a comment

【翻译】在Entity Framework 4.0中使用 Repository 和 Unit of Work 模式

【原文地址】Using Repository and Unit of Work patterns with Entity Framework 4.0
【原文发表日期】 16 June 09 04:08 PM

如果你一直在关注这个博客的话,你知道我最近在讨论我们加到Entity Framework 4.0中的POCO功能的方方面面,新加的POCO支持促成了在Entity Framework中实现透明性持久的新方式,而该方式在Entity Framework 3.5中是无法实现的。

如果你错过了我的POCO系列,为方便起见,我在下面列了出来,快速浏览一下也许是个不错的主意。

POCO in Entity Framework : Part 1 – The Experience(【翻译】实体框架中的POCO支持 – 第一部分 – 体验

POCO in Entity Framework : Part 2 – Complex Types, Deferred Loading and Explicit Loading (【翻译】实体框架中的POCO支持 – 第二部分 – 复杂类型,延迟装载和显式装载

POCO in Entity Framework : Part 3 – Change Tracking with POCO (【翻译】实体框架中的POCO支持 – 第三部分 – POCO的变动跟踪

在这个贴子里,我想要看一下如何将我们的例子做进一步扩充,使用一些诸如RepositoryUnit Of Work这样常见的模式,这样我们可以在我们的例子中实现一些特定于持久性的关注。

将我们基于Northwind的例子做一下扩充,譬如说,我有兴趣支持下列与Customer(客户)实体相关的操作:

  • 通过ID来查询客户
  • 通过名字来查询客户
  • 往数据库中加一个新客户

我还想能够基于ID来查询产品。

最后,给定一个客户,我想要能够往数据库中加一个Order(订单)。

在进入细节之前,我想先说明两件事情:

  • 处理这个问题,“正确”的方式不止一个。在过程中,我会做许多简化的假设,其目的是展示一幅非常高层次的草图,示范如何实现这2个模式来解决手上的问题。
  • 通常来说,在使用TDD时,我会从测试开始,使用我的测试来逐步展开我的设计。因为在这个例子中我没有按TDD做,请耐心点,如果你看到我在做象预先定义接口这样的事情,而不是让测试和共用的东西来主宰接口的必要性,等等。

实现 Repository 模式

让我们先开始实现针对Customer实体的操作,看一下Customer的repository的样子:

public interface ICustomerRepository{            Customer GetCustomerById(string id);    IEnumerable<Customer> FindByName(string name);    void AddCustomer(Customer customer);}

这个repository接口看上去满足有关Customer的所有需求:

  • GetCustomerById 应该允许我通过主键来得单个客户实体
  • FindByName 应该允许我查询客户
  • AddCustomer 应该允许我往数据库中加一个客户

这暂时听上去不错,象这样,为你的repository定义一个接口是个好主意,特别是你对使用mocks(模拟对象) 或 fakes (假冒对象)来编写测试感兴趣的话,接口允许你完全不用考虑数据库,促成更好的单元测试。在将来会有更多的博客贴子讨论可测试性,mocks 和 fakes,等等。

你也许可以进一步扩展这个接口定义,定义一个共用的IRepository,来处理多个repository类型中共有的关注。如果对你有用的话,这是件值得做的事。在这个特定的例子中,我没看到其必要性,所以我就免了。但在你添加更多的repository以及重构时,这完全有可能会变得非常重要。

让我们拿这个repository来看一下如何利用Entity Framework来做一个实现,允许数据访问:

首先,我需要一个可以用来查询数据的ObjectContext。你也许想作为repository构造器的一部分生成一个ObjectContext,但最好还是把那个关注从repository中去除,在别的地方处理为好。

这是我的构造器:

public CustomerRepository(NorthwindContext context){    if (context == null)        throw new ArgumentNullException("context");

   _context = context;} 

在上面的代码片段中,NorthwindContext是个我自己的ObjectContext类型。

让我们来提供 ICustomerRepository 接口所需的方法的实现。

GetCustomerById 非常容易实现,多亏了LINQ。使用标准的LINQ运算符,我们可以象这样实现 GetCustomerById

public Customer GetCustomerById(string id){    return _context.Customers.Where(c => c.CustomerID == id).Single();}

类似地,FindByName 可以象这样。 再一次,LINQ支持使得其实现非常容易:

public IEnumerable<Customer> FindByName(string name){    return _context.Customers.Where( c => c.ContactName.StartsWith(name)                                   ).ToList();                        }

注意,我选择将结果呈示为IEnumerable<T>,你也许会选择将这呈示为IQueryable<T>。这么做有其含意,在这个情形下,我对呈示建立在repository返回结果之上的基于IQueryable的另外的查询组合,不是很感兴趣。

最后,让我们来看一下可以如何实现AddCustomer

public void AddCustomer(Customer customer){    _context.Customers.AddObject(customer);}

你也许想把保存功能作为AddCustomer方法的一部分来实现。那么做也许对这个简单的例子没问题,但一般来说,这不是个好主意,这正是Unit of Work出场的地方,过一会儿,我们会看到如何使用这个模式来允许我们实现和协调Save行为。

这里是使用Entity Framework来处理持久性的CustomerRepository的完整实现:

public class CustomerRepository : ICustomerRepository{    private NorthwindContext _context;

    public CustomerRepository(NorthwindContext context)    {        if (context == null)            throw new ArgumentNullException("context");

         _context = context;    }

    public Customer GetCustomerById(string id)    {        return _context.Customers.Where(c => c.CustomerID == id).Single();    }

    public IEnumerable<Customer> FindByName(string name)    {        return _context.Customers.Where(c => c.ContactName.StartsWith(name))            .AsEnumerable<Customer>();                            }

    public void AddCustomer(Customercustomer)    {        _context.Customers.AddObject(customer);    }}

这里是我们可以如何在客户端代码中使用该repository:

CustomerRepository repository = new CustomerRepository(context);Customer c = new Customer( ... );repository.AddCustomer(c);context.SaveChanges();

在处理与ProductOrder相关的需求时,我可以定义下列接口(建造类似CustomerRepository一样的实现)。为简便起见,我把其细节省略了。

public interface IProductRepository{    Product GetProductById(int id);}

public interface IOrderRepository{    void AddOrder(Order order);}

使用ObjectContext实现 Unit of Work (工作单元) 模式

你也许已经注意到了,尽管我们没有实现任何特定的模式来明确地地允许我们将相关操作聚合进一个工作单元(unit of work),但我们已经通过NorthwindContext(我们的ObjectContext类)免费获取了Unit of Work功能。

其想法是,我可以使用Unit of Work将一套相关的操作聚合在一起,Unit of Work记录我感兴趣的变动,直到我准备将它们保存到数据库为止。最终,当我准备保存时,我可以那么做。

我可以象这样定义一个“Unit of Work”接口:

public interface IUnitOfWork{            void Save();}

注意,在Unit of Work中,你也许还会选择实现Undo / Rollback功能。在使用Entity Framework时,推荐的undo做法是,丢弃你的上下文以及你想undo的变动。

我已经提到,我们的ObjectContext类型(NorthwindContext)在极大程度上支持Unit of Work模式。 基于我刚定义的契约,为了使得事情更为明确一点,我可以将NorthwindContext类改成实现IUnitOfWork接口:

public class NorthwindContext : ObjectContext, IUnitOfWork{       public void Save()    {        SaveChanges();    }

. . . 

在做了这个变动之后,我需要对我们的repository实现做个小的调整:

public class CustomerRepository : ICustomerRepository{    private NorthwindContext _context;

    public CustomerRepository(IUnitOfWork unitOfWork)    {        if (unitOfWork == null)            throw new ArgumentNullException("unitOfWork");

        _context = unitOfWork as NorthwindContext;    }

    public Customer GetCustomerById(string id)    {        return _context.Customers.Where(c => c.CustomerID == id).Single();    }

    public IEnumerable<Customer> FindByName(string name)    {        return _context.Customers.Where(c => c.ContactName.StartsWith(name))            .AsEnumerable<Customer>();    }

    public void AddCustomer(Customer customer)    {        _context.Customers.AddObject(customer);    }}

 

就这么简单!现在,我们有了一个对IUnitOfWork友好的repository,你甚至可以使用基于IUnitOfWork的上下文来协调跨越多个repository的工作。下面是一个将订单加到数据库的例子,需要多个repository的工作来查询数据,最终将记录保存回数据库中去:

IUnitOfWork unitOfWork = new NorthwindContext();

CustomerRepository customerRepository = new CustomerRepository(unitOfWork);Customer customer = customerRepository.GetCustomerById("ALFKI");

ProductRepository productRepository = new ProductRepository(unitOfWork);Product product = productRepository.GetById(1);

OrderRepository orderRepository = new OrderRepository(unitOfWork);

Order order = new Order(customer); order.AddNewOrderDetail(product, 1);

orderRepository.AddOrder(order);

unitOfWork.Save();

非常有趣地看到,为了在Entity Framework之上使用Repository 和 Unit of Work模式来访问数据,我们要编写的代码是如此地少。Entity Framework的LINQ支持以及原装的Unit of Work功能使得在Entity Framework之上建立repository简单之极。

另一个要注意的事情是,我在这个贴子里讨论的很多东西,都不是与Entity Framework 4.0特别有关,所有这些一般原理在Entity Framework 3.5下也适用。但要能够象上面展示的那样,在POCO支持的基础之上,使用RepositoryUnit of Work,确实显示了Entity Framework 4.0中的威力。我希望你会发现这非常有用。

最后,我要重申一下,有很多方式可以处理这个题目,在应用Entity Framework, Repository 和 Unit of Work时,还有许多变种方案可以符合你的需求。你也许会选择实现公共的IRepository接口,你也许还会选择实现Repository<TEntity>基类,来给予你一些跨越所有repository的常用repository功能。

尝试一些方法,看哪个最适用于你。我在这里讨论的只是一般的指南而已,但我希望这足够让你起步。更重要的是,我希望这示范了如何可以使用我们在Entity Framework 4.0中引进的POCO支持,来帮助你建造Entity Framework友好的领域模型,而不必违背透明持久性方面的基本原则。

包含我上面一些示范代码的项目附在本贴之后。在我们的将来贴子中讨论单元测试,TDD和可测试性时,我们还将对这个题目做更多的讨论,敬请期待。

Faisal Mohamood

Entity Framework的Program Manager

Posted in 未分类 | 1 Comment

【翻译】实体框架中的POCO支持 – 第三部分 – POCO的变动跟踪

【原文地址】POCO in the Entity Framework : Part 3 – Change Tracking with POCO
【原文发表日期】 10 June 09 08:43 AM

上一个POCO贴子里,我提到了跟踪POCO变动的两种可行性:基于快照的变动跟踪(Snapshot based Change Tracking)使用代理的基于通知的变动跟踪(Notification based Change Tracking with Proxies)。 在这个贴子里,我将对这两个选项做进一步的讨论,讨论它们的优缺点,以及使用它们的含意(implications)。我们还将在《实体框架设计博客》上贴出针对至今为止受到的关于Entity Framework 4.0中POCO支持的一些反馈的想法。

基于快照的变动跟踪(不用代理的纯POCO类)

就象我在这个系列的第二部分中提到的,基于快照(Snapshot)的变动跟踪,就是纯粹的POCO实体的做法,不使用代理来处理变动跟踪。这是个简明的变动跟踪方案,依赖于实体框架所维护的之前和之后值的完整快照,在SaveChanges中对这些值进行比较,决定到底哪些值与初始值有所不同。在这个模型中,除非你用了懒式装载,你的实体的运行时类型跟你定义的POCO实体类型是一模一样的。

这个做法没什么问题,如果你在乎你的实体类型的运行时纯粹性(不使用代理),完全可以依赖这个方案,走纯粹的POCO实体之路(不依赖于代理类型实现额外的功能)。

基于快照的变动跟踪唯一潜在的问题是,有几件事情你需要注意,因为在你的对象变动时没有向实体框架做直接变动通知,实体框架的对象状态管理器将与你的对象图不再同步。

让我们来看一个展示这个问题的例子:

Customer customer = (from c in context.Customers                     where c.CustomerID == "ALFKI"                     select c).Single();

ObjectStateEntry ose = context.ObjectStateManager.GetObjectStateEntry(customer);

Console.WriteLine("Customer object state: {0}", ose.State); // Unchanged

customer.Country = "UK";

Console.WriteLine("Customer object state: {0}", ose.State); // Still Unchanged

在这个例子中,Customer是个纯POCO类型,跟基于EntityObjectIPOCO的实体不一样,对该实体做变动并不自动与状态管理器保持同步,因为在你的纯POCO实体与实体框架间没有自动的通知机制。所以,在查询状态管理器时,它会认为你的客户对象的状态是Unchanged(未被改动),尽管我们显式地对该实体的一个属性做了变动。

如果你调用SaveChanges而不选择acceptChangesDuringSave(在保存后接受变动之选项)的话,你会看到在保存后,其状态变成了Modified(改动过了)。这是因为在保存时,基于快照的变动跟踪机制开始起作用,检测到了变动。当然,默认的SaveChanges调用会将状态变回到 Unchanged ,因为默认的Save行为是在保存后接受变动。

在后面一点,我们将进一步讨论在你需要让你的对象,对象图以及状态管理器间保持同步时该怎么做,但让我们先来看一下可为你所用的POCO的另一类变动跟踪机制。

使用代理的基于通知的变动跟踪

如果你在乎在你对实体值,关系和对象图做变动时的非常高效的和即时性的变动跟踪的话,这是个另样的方案,基于代理的变动跟踪。如果你把某个特定实体类型上的所有映射属性都声明为virtual的话,你就可以利用基于代理的变动跟踪了。

使用代理来跟踪变动的实体总是与实体框架的对象状态管理器保持同步,因为代理会在实体的值和关系变动时通知实体框架。总的来说,这使得变动跟踪更为有效,因为对象状态管理器可以略去比较属性的原始值和当前值这一步,如果它知道属性没有变动的话。

实际上,你从代理上得到的变动跟踪行为跟从基于EntityObject的非POCO实体或IPOCO实体上得到的变动跟踪行为是完全一样的。

让我们用同一个例子来看一下:

Customer customer = (from c in context.Customers                     where c.CustomerID == "ALFKI"                     select c).Single();

ObjectStateEntry ose = context.ObjectStateManager.GetObjectStateEntry(customer);

Console.WriteLine("Customer object state: {0}", ose.State); // Unchanged

customer.Country = "UK";

Console.WriteLine("Customer object state: {0}", ose.State); // Modified

这个例子是不言自明的,在你对实体做变动时,对象状态管理器被通知到你的变动了。在调用SaveChanges时,不会导致另外的花销(overhead)。

但是,基于代理的变动跟踪也意味着你的实体的运行时类型跟你定义的类型不完全一样,而是你的类型的子类。这在许多场景下(譬如序列化)也许不太合适,你需要选择在你的应用和领域的需求和约束下最合适的方法。

不使用代理,与状态管理器保持同步

两种方法都有其优缺点,但不是基于代理的纯POCO方案,其之简明和优雅也意味着它会成为你们中许多人的默认选择,也就毫不奇怪了。所以让我们来看一下在这种模式下,如何使状态管理器与你的对象图保持同步。

ObjectContext.DetectChanges()

在你使用基于快照的纯POCO实体时,有一个至关重要的方法:ObjectContext.DetectChanges()

这个API在你无论何时改变对象图时都应该显式调用,它会告知状态管理器它需要与的你对象图做同步。

ObjectContext.SaveChanges在默认情形下会隐式调用DetectChanges,所以,如果你所做的就是对你的对象们做一系列的变动,然后立刻调用Save的话,你不必显式调用DetectChanges。但是,要记住的是,取决于你的对象图的大小,DetectChanges也许会花销很大(而且,取决于你在做什么,DetectChanges也许是多余的),所以,有可能你可以略去SaveChanges中隐式调用的DetectChanges,这一点,在我们讨论Entity Framework 4.0中新引进的SaveChanges的重载方法时会做更多的讨论。

让我们来看一下DetectChanges如何影响我们前面看过的例子:

Customer customer = (from c in context.Customers                     where c.CustomerID == "ALFKI"                     select c).Single();

ObjectStateEntry ose = context.ObjectStateManager.GetObjectStateEntry(customer);

Console.WriteLine("Customer object state: {0}", ose.State); // Unchanged

customer.Country = "UK";Console.WriteLine("Customer object state: {0}", ose.State); // Still Unchanged

context.DetectChanges();Console.WriteLine("Customer object state: {0}", ose.State); // Modified

显式调用DetectChanges 会导致状态管理器与你的对象的状态保持一致。

因为DetectChanges的潜在花销,依赖于状态管理器与对象图保持一致的ObjectContext上的其他一些APIs是不显式调用它的。因此,无论什么时候你在context上做依赖于状态的操作时,你需要调用DetectChanges

Object Servces(对象服务)中的下列API的行为取决于ObjectStateManager的当前状态,因此,如果它们操作的部分对象图的情况与对象图的实际状态不同步的话,会受影响:

ObjectContext API:

  • AddObject
  • Attach
  • AttachTo
  • DeleteObject
  • Detach
  • GetObjectByKey
  • TryGetObjectByKey
  • ApplyCurrentValues
  • ApplyPropertyChanges
  • ApplyOriginalValues
  • Refresh
  • ExecuteQuery

ObjectStateManager API:

  • ChangeObjectState
  • ChangeRelationshipState
  • GetObjectStateEntry
  • TryGetObjectStateEntry
  • GetObjectStateEntries

ObjectStateEntry API:

  • Any

EntityCollection/EntityReference API:

  • Any

SaveChanges的新重载方法

在Entity Framework 3.5中, 在调用ObjectContext.SaveChanges()时,有两种可能:

  • SaveChanges() – 无参数选项,允许你保存变动,同时隐式接受变动。
  • SaveChanges(bool acceptChangesDuringSave) – 这个选项允许你跳出默认的“accept changes during save”(保存时同时接受变动)行为,你需要在保存后显式地使用AcceptAllChanges来接受变动。

在加了DetectChanges行为后,再加另外的重载方法很明显会搞乱API。为了解决这个问题,引进了SaveOptions这个列举标记:

[Flags]public enum SaveOptions{    None = 0,    DetectChangesBeforeSave = 1,    AcceptChangesAfterSave = 2,}

SaveChanges现在包括了象这样一个重载:

public virtual int SaveChanges(SaveOptions options);

SaveOptions将允许我们(Entity Framework开发团队)以可控制得多的方式来扩展API,假如我们需要在将来添加另外的选项的话。

但这个重载的有趣性还有另外一个原因:注意,这个SaveChanges重载是virtual。在Entity Framework 4.0中,你可以用你自己的定制行为,作为你选择编写的ObjectContext的继承类的一部分,来覆盖SaveChanges行为。无论你是否使用Entity Framework中的POCO支持,这将允许你以一种Entity Framework 3.5中不可能的方式来做定制。

至此,我想要重点阐述的Entity Framework 4.0中有关POCO的事情几乎都说完了,请接着期待下一篇在Entity Framework中使用模式的贴子。

Faisal Mohamood

Entity Framework开发团队的Program Manager

Posted in 未分类 | Leave a comment

【翻译】实体框架中的POCO支持 – 第二部分 – 复杂类型,延迟装载和显式装载

【原文地址】POCO in the Entity Framework : Part 2 – Complex Types, Deferred Loading and Explicit Loading
【原文发表日期】 28 May 09 09:03 AM

在上星期的贴子《POCO Experience in Entity Framework》 (实体框架中的POCO体验)中,我讨论了Entity Framework 4.0中POCO支持的基本。在这个贴子里,我将讨论与POCO相关的另外几个方面。

复杂类型(Complex Types)

POCO中的复杂类型支持跟常规的基于EntityObject的实体中的复杂类型支持一样。你要做的就是将它们声明为POCO类,然后在你的POCO实体中使用和声明基于它们的属性。

作为例子,这里是一个InventoryDetail复杂类型,代表我的Product实体的一个部分:

public class InventoryDetail{    public Int16 UnitsInStock { get; set; }    public Int16 UnitsOnOrder { get; set; }    public Int16 ReorderLevel { get; set; }} 

把我的Product类修改成包含一个这个类型的属性,用来组合几个有关库存细节的字段:

public class Product{    public int ProductID { get; set; }    public string ProductName { get; set; }    public int SupplierID { get; set; }    public string QuantityPerUnit { get; set; }    public decimal UnitPrice { get; set; }    public InventoryDetail InventoryDetail { get; set; }    public bool Discontinued { get; set; }    public Category Category { get; set; }}

然后你可以做你以前对复杂类型所能做的一切,这是一个查询的例子:

var outOfStockProducts = from c in context.Products                         where c.InventoryDetail.UnitsInStock == 0                         select c;

 

你可以看到,POCO中的复杂类型支持用起来非常直截了当。但在POCO中使用复杂类型支持时,你需要记住几件事情:

  1. 你必须将复杂类型定义为类(class),结构体(struct)是不支持的。
  2. 在你的复杂类型类中,你不能使用继承。

既然在讨论复杂类型,我想我要提一下另外一件事,你知道Visual Studio 2010中的实体框架设计器支持复杂类型的声明么?

在Visual Studio 2008中,你只能手工将复杂类型的声明加到CSDL中去,但随着Visual Studio 2010中的设计器中对复杂类型支持的推出,这一切都成了历史。

image

更酷的是,因为Visual Studio 2010支持多定向(Multi-Targeting),你可以在开发针对.NET Framework 3.5,使用了Entity Framework 3.5的应用中也使用这个功能!

延迟/懒式装载

在我2个星期前发表的《延迟装载初览》一文中,我提到了实体框架现在支持延迟装载了。默认的、代码生成的基于EntityObject的实体类型将提供延迟装载,自然毫不奇怪。如果你想知道POCO对象中是否也支持延迟装载,那么我想,你会很高兴地知道,你在POCO中也能得到延迟装载支持。

为在POCO实体中使用延迟装载支持,你需要做2件事情:

  1. 将你想要懒式装载的属性声明为virtual,这些属性可以是任何实现了ICollection<T> 的集合类,或者它们可以是代表了1/0..1 关系的引用。

例如,这里是更新过的Category实体类的部分代码,我将其改成支持延迟装载了:

public class Category{    public int CategoryID { get; set; }    public string CategoryName { get; set; }    public string Description { get; set; }    public byte[] Picture { get; set; }    public virtual List<Product> Products { get; set; }    ...

     2.   在上下文中启用延迟装载选项:

context.ContextOptions.DeferredLoadingEnabled = true;

 

这就行了,你现在就将得到POCO类型的自动延迟装载,而不用做任何其他什么事情。

那么这玩意到底是怎么工作的,底层是怎么进行的?

这玩意能工作的原因是因为,在我将集合类型的属性标记为virtual后,这允许实体框架在运行时为我的POCO类型提供一个代理(proxy) 实例,正是这个代理实现了自动的延迟装载。该代理实例是基于一个继承自我的POCO实体类的类型,所以你提供的所有功能都被保留下来了。从开发人员的角度来看,即使延迟装载或许是个需求,这也允许你编写透明持久性的代码。

如果你在调试器中检查实际的实例时,你会看到该实例的底层类型与我原先声明的类型是不同的:

image

虽然实体框架尽力以最小的摩擦提供自动的延迟装载,但在处理你想要添加或附加手工生成的实例时,或者当你序列化/发序列化实例时,这是你需要知道的事情。

为POCO实体手工生成代理实例

为了允许可以添加或附加的代理实例的生成,你可以使用ObjectContext上的CreateObject工厂方法来生成实体实例:

Category category = context.CreateObject<Category>();

 

要把这个记住,在生成你想要用于实体框架的实例时,要使用CreateObject

用“变动跟踪代理(Change Tracking Proxies)”来提供更有效的变动跟踪

到目前为止,我们讨论过的标准POCO实体都依赖于基于快照(snapshot)的变动跟踪,即,实体框架会保管实体变动之前的值和关系的快照,这样,在保存(Save)时,可以与当前的值做比较。但这个比较的花销是相当大的,如果跟基于EntityObject的实体的变动跟踪的方式相比的话。

还有另外一种类型的代理,它允许你在使用POCO实体时得到比较好的变动跟踪性能。

如果你熟悉IPOCO接口,你知道IEntityWithChangeTracker是要求你在类中实现、来向实体框架提供变动通知的接口之一。

变动跟踪代理从你的POCO实体类继承而来,在运行时给你提供这个功能,而不要求你自己实现IPOCO接口。

从许多方面讲,用这种方式的话,你是鱼与熊掌都兼得了:你得到了POCO类的透明持久性,在变动跟踪方面你也得到了EntityObject / IPOCO 的性能。

为了得到变动跟踪代理,基本的规则是,你的类必须是公开的,非抽象的或者非密封的(non-sealed)。你的类对所有要持久的属性都必须实现公开的virtual getters/setters。最后,你必须将基于集合的关系导航属性严格声明为ICollection<T>。它们不能是具体的实现或者继承自ICollection<T>的另外的接口(与延迟装载代理有所不同)。

这里是我的Product POCO类的例子,它将在运行时给我提供更有效的基于代理的变动跟踪:

public class Product{    public virtual int ProductID { get; set; }    public virtual string ProductName { get; set; }    public virtual int SupplierID { get; set; }    public virtual string QuantityPerUnit { get; set; }    public virtual decimal UnitPrice { get; set; }    public virtual InventoryDetail InventoryDetail { get; set; }    public virtual bool Discontinued { get; set; }    public virtual Category Category { get; set; }}

再说一遍,要记住,如果你要将实体加到或附加到上下文的话,你必须使用CreateObject来生成代理实例。但不依赖代理的纯粹的POCO实体和基于代理的实体可以共处。你只有在涉及基于代理的实体时才需使用CreateObject。

如果我想在同个POCO类型中同时启用延迟装载和更好的变动跟踪,该怎么办?

这两个东西不是互相排斥的,你不必在延迟装载代理和变动跟踪代理间做选择。如果你想要延迟装载,以及有效的变动跟踪,你只要按照变动跟踪代理的规则办,以及启用延迟装载选项。变动跟踪代理会给你提供延迟装载,如果延迟装载选项是启用了的话。

显式装载

这个延迟装载的功能确实很棒,但你们中很多人大概想要完全控制你是如何装载相关实体的吧。你甚至会选择走纯粹的POCO之路,而不诉诸于任何自动代理生成能给你提供的功能。

这是完全可以接受的,(在很多情形下也许是更好的方法),你可以使用显式关系装载,对你是如何在数据库中查询数据的做完全的控制。

在POCO中做显式装载,有2个选项:

一个是使用 ObjectContext.LoadProperty ,设置你想要装载的导航属性的名称:

context.LoadProperty(beveragesCategory, "Products");

这是可行的,但你可以看出来,这并不类型安全(type safe)。如果我没有正确的导航属性的名称的话,我会得到一个运行时异常。

你们中的一些人大概更喜欢这个:

context.LoadProperty(beveragesCategory, c => c.Products);

我可以使用lambda表达式来指定我想要显式装载的属性,这提供了更好的类型安全。

上面是我计划在这第二个贴子里讨论的所有的内容了。但以后还会有更多内容,在这个系列的最后一篇里,我们将讨论在处理纯POCO(非代理化的)实例以及在你的对象图和对象状态管理器(Object State Manager)间保持一致时需要知道的几件事情。我们也会讨论你也许想要知道的SaveChanges方法的多个变种。

与此同时,请看一下我更新过的样例代码,其中包括了我们在本贴里讨论过的一些东西。

Faisal Mohamood

Entity Framework的Program Manager

Posted in 未分类 | Leave a comment

【翻译】实体框架中的POCO支持 – 第一部分 – 体验

【译者按】 Entity Framework 1.0 发布也有一段时间了,但感觉用的人很少。其中一个很大的原因,也许就是不支持POCO。要知道,Entity Framework 1.0的做法是让你的实体从EF的基类继承而来,这对很多人,特别是崇尚DDD的人来说,那是一副难以下咽的药啊。曾有微软开发人员提供了一个 POCO Adapter,但那究竟不是正规的做法。Visual Studio 2010 和 .NET 4.0 提供了许许多多的新特性,真是让人激动,向往,大有一种回到.NET 1.0 刚出来时的感觉。春天啊(应该是夏天啊),你终于回来了(虽然早晨/晚上还是unseasonably冷)。其中的Entity Framework 4.0版本将提供POCO支持,对很多人来说,这是开始使用Entity Framework的时候了。ADO.NET 团队博客上贴出了一些关于EF和POCO的贴子,非常值得一读。

【原文地址】POCO in the Entity Framework: Part 1 – The Experience
【原文发表日期】 21 May 09 05:46 PM

上个星期,我在《POCO初览》中提到了POCO实体支持是我们加到Entity Framework 4.0中的新功能之一。这个星期,我要对Entity Framework 4.0中POCO支持的细节进行深入讨论。

要讨论的东西很多,包括

  • Entity Framework 4.0中总的POCO体验
  • POCO的变化跟踪
  • 关系修整
  • 复杂类型
  • 延迟(懒式)装载和显式装载
  • 最佳实践

在这个贴子中,我将主要着重于总的体验,这样,你可以马上启用Entity Framework 4.0中的POCO支持。我将使用一个简单的例子做示范,这样,你可以看到在Entity Framework 4.0中使用POCO的感觉。我将使用Northwind数据库,在随后的贴子中,将继续建立在这个例子的基础之上。

第一步: 创建模型,关闭默认的代码生成

虽然POCO允许你以透明持久化的方式编写自己的实体类,但还是有必要“接入”持久性和EF元数据,这样你的POCO实体可以从数据库中复原,以及持久化到数据库中。为实现这个,你还是需要使用实体框架设计器创建一个实体数据模型(Entity Data Model),或者提供跟你在Entity Framework 3.5中生成的完全一样的CSDL, SSDL 和 MSL 元数据文件。所以,首先,我将使用ADO.NET实体数据模型向导(Entity Data Model Wizard)来生成一个EDMX。

image

  1. 创建一个类库项目来定义你的POCO类型,我将其命名为NorthwindModel。这个项目与持久性毫不相关,对实体框架没有依赖性。
  2. 创建一个类库项目,它将包含与持久性相关的代码,我将其命名为NorthwindData。这个项目除了对NorthwindModel项目有依赖外,还对实体框架(System.Data.Entity)有依赖。
  3. NorthwindData 项目中添加新项,加一个名为“Northwind.edmx ”的ADO.NET实体数据模型(这么做将自动在项目中添加对实体框架的依赖)。
  4. 选择“从数据库生成”,给Northwind数据库建造模型
  5. 暂时只选CategoriesProducts这两个你感兴趣的表到你的实体数据模型中。

至此,生成了可以为我所用的实体数据模型,但在开始编写代码前,还有最后一步:关闭代码生成。毕竟你感兴趣的是POCO啊,请去除负责从Northwind.edmx生成基于EntityObject代码的Custom Tool(自定义工具)属性,这将关闭你的模型的相应代码生成。

image

现在,我们可以编写POCO实体了。

第二步: 编写你的POCO实体

我将为Category和Product编写简单的POCO实体,这些类将加到NorthwindModel项目中去。注意,我在这里展示的,不应该看成是最佳实践,这里的意图只是示范可以工作的最简单的例子。在以后我们将根据需要将对这些例子进行扩展和定制,之后建立在其基础之上,使用RepositoryUnit of Work模式做进一步的扩展。

这里是Category实体的样例代码:

    public class Category
    {
        public int CategoryID { get; set; }
        public string CategoryName { get; set; }
        public string Description { get; set; }
        public byte[] Picture { get; set; }
        public List<Product> Products { get; set; }
    }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

注意,在模型中我既为标量字段定义了属性,也定义了导航属性。模型中的导航属性(Navigation Property)转换成了List<Product>。

类似地,Product实体可以这样编写:

    public class Product
    {
        public int ProductID { get; set; }
        public string ProductName { get; set; }
        public int SupplierID { get; set; }
        public string QuantityPerUnit { get; set; }
        public decimal UnitPrice { get; set; }
        public Int16 UnitsInStock { get; set; }
        public Int16 UnitsOnOrder { get; set; }
        public Int16 ReorderLevel { get; set; }
        public bool Discontinued { get; set; }
        public Category Category { get; set; }
    }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

在这个例子中,因为其间的关系,一个Product只允许属于一个Category,因此我们有一个对Category的引用(而不象Category中的导航属性,是一个List<T>集合)。

第三步: 编写你的实体框架上下文

为把所有这些东西结合在一起,我所要做的最后一件事情是,提供一个上下文实现(就象使用默认的代码生成时你得到的ObjectContext实现一样)。上下文(context)是把持久化意识带进你的应用的胶水(glue),它允许你编写查询,将实体复原,以及将变化存回到数据库中去。该上下文将是NorthwindData项目的一部分。

为简单起见,我将我们的上下文类包括在了包含实体类型的同个类库项目中了,但在我们讨论诸如Repository Unit of Work等模式时,我们将做这样的设置,即,你将拥有一个纯粹的POCO类库,不带任何一丝与持久性相关的东西。

这里是针对我们场景的简单的上下文实现:

public class NorthwindContext : ObjectContext
    {
        public NorthwindContext() : base("name=NorthwindEntities", "NorthwindEntities")
        {
            _categories = CreateObjectSet<Category>();
            _products = CreateObjectSet<Product>();
        }

        public ObjectSet<Category> Categories
        {
            get
            {
                return _categories;
            }
        }
        private ObjectSet<Category> _categories;

        public ObjectSet<Product> Products
        {
            get
            {
                return _products;
            }
        }
        private ObjectSet<Product> _products;
    }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

注意,ObjectSet<T> 是Entity Framework 4.0中引进的一个特殊的ObjectQuery<T>。

就这么简单!现在你有了纯粹的POCO实体,一个简单的上下文,允许你编写象这样的查询:

    NorthwindContext db = new NorthwindContext();

    var beverages = from p in db.Products
                    where p.Category.CategoryName == "Beverages"
                    select p;

从数据库中复原的实体是纯粹的POCO实体,你可以对这些实体做变动,将变动保存到数据库中,跟平常的EntityObjectIPOCO实体非常类似。你将得到实体框架提供的所有服务,唯一的区别是,你现在使用的是纯粹的POCO实体。

就我们的简化了的例子而言,尚有无数改进的可能性。但在那之前,我想先把一些基本的问题解决掉。

在使用POCO之前我需要一个实体数据模型么?

是的,Entity Framework 4.0中的POCO支持只是去除了在你的实体类中带特定于持久性的关注的需求而已。但还是需要你提供CSDL/SSDL/MSL (总称EDMX)元数据,这样实体框架才能够将你的实体和元数据结合起来,以允许你访问数据。我们还在另外开发一个东西,它允许你做真正的“代码优先(code-first)”的开发,而不需要预先定义好的EDMX模型。这个功能的社区预览版将在几个月内在网上发布,一有机会我们就会将其加入实体框架。一如既往,你的反馈对我们极其有用。

我始终都只能手写这些实体和上下文么?

不, Entity Framework 4.0中有一个非常强大和灵活的代码生成机制,该机制是基于T4之上的(Alex曾在这里的博客上讨论过)。你可以提供你自己的模板,允许你按照你自己选择的方式建造实体。我们还在努力,以提供一些标准的模板,可为你生成POCO实体。

使用POCO实体时,元数据是怎么映射的?

在Entity Framework 3.5中,基于EntityObjectIPOCO的实体都是依赖着使用映射特性(attributes),对实体类型和属性进行修饰和映射到概念性模型中对应的元素的。Entity Framework 4.0 引入了基于约定(convention)的映射,以允许不用显式的修饰,就可将实体类型,属性,复杂类型和关系映射到概念性模型。一个简单的规则是,在你的POCO类中使用的实体类型名称,属性名称,和复杂类型名称必须匹配那些在概念性模型中定义了的相应名称。命名空间的名称不在考虑之中,类中的命名空间定义和概念性模型中的命名空间定义不必相符。

我的实体类中的所有属性都需要有公开的getters和setters么?

你可以在你的POCO类型的属性上使用任何访问控制修饰符(access modifier),只要被映射的任何属性都不是虚拟的,以及你不需要局部信任(partial trust)支持。在局部信任下运行时,对你的实体类的访问控制修饰符的可见性有一些特定的要求。我们将对在涉及局部信任时,所支持的完整的访问控制修饰符集提供详细的文档。

对基于集合的导航属性都支持哪些集合类型?

任何属于ICollection<T>的类型都是支持的。如果你对ICollection<T>类型的字段不以具体的类型初始化的话,那么从数据库中复原实体集合时, 将提供List<T>

我可以有单向的关系么?例如,我想在我的Product类中有一个 Category 属性,但在Category类中我不想要一个Products集合。

是的,这是支持的。唯一的限制是,你的实体类型必须反映模型中所定义的东西。如果你不想拥有对应于关系的某一边的导航属性的话,那么你需要从模型中将其完全剔除。

POCO支持延迟(懒式)装载么?

是的,POCO是通过使用代理类型来支持延迟(懒式)装载的,这些代理类型是在你的POCO类之上提供自动的懒式装载行为的。在讨论到延迟装载时,我们会对此做详述,在那之前,知道一下我们也支持通过使用“Include”来实现的早期装载(eager loading),象这样:

var beverageCategory = (from c in context.Categories.Include("Products")
                        where c.CategoryName == "Beverages"
                        select c).Single();

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

如果我要使用延迟(懒式)装载的话,我不用这么做, 在讨论代理时我们会对此进行讨论。

修整(fix-up)关系

要了解有2种类型的修整:1)查询时的修整, 2) 对你的实体/关系进行改动时的修整。

查询时的修整

查询时的修整是在同一个ObjectContext上使用不同的查询来装载相关的实体时发生的。例如,如果我查询了一个分类实例“Beverages(饮料)”,之后又查询一个产品实例“Chai”(是在Beverages分类中的),我想要chai.Category 指向Beverages分类实例,不用我做额外的工作。

这在今天是自动的,而且已经工作。

对你的实体/关系进行改动时的修整

这是在添加/去除与另一个实体相关的实体或者改变关系时两个实体间的关系修整。设想一下这样的例子:在我创建一个名叫“Diet Chai”的新产品时,我想要将其与Beverages 分类相关联。

在Entity Framework 4.0 Beta1 中,在这种情形下,POCO实体得不到自动的关系修整。在处理实体间的关系变动时,你需要确认你的POCO类型包含了在关系两头正确处理关系修整的逻辑。

例如,在我的Northwind例子中,我在Category上定义了如下的方法,以支持添加/去除订单。

        public void AddProduct(Product p)
        {
            if (Products == null)
            {
                Products = new List<Product>();
            }

            if (!Products.Contains(p))
            {
                Products.Add(p);
            }

            p.Category = this;
        }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

总的来说,使用象这样的模式来支持添加/去除相关项,要比使用关系集合来添加/去除相关实体为好。你还可以选择将实际由EF支持的集合的getter/setter变成私有或内部访问控制,来支持更为细颗粒的访问,但就象前面提到的那样,其中一些东西取决于你是否需要局部信任支持这样的需求。

我们在这个“简短的综述”中对总的POCO体验做了一番讨论。要讨论的东西还有很多很多,下个星期我将继续这个讨论。与此同时,请看一下附件中的完整解决方案,如果你对我们在这里讨论的所有概念的相应例程代码感兴趣的话。请记住,你必须在运行这个例程的机器上安装一个本地Northwind数据库。

在下一个贴子中,我们将看一下复杂类型,变化跟踪,代理,懒式装载和早期装载。在那之前,请阅读一下MSDN这里的关于POCO的文档,以了解Entity Framework 4.0中的POCO支持的细节。

请告知我们你们的想法!

Faisal Mohamood

Entity Framework的Program Manager

Posted in 未分类 | Leave a comment

安装 Windows 7 beta

大家都知道,Windows 7 beta(7000)已于昨天推出,参与微软Connect,以及有MSDN和TechNet订阅的都可以下载了。

下载完后,决定不是全新安装,而是选择了从先前安装的Windows 7的6801版本升级,没想到安装花了很长的时间。只好安慰自己,假如全新安装的话,加上安装办公软件+抗毒软件+以及小孩安装的各种软件,总的时间恐怕会更长。

安装完了,赶紧安装第一个补丁,KB961367,这修复了Media Player12的一个缺陷,因为它会在某些特别的情形下剪去某些mp3文件的前面几秒。呵,因为这是小孩的机器,我可不愿为坏掉了的mp3文件负责。

运行Norton的更新和扫描,居然蓝屏死机了,又试了一次,还是一样。

界面有了几个变动,譬如新的taskbar和Jump list,细节就不谈了。

用的时候感觉很平滑,响应比前面的那个版本要快,性能比以前好了。

【更新】微软刚公开了beta测试,任何人都可以在下面这个链接下载(但限于前面二百五十万位):

Welcome to the Windows 7 Beta Customer Preview Program
http://technet.microsoft.com/en-us/evalcenter/dd353205.aspx

【最新更新(1月11日)】因为下载的人太多,导致服务器运行缓慢,微软一度推迟了下载。在做了调整之后,现在恢复了下载。同时取消了二百五十万份下载数的限制,但只开放下载2个星期(到1月24日为止)。上面的链接依然有效,还有这个链接:

Download the Windows 7 Beta
http://www.microsoft.com/windows/windows-7/beta-download.aspx

 

Posted in 未分类 | Tagged | Leave a comment