------------------------------------------------------------------------------------------------------------

  注意:以下所讨论的功能或 API 等只针对 Entity Framework 6 ,如果你使用早期版本,可能部分或全部功能不起作用!

  ------------------------------------------------------------------------------------------------------------

  Entity Framework Code First 默认的 Conventions 约定解决了一些诸如哪一个属性是实体的主键、实体所 Map 的表名、以及列的精度等问题,但是某些时候,这些默认的约定对于我们的模型是不够理想的,此时我们就希望能够自定义一些约定。当然通过使用 Data Annotations 或者 Fluent API 也能实现这样的目的,无非就是对许多实体作出配置,但是这样的工作是极其繁琐和繁重的。而定制约定能很好地解决我们的问题,接下来就将展示如何来实现这些定制约定。

Our Model

  为了定制约定,本文引入了DbModelBuilder API ,这个 API 对于编程实现大部分的定制约定是足够的,但它还有更多的能力,例如 model-based 约定,更过信息,请参考 http://msdn.microsoft.com/en-us/data/dn469439

  在开始之前,我们先定义一个简单的模型

Custom Conventions

  下面这个约定使得任何以 key 命名的属性都将成为实体的主键

  我们也可以使得约定变得更加精确:过滤类型属性(如只有 integer 型并且名称为 key 的才能成为主键)

 protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<int>()
.Where(p => p.Name == "Key")
.Configure(p => p.IsKey());
}

  关于 IsKey 方法,有趣的是它是可添加的,这意味着如果你在多个属性上施加这个方法,那么这些属性都将变成组合键的一部分,对于组合键,指定属性的顺序是必须的。指定的方法如下

modelBuilder.Properties<int>()
.Where(x => x.Name == "Key")
.Configure(x => x.IsKey().HasColumnOrder()); modelBuilder.Properties()
.Where(x => x.Name == "Name")
.Configure(x => x.IsKey().HasColumnOrder());

Convention Classes

  另一种定义约定的方式是通过约定类来封装约定,为了使用约定类,你定义一个类型,继承约定基类(位于 System.Data.Entity.ModelConfiguration.Conventions 命名空间下)

    public class DateTime2Convention : Convention
{
public DateTime2Convention() {
this.Properties<DateTime>()
.Configure(c => c.HasColumnType("datetime2"));
}
}

  为了通知 Entity Framework 使用这个约定,需把它添加到约定集合中,代码如下

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<int>()
.Where(p => p.Name == "Key")
.Configure(p => p.IsKey()); modelBuilder.Conventions.Add(new DateTime2Convention());
}

  如你所见,我们在约定集合中添加了一个上面定义的约定的实例。

  从 Convention 继承为我们提供了一种非常方便的方式,使得组织、管理非常便捷并且易于跨项目使用。例如你可以为此建立一个类库,专门提供这些约定的合集。

Custom Attribute

  定制属性:另一种使用约定的方式就是通过在模型上配置属性(Attribute)。示例如下:我们建立一个属性(Attribute)用于标识字符属性(Property)为非Unicode

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class NonUnicode : Attribute
{ }

  现在让我们在模型上新建约定以使用此属性

modelBuilder.Properties()
.Where(x => x.GetCustomAttributes(false).OfType<NonUnicode>().Any())
.Configure(c => c.IsUnicode(false));

  通过这个约定,我们可以把 NonUnicode 属性(Attribute)施加于任何字符属性(Property),这也意味着此列在数据库中将以 varchar 的而非 nvarchar 的形式存储。

  需要注意的是,如果你把此约定施加于任何非字符属性都将引发异常,这是因为 IsUnicode 只能施加于 string (其它类型都不可以),为此我们需使得约定变得更加精确,即过滤掉任何非 string 的东西

  上面的约定解决了定义定制属性的问题,我们需要注意的是还有另一个 API 非常易于使用,尤其是你想使用 Attribute Class Properties

  让我们对上面的类做一些更新

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class IsUnicode : Attribute
{
public bool Unicode { get; set; }
public IsUnicode(bool isUnicode)
{
Unicode = isUnicode;
}
}

  一旦我们有了这个,我们就可以在 Attribute 上设置一个 bool 通知约定 Property 是否是 Unicode.  配置如下

modelBuilder.Properties()
.Where(x => x.GetCustomAttributes(false).OfType<IsUnicode>().Any())
.Configure(c => c.IsUnicode(c.ClrPropertyInfo.GetCustomAttribute<IsUnicode>().Unicode));

  上面的足够简单,但是还有一种更简洁的方式 - 就是使用 Conventions APIHaving 方法,这个 Having 方法有一个 Func<PropertyInfo, T> 类型参数,这个参数能够像 Where 一样接收 PropertyInfo. 但是前者返回的是一个 object. 如果返回对象为 null, 那么 property 将不会被配置 -- 这意味着我们可以像 Where 一样过滤某些 properties -- 但是它们又是不同的,因为前者还可以捕获并返回 object 然后传递给 Configure 方法

modelBuilder.Properties()
.Having(x => x.GetCustomAttributes(false).OfType<IsUnicode>().FirstOrDefault())
.Configure((config, att) => config.IsUnicode(att.Unicode));

  当然定制属性并不是我们使用 Having 方法的唯一原因,在任何时候,当我们配置类型或属性,需要过滤某些东西的时候是非常有用的。

Configuring Types

  到目前为止,所有的约定都是针对属性(properties)而言,其实还有其它的 conventions API 用于针对模型的类型配置。前者是在属性级别(Property Level),后者是在类型级别(Type Level

  Type Level Conventions 一个显而易见的用处是更改表的命名约定,既可以改变 Entity Framework 默认提供的从而匹配于现有的 schema, 也可以基于完全不同的命名约定创建一个全新的数据库,为此我们首先需要一个方法,接收 the TypeInfo for a type, 返回  the table name for that type

private string GetTableName(Type type)
{
var result = Regex.Replace(type.Name, ".[A-Z]", m => m.Value[] + "_" + m.Value[]); return result.ToLower();
}

  上面的方法意味着,如果施加于 ProductCategory 类,则该类将会被映射于表名 product_category 而不是 ProductCategories  

  我们可以在一个约定中这样使用它

modelBuilder.Types()
.Configure(c => c.ToTable(GetTableName(c.ClrType)));

  这个约定将配置模型中的每一个类型与方法 GetTableName 返回的表名相匹配,这与通过 Fluent API  为模型中每一个实体使用方法 ToTable 是等效的。

  需要注意的是方法 ToTable 需要一个字符串参数来作为确切的表名,如果没有复数化( pluralization )要求,我们通常会这么做。这也是为什么上面约定表名是 product_category 而不是 ProductCategories, 这可以在约定中通过调用 pluralization service 来解决

  在接下来的示例中,我们将使用 Entity Framewrok 6 中新增加的功能 Dependency Resolution 来获得 pluralization service, 从而实现表名复数化

private string GetTableName(Type type)
{
var pluralizationService = DbConfiguration.DependencyResolver.GetService<IPluralizationService>(); var result = pluralizationService.Pluralize(type.Name); result = Regex.Replace(result, ".[A-Z]", m => m.Value[] + "_" + m.Value[]); return result.ToLower();
}

  注意:GetService 的泛型版本是命名空间 System.Data.Entity.Infrastructure.DependencyResolution 下的一个扩展方法

ToTable and Inheritance

  ToTable 的另一个重要方面是如果你明确一个类型映射到给定的表,那么你可以改变 EF 使用的映射策略。如果你在继承层次中为每一个类型都调用此方法,像上面所做的那样 -- 把类型名当参数传递作为表名,那么你将改变默认的映射策略 Table-Per-Hierarchy (TPH) -- 使用 Table-Per-Type (TPT). 为了更好的说明举例如下

public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
} public class Manager : Employee
{
public string SectionManaged { get; set; }
}

  默认情况下,Employee 和 Manager 都将映射成数据库中的同一张表(Employees),表中同时包含 employeesmanagers , 存储在每一行的实例类型将由一个标识列来决定,这就是 TPH 策略带来的结果 -- 对层级只有一张表。但是如果你对每一个类都使用 ToTable, 那么每一个类型都将各自映射成自己的表,这正如 TPT 策略所示的那样

modelBuilder.Types()
.Configure(c=>c.ToTable(c.ClrType.Name));

  上面代码映射成的表结构如下图

  你可以通过如下几种方式来避免此问题并且维护默认的 TPH 映射

  • 使用相同的表名为层级中的每一个类型调用 ToTable ;
  • 只为层级中的基类调用ToTable (上例中为 Employee

Execution Order

  最后一个约定生效,这和 Fluent API 是一样的。这意味着如果在同一个属性上有两个约定,那最后一个起作用。

modelBuilder.Properties<string>()
.Configure(c => c.HasMaxLength()); modelBuilder.Properties<string>()
.Where(x => x.Name == "Name")
.Configure(c => c.HasMaxLength());

  由于最大长度250约定设置位于500后面,所以字符串的长度将会被限定在250。以这种方式可以实现约定的覆写(override

  在一些特殊的情况下,Fluent API Data Annotations 也可被用来 override Conventions

Built-in Conventions

   因为定制约定会受到默认 Code First Conventions 的影响,所以在一个约定运行之前或之后添加另一个约定是有意义的,为了实现这个,我们可以在约定集合中使用方法 AddBeforeAddAfter 

modelBuilder.Conventions.AddBefore<IdKeyDiscoveryConvention>(new DateTime2Convention());

  内建约定列表请参考命名空间 System.Data.Entity.ModelConfiguration.Conventions Namespace 

  当然你也可以移除一个约定,示例如下

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}

  参考原文:http://msdn.microsoft.com/en-us/data/jj819164

Entity Framework Code First (二)Custom Conventions的更多相关文章

  1. Entity Framework Code First (一)Conventions

    Entity Framework 简言之就是一个ORM(Object-Relational Mapper)框架. Code First 使得你能够通过C#的类来描述一个模型,模型如何被发现/检测就是通 ...

  2. Entity Framework Code First约定

    Code First使你能够通过C# 或者 Visual Basic .NET来描述模型,模型的基本规则通过使用约定来进行检查,而约定就是一系列内置的规则. 在Code First中基于类的定义通过一 ...

  3. Entity Framework Code First数据库连接

    1. 安装Entity Framework 使用NuGet安装Entity Framework程序包:工具->库程序包管理器->程序包管理器控制台,执行以下语句: PM> Insta ...

  4. Entity Framework Code First关系映射约定

    本篇随笔目录: 1.外键列名默认约定 2.一对多关系 3.一对一关系 4.多对多关系 5.一对多自反关系 6.多对多自反关系 在关系数据库中,不同表之间往往不是全部都单独存在,而是相互存在关联的.两个 ...

  5. Entity Framework Code First (三)Data Annotations

    Entity Framework Code First 利用一种被称为约定(Conventions)优于配置(Configuration)的编程模式允许你使用自己的 domain classes 来表 ...

  6. Entity Framework Code First主外键关系映射约定

    本篇随笔目录: 1.外键列名默认约定 2.一对多关系 3.一对一关系 4.多对多关系 5.一对多自反关系 6.多对多自反关系 在关系数据库中,不同表之间往往不是全部都单独存在,而是相互存在关联的.两个 ...

  7. 使用 Entity Framework Code First

    使用 Entity Framework Code First 在家闲着也是闲着,继续写我的[ASP.NET MVC 小牛之路]系列吧.在该系列的上一篇博文中,在显示书本信息列表的时候,我们是在程序代码 ...

  8. Entity Framework Code first(转载)

    一.Entity Framework Code first(代码优先)使用过程 1.1Entity Framework 代码优先简介 不得不提Entity Framework Code First这个 ...

  9. ADO.NET Entity Framework -Code Fisrt 开篇(一)

    ADO.NET Entity Framework -Code Fisrt 开篇(一) 2012-12-25 15:13 by 易code, 911 阅读, 0 评论, 收藏, 编辑 ADO.NET E ...

随机推荐

  1. WinCE及Windows软件开发相关书籍转让

    从开始做WinCE开发到现在已经十多年了,最初可以学习和参考的资料并不多,那时候还没有Stack Overflow,Google也还可以正常访问.遇到问题时,一般都在Google Groups的mic ...

  2. ural Infernal Work

    Infernal Work Time Limit:2000MS     Memory Limit:65536KB     64bit IO Format:%I64d & %I64u Descr ...

  3. AC日记——元素查找 codevs 1230

    1230 元素查找  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 钻石 Diamond 题解  查看运行结果     题目描述 Description 给出n个正整数,然后有 ...

  4. 关于PHP上传文件和中文名乱码情况

    关于PHP文件上传 在前端HTML页面,表单如下 Upload.html <!doctype html><html lang="en"><head&g ...

  5. java 24 - 5 GUI之 鼠标移动改变窗体背景颜色

    需求: 创建一个可改变背景颜色的窗口,鼠标移动到按钮上,窗口背景就会改变成特定的颜色 步骤: (大致上) 创建窗口对象 创建按钮对象 添加按钮到窗口中 设置窗口关闭监听事件 设置鼠标进入按钮区域的监听 ...

  6. linux系统下对网站实施负载均衡+高可用集群需要考虑的几点

    随着linux系统的成熟和广泛普及,linux运维技术越来越受到企业的关注和追捧.在一些中小企业,尤其是牵涉到电子商务和电子广告类的网站,通常会要求作负载均衡和高可用的Linux集群方案. 那么如何实 ...

  7. iOS根据Url 获取图片尺寸

    iOS根据Url 获取图片尺寸 // 根据图片url获取图片尺寸 +(CGSize)getImageSizeWithURL:(id)imageURL { NSURL* URL = nil; if([i ...

  8. web.config connectionStrings 数据库连接字符串的解释(转载)

    先来看一下默认的连接SQL Server数据库配置 1.默认生成 <connectionStrings> <add name="Exa*DB" connectio ...

  9. Python之路【第十一篇】前端初识之HTML

    HTML HTML解释: HTML是英文Hyper Text Mark-up Language(超文本标记语言)的缩写,他是一种制作万维网页面标准语言(标记).相当于定义统一的一套规则,大家都来遵守他 ...

  10. weblogic启动失败:Could not obtain the localhost address 解决办法

    linux下weblogic启动如果出现这个错误,多半是hosts文件不对 1.先输入hostname,查看本机计算机名(比如:server123) 2.sudo vi /etc/hosts 编辑ho ...