仅限EF6仅向前 - 此页面中讨论的功能,API等在实体框架6中引入。如果您使用的是早期版本,则部分或全部信息不适用。

使用Code First时,您的模型是使用一组约定从您的类计算的。默认的Code First Conventions确定哪些属性成为实体的主键,实体映射到的表的名称,以及默认情况下十进制列具有的精度和比例。

有时,这些默认约定对于您的模型并不理想,您必须通过使用Data Annotations或Fluent API配置许多单个实体来解决这些问题。自定义代码优先约定允许您定义自己的约定,为您的模型提供配置默认值。在本演练中,我们将探讨不同类型的自定义约定以及如何创建它们。

此页面介绍了用于自定义约定的DbModelBuilder API。此API应足以创作大多数自定义约定。但是,还可以创建基于模型的约定 - 一旦创建后操纵最终模型的约定 - 来处理高级场景。有关更多信息,请参阅基于模型的约定(EF6以上版本)

让我们从定义一个可以与我们的约定一起使用的简单模型开始。将以下类添加到项目中。

     using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq; public class ProductContext : DbContext
{
static ProductContext()
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<ProductContext>());
} public DbSet<Product> Products { get; set; }
} public class Product
{
public int Key { get; set; }
public string Name { get; set; }
public decimal? Price { get; set; }
public DateTime? ReleaseDate { get; set; }
public ProductCategory Category { get; set; }
} public class ProductCategory
{
public int Key { get; set; }
public string Name { get; set; }
public List<Product> Products { get; set; }
}

让我们编写一个约定,将任何名为Key的属性配置为其实体类型的主键。

在模型构建器上启用约定,可以通过在上下文中重写OnModelCreating来访问这些约定。更新ProductContext类,如下所示:

  public class ProductContext : DbContext
{
static ProductContext()
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<ProductContext>());
} public DbSet<Product> Products { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties()
.Where(p => p.Name == "Key")
.Configure(p => p.IsKey());
}
}

现在,名为Key的模型中的任何属性都将被配置为其所属实体的主键。

我们还可以通过过滤我们要配置的属性类型来使我们的约定更具体:

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

这会将名为Key的所有属性配置为其实体的主键,但前提是它们是整数。

IsKey方法的一个有趣特征是它是附加的。这意味着如果您在多个属性上调用IsKey,它们都将成为复合键的一部分。需要注意的是,当您为键指定多个属性时,还必须为这些属性指定顺序。您可以通过调用HasColumnOrder方法执行此操作,如下所示:

     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());

此代码将配置模型中的类型,以使组合键由int Key列和字符串Name列组成。如果我们在设计器中查看模型,它将如下所示:

属性约定的另一个示例是在我的模型中配置所有DateTime属性以映射到SQL Server中的datetime2类型而不是datetime。您可以通过以下方式实现此目的:

     modelBuilder.Properties<DateTime>()
.Configure(c => c.HasColumnType("datetime2"));

定义约定的另一种方法是使用约定类来封装您的约定。使用Convention类时,您将创建一个继承System.Data.Entity.ModelConfiguration.Conventions命名空间中的Convention类的类型。

我们可以通过执行以下操作来创建具有我们之前显示的datetime2约定的Convention类:

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

要告诉EF使用此约定,请将其添加到OnModelCreating中的Conventions集合中,如果您一直按照演练进行操作,则它将如下所示:

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

如您所见,我们将约定的实例添加到约定集合中。继承自Convention提供了一种跨团队或项目分组和共享约定的便捷方式。例如,您可以拥有一个类库,其中包含所有组织项目使用的一组通用约定。

自定义属性

 约定的另一个重要用途是在配置模型时启用新属性。为了说明这一点,让我们创建一个属性,我们可以使用该属性将String属性标记为非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属性添加到任何字符串属性,这意味着数据库中的列将存储为varchar而不是nvarchar。

有关此约定的一点需要注意的是,如果将NonUnicode属性放在字符串属性以外的任何内容上,则会抛出异常。这样做是因为您无法在字符串以外的任何类型上配置IsUnicode。如果发生这种情况,那么您可以使您的约定更具体,以便过滤掉任何不是字符串的内容。

虽然上述约定适用于定义自定义属性,但还有另一个API可以更容易使用,尤其是当您想要使用属性类中的属性时。

对于此示例,我们将更新我们的属性并将其更改为IsUnicode属性,因此它看起来像这样:

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

一旦我们有了这个,我们可以在我们的属性上设置一个bool来告诉约定一个属性是否应该是Unicode。我们可以通过访问配置类的ClrProperty来实现这一点,如下所示:

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

这很容易,但通过使用convention API的Having方法,有一种更简洁的方法来实现这一点。Having方法有一个类型为Func <PropertyInfo,T>的参数,它接受PropertyInfo与Where方法相同,但是应该返回一个对象。如果返回的对象为null,则不会配置该属性,这意味着您可以像使用Where一样过滤掉属性,但它的不同之处在于它还将捕获返回的对象并将其传递给Configure方法。这类似于以下内容:

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

自定义属性不是使用Having方法的唯一原因,在配置类型或属性时,您需要在任何地方推断您正在过滤的内容。

到目前为止,我们所有的约定都是针对属性的,但是还有另一个约定API区域用于配置模型中的类型。这种体验类似于我们目前看到的约定,但configure中的选项将在实体而不是属性级别。

类型级约定可以真正有用的一件事是更改表命名约定,要么映射到不同于EF默认值的现有模式,要么创建具有不同命名约定的新数据库。为此,我们首先需要一个方法,它可以接受模型中类型的TypeInfo,并返回该类型的表名应该是什么:

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

此方法接受一个类型并返回一个字符串,该字符串使用带有下划线的小写而不是CamelCase。在我们的模型中,这意味着ProductCategory类将映射到名为product_category而不是ProductCategories的表。

一旦我们有了这个方法,我们就可以在这样的约定中调用它:

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

此约定将我们模型中的每个类型配置为映射到从GetTableName方法返回的表名。此约定相当于使用Fluent API为模型中的每个实体调用ToTable方法。

有一点需要注意的是,当你调用ToTable时,EF将把你提供的字符串作为确切的表名,而不是在确定表名时通常会做的任何复数。这就是为什么我们的约定中的表名是product_category而不是product_categories。我们可以通过自己打电话给多元化服务来解决这个问题。

在下面的代码中,我们将使用EF6中添加的依赖项解析功能来检索EF将使用的复数化服务并复数我们的表名。

  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命名空间中的扩展方法,您需要在上下文中添加using语句才能使用它。

ToTable和继承

ToTable的另一个重要方面是,如果您将类型显式映射到给定表,那么您可以更改EF将使用的映射策略。如果为继承层次结构中的每种类型调用ToTable,将类型名称作为表的名称传递,就像我们上面所做的那样,那么您将默认的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)。该表将包含一个带有鉴别器列的员工和经理,该列将告诉您每行中存储的实例类型。这是TPH映射,因为层次结构有一个表。但是,如果在两个classe上调用ToTable,则每个类型将被映射到其自己的表,也称为TPT,因为每个类型都有自己的表。

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

上面的代码将映射到如下所示的表结构:

您可以通过以下几种方式避免这种情况,并维护默认的TPH映射:

  1. 使用层次结构中每种类型的相同表名调用ToTable。
  2. 仅在层次结构的基类上调用ToTable,在我们的示例中将是employee。

约定以最后的方式运行,与Fluent API相同。这意味着如果你编写两个约定来配置相同属性的相同选项,那么最后一个执行获胜。例如,在下面的代码中,所有字符串的最大长度都设置为500,但我们将模型中名为Name的所有属性配置为最大长度为250。

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

因为将max length设置为250的约定是在将所有字符串设置为500的约定之后,所以我们模型中名为Name的所有属性将具有250的MaxLength,而任何其他字符串(例如描述)将为500。这种方式意味着您可以为模型中的类型或属性提供一般约定,然后将它们覆盖在不同的子集上。

Fluent API和Data Annotations也可用于在特定情况下覆盖约定。在上面的示例中,如果我们使用Fluent API来设置属性的最大长度,那么我们可以在约定之前或之后放置它,因为更具体的Fluent API将胜过更一般的配置约定。

由于自定义约定可能受默认的Code First约定的影响,因此添加约定以在另一个约定之前或之后运行会很有用。为此,您可以在派生的DbContext上使用Conventions集合的AddBefore和AddAfter方法。以下代码将添加我们之前创建的约定类,以便它将在内置键发现约定之前运行。

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

在添加需要在内置约定之前或之后运行的约定时,这将是最有用的,可以在此处找到内置约定的列表:System.Data.Entity.ModelConfiguration.Conventions Namespace

您还可以删除不希望应用于模型的约定。要删除约定,请使用Remove方法。以下是删除PluralizingTableNameConvention的示例。

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

实体框架自定义代码优先约定(EF6以后)的更多相关文章

  1. 从实体框架核心开始:构建一个ASP。NET Core应用程序与Web API和代码优先开发

    下载StudentApplication.Web.zip - 599.5 KB 下载StudentApplication.API.zip - 11.5 KB 介绍 在上一篇文章中,我们了解了实体框架的 ...

  2. 【摘录】使用实体框架、Dapper和Chain的仓储模式实现策略

    以下文章摘录来自InfoQ,是一篇不错的软问,大家细细的品味 关键要点: Dapper这类微ORM(Micro-ORM)虽然提供了最好的性能,但也需要去做最多的工作. 在无需复杂对象图时,Chain这 ...

  3. 领域实体框架Rafy2 发布了

    在2009年我在codeplex发布了1.0版本OpenExpressApp,下载地址:http://openexpressapp.codeplex.com/.OEA 1.0 作为我十多年开发工作的一 ...

  4. 福利到!Rafy(原OEA)领域实体框架 2.22.2067 发布!

    距离“上次框架完整发布”已经过去了一年半了,应群中的朋友要求,决定在国庆放假之际,把最新的框架发布出来,并把帮助文档整理出来,这样可以方便大家快速上手.   发布内容 注意,本次发布,只包含 Rafy ...

  5. Rafy 领域实体框架简介

    按照最新的功能,更新了最新版的<Rafy 领域实体框架的介绍>,内容如下: 本文包含以下章节: 简介 特点 优势 简介 Rafy 领域实体框架是一个轻量级 ORM 框架. 与一般的 ORM ...

  6. 在快速自定义的NopCommerce中使用实体框架(EF)代码优先迁移

    我看到很多nopCommerce论坛的用户问他们如何使用Entity Framework(EF)代码优先迁移来自定义nopCommerce,添加新的字段和entites核心.我实际上在做nopComm ...

  7. 结合实体框架(代码优先)、工作单元测试、Web API、ASP. net等,以存储库设计模式开发示例项目。NET MVC 5和引导

    介绍 这篇文章将帮助你理解在库模式.实体框架.Web API.SQL Server 2012.ASP中的工作单元测试的帮助下设计一个项目.净MVC应用程序.我们正在开发一个图书实体和作者专用的样例图书 ...

  8. 用 MVC 5 的 EF6 Code First 入门 系列:MVC程序中实体框架的Code First迁移和部署

    用 MVC 5 的 EF6 Code First 入门 系列:MVC程序中实体框架的Code First迁移和部署 这是微软官方SignalR 2.0教程Getting Started with En ...

  9. EF框架搭建小总结--CodeFirst代码优先

    前言:之前在下总结编写了一篇 EF框架搭建小总结--ModelFirst模型优先 博文,看到一段时间内该博文的访问量蹭.蹭蹭.蹭蹭蹭...往上涨(实际也不是很多,嘿嘿),但是还是按捺不住内心的喜悦(蛮 ...

随机推荐

  1. Python2.7-sqlite3

    sqlite3模块,SQLite 是用 C 写的轻量级的数据库,sqlite3 模块提供了对数据库的接口,要使用必须首先创建一个 Connection 对象,代表连接至数据库,然后才能继续操作,操作数 ...

  2. C#委托+回调详解

    今天写不完,明天会接着写的,,,, 学习C#有一段时间了,不过C#的委托+回调才这两天才会用,以前只是知道怎么用.前面的一篇文章,函数指针,其实是为这个做铺垫的,说白了委托就相当于C语言中的函数指针, ...

  3. 采用PowerDesigner 设计数据库

    PowerDesigner设计数据库的教程网上都有,最好的是我一位同学写的,地址: 点击这里 我的大致流程如下: 首先要以管理员的身份打开PowerDesigner,如果没这么做,将导致后面无法创建S ...

  4. word导入导出自定义属性列表

    Sub ExportCustom() ' ' ExportCustom 宏 ' 导出自定义属性到custom.txt ' Dim lFileNumber As Long Dim sFilePath A ...

  5. EZ 2018 03 30 NOIP2018 模拟赛(六)

    链接:http://211.140.156.254:2333/contest/67 转眼间上次加回来的Rating又掉完了. 这次不知为何特别水,T1想了一段时间没想出来弃了,导致后面心态炸了. T2 ...

  6. mfc CSpinButton

    知识点: CSliderCtrl(滑块)控件 CSliderCtrl常用属性 CSliderCtrl类常用成员函数 CSliderCtrl运用示例 一.CSliderCtr常用属性 Orientati ...

  7. Canvas事件绑定

    canvas事件绑定 众所周知canvas是位图,在位图里我们可以在里面画各种东西,可以是图片,可以是线条等等.那我们想给canvas里的某一张图片添加一个点击事件该怎么做到.而js只能监听到canv ...

  8. TMS320VC5509驱动AT24C02

    1. 刚开始的波形不太对,比如如下代码 i2c_status = I2C_write( at24c02_write_buf, //pointer to data array , //length of ...

  9. 页面添加友盟(CNZZ)统计和事件追踪

    1. 在页面中引入友盟(CNZZ)统计的 JS 代码 <script type="text/javascript"> // 统计 var cnzz_protocol = ...

  10. ZAB协议和Paxos算法

    前言在上一篇文章Paxos算法浅析中主要介绍了Paxos一致性算法应用的场景,以及对协议本身的介绍:Google Chubby是一个分布式锁服务,其底层一致性实现就是以Paxos算法为基础的:但这篇文 ...