【原文地址】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