代码改变世界

【转】【翻译】Code Only增强

2009-08-18 11:20  E2Home  阅读(462)  评论(0编辑  收藏  举报

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

在配置完映射后,你还可以象这样在映射上指定facets:

customerMap.ForProperty(c => c.ID)
           .Identity();
customerMap.ForProperty(c => c.Name)
           .MaxLength(100)
           .NonUnicode();
customerMap.ForProperty(c => c.Website)
           .MaxLenght(200)
           .Nullable();

指定数据表

最后一步是指派数据表映射。

builder.Tables[“dbo.Custs”] = customerMap;

至此,我们为Customer类指定了自定义表,映射和自定义facets。

指定继承:

CodeOnly默认所用的继承策略是Table Per Hierarchy (每个类分层结构共用一个数据表)(或 TPH)。

但如果你需要一个不同的策略,你需要动手配置相应映射

设想一下,如果你要映射三个类:Vehicle , CarBoat,其中CarBoat是从Vehicle继承而来,而Vehicle本身是个抽象类。

clip_image001

Table Per Hierarchy (TPH,每个类分层结构共用一个数据表)

如果你想要使用TPH做映射,你可以这么做:

var vehicleMap = 
    Map.OfTypeOnly<Vehicle>(
        v => new {
            vid = v.ID,
            v.Name,
            vdesc = v.Description
            v.MaxPassengers,
        }
    ).Union(Map.OfTypeOnly<Car>(
        c => new { 
            vid = c.ID, 
            c.Name,
            vdesc = c.Description 
            c.MaxPassengers,
            trans = c.Transmission,
            tspd = c.Topspeed,
            ccty = c.EngineCapacity,
            ncyld = c.NoCylinder,
            discriminator = “CAR”
        })
    ).Union(Map.OfTypeOnly<Boat>(
        b => new { 
            vid = b.ID, 
            b.Name,
            vdesc = b.Description 
            b.MaxPassengers,
            lng = b.Length,
            b.HasSail,
            b.HasEngine
            discriminator = “BOAT”
        })
    );

builder.Tables[“dbo.vehicles”] = vehicleMap;

在TPH映射中:

  1. OfTypeOnly() 用来创建映射片段。
  2. 然后,映射片段可以联合(union)起来,这样,它们就可以指派到一个表上(TPH中整个类分层结构共用一个数据表)。
  3. 每个非抽象类型需要一个鉴别器字段(discriminator column),该字段可以是任何名称,但类分层结构中的每个非抽象类型必须有一个不同的常数(即“CAR”)。
  4. 在映射继承类时,你必须重新映射基类中已经映射了的所有属性。

Table Per Type (TPT,每个类型一个表)

如果你想要使用Table Per Type (每个类型一个表) 或TPT来映射同个类分层结构,你该这么做:

builder.Table[“dbo.Vehicles”]= 
    Map.OfType<Vehicle>(
        v => new {
            vid = v.ID,
            v.Name,
            vdesc = v.Description
            v.MaxPassengers,
        }
    );

builder.Tables[“dbo.Cars”] = 
    Map.OfType<Car>(
        c => new {
            cid = c.ID,
            trans = c.Transmission,
            tspd = c.Topspeed,
            ccty = c.EngineCapacity,
            ncyld = c.NoCylinders,
         }
     );

builder.Tables[“dbo.Boats”] = 
    Map.OfType<Boat>(
        b => new {
            bid = b.ID,
            lng = b.Length,
            b.HasSail,
            b.HasEngine
        }
    );

在TPT映射中:

  1. OfType<>()用于每个映射“片段”。
  2. 每个映射“片段”指派到不同的表上。
  3. 每个映射“片段”只映射在当前类上声明的属性,除非。。。
  4. 键属性必须在每个片段中映射(这允许参与该类型的表间的JOIN)。
  5. 没有鉴别器字段。

Table Per Class (TPC,每个类一个表,【译注】常规的说法是,每个非抽象类或每个具体类一个表)

你还可以象这样使用TPC来映射这个类分层结构:

builder.Tables[“dbo.Cars”] = 
    Map.OfTypeOnly<Car>(
        c => new {
            cid = c.ID,
            c.Name,
            vdesc = c.Description
            c.MaxPassengers,
            trans = c.Transmission,
            tspd = c.Topspeed,
            ccty = c.EngineCapacity,
            ncyld = c.NoCylinders,
         }
     );

builder.Tables[“dbo.Boats”] = 
    Map.OfTypeOnly<Boat>(
        b => new {
            bid = b.ID,
            b.Name,
            description = b.Description
            b.MaxPassengers,
            lng = b.Length,
            b.HasSail,
            b.HasEngine
        }
    );

在TPC映射中:

  1. 跟TPH一样,我们使用OfTypeOnly(..)
  2. 但跟TPH不一样,每个非抽象的类型拥有自己的表(所以,Vehicle没有自己的表,因为它是个抽象类)。
  3. 每个映射片段重新映射每个持久(non-transient)属性。
  4. 类分层结构中的抽象类没有映射。
  5. 没有鉴别器字段。

默认的外键的位置:

如果我们看到一个引用(譬如Order.Customer),我们假定其多重性(multiplicity)为0..1。意即,其外键或FK是可null的。

如果我们看到一个集合(譬如Customer.Orders),我们假定其多重性为多(many)

然后,在注册倒转(inverse)时,我们知道一个关系的两头的多重性,譬如,在上面的例子中,我们知道,每个Order可以有0..1个 Customer,每个Customer可以有多个Order

所以按约定,共有三种主要的关系类型,我们需要推断出其FK的位置:

0..1 to many –> 按约定,我们把FK放在many一端,所以上面的Customer.Orders例子中, FK放在Orders 表上。 

many to many –> 没什么选择,只能引进一个连接表(join table)。

0..1 to 0..1
–> 你可以配置FK应该在什么地方,但不配置的话,我们会引进一个连接表(join table)。

但有时候,引用可以不是0..1,而是1,例如,FK(不管它在什么地方),也许不可以null的。

你可以这样来指定FK不是nullable的:

var orderConfig = builder.Configure<Order>();
orderConfig.RegisterInverse(o => o.Customer, c => c.Orders);
orderConfig.ForProperty(o => o.Customer).NonNullable();


这告诉我们,每个Order正好有一个1Customer,而每个Customer多个Orders

能够区分一个引用是非Nullable,会引进几个新的多重性组合,对此,我们也需要约定:

1 to many –> 按约定,我们把FK放在many的一端, 并且在数据库中将其设置为非nullable。

0..1 to 1 –> 按约定,我们把FK放在1的一端, 并且将其设置为非nullable。

1 to 1 –> 跟0..1 to 0..1一样,我们无法决定该把FK放在何处,所以按约定,我们要引进一个连接表(join table)。

指定外键(FK)映射:

至此,我们为实体的属性创建了映射。

那么导航属性和外键怎么办?

所有的关系类型(除了many to many)可以不用数据库中的连接表(join table)来建模,所以我们允许你象这样,作为EntityMap的一部分来映射外键:

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

customerMap.RegisterInverse(c => c.SalesPerson, s => c.Clients);
builder.Tables[“dbo.Custs”] = customerMap;

这指定,Customer.SalesPerson导航属性,以及它的倒转(inverse)SalesPerson.Customers是保存在 dbo.Custs表的salesPersonFK字段中的。因为映射片段把 salesPersonFK 字段映射到了c.SalesPerson.ID上,而 SalesPerson.ID是相关的SalesPerson实体的主键(或者是主键的一部分),当然,外键是指向主键的。

指定连接表(Join Table)映射:

在many to many关系的情形下,你必须有一个连接表,所以按约定,没有映射信息,我们就会产生一个连接表(join table)。

但假如你需要更多的控制,你可以这么做:

var blogPostsMap = new AssociationMap<Blog, Post>(
     b => b.Posts
).Map(
    (b, p) => new {BlogId = b.ID, PostId = p.ID}
);

builder.Tables[“dbo.BlogPosts”] = blogPostsMap;

这是说, BlogPost间的many to many关系是保存在dbo.BlogPosts表中的,它有2个字段:

  • BlogId’ 是个外键,指向BlogID属性映射到的字段。
  • PostId’ 是个外键,指向PostID属性映射到的字段。

分割实体(Entity Splitting):

Code Only甚至还支持象分割实体这样高级的映射策略:

builder.Tables[“dbo.Customer”] = Map.OfType<Customer>(
   c => new {
      cid = c.ID,
      c.Name,
      active = c.IsActive
   }
);

builder.Tables[“dbo.CustomerDetails”] = Map.OfType<Customer>(
    c => new {
       cid = c.ID,
       c.Size,
       c.Industry
    }
);

这是说,Customer实体是分开保存在dbo.Customerdbo.CustomerDetails两个表中的。

封装所有的配置:

你还可以编写一个类,从EntityMap<T>继承而来,含有所有的映射,facets等等。例如,下面是一个类,包含了Product的配置:

public class ProductMap: EntityMap<Product>
{
    public ProductMap{
         this.Map( p => new {
              pid = p.ID,
              pcode = p.Name,
              cid = p.Category.ID
         });
         this.ForProperty(p => p.ID).Identity();
         this.ForProperty(p => p.Name).MaxLength(100)
             .NonUnicode();
         this.ForProperty(p => p.Category).NonNullable();
         this.RegisterInverse(p => p.Category,
                              c => c.Products);   
     }
}

这是个高度推荐的做法,因为配置Product类型变得容易之极:

builder.Tables[“dbo.Products”] = new ProductMap();

结束语:

正如你看到的,我们正在计划许多的增强,以允许最核心的场景。

你觉得怎么样?你喜欢这些API么?有什么东西你想要改变的?

一如既往,我们期待听到你的反馈。

Alex James
微软Entity Framework开发团队的Program Manager

本贴是Entity Framework开发团队的透明设计实践的一部分。想了解其工作原理,以及你的反馈是如何被使用的,请参阅 这个贴子



推荐文章