仅限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. ROS 安装kinect驱动+测试

    有时 ,需要用到kinect 的所有需要驱动才能使用kinect ,turtlebot2上的传感器就是kinect ,所以kinect 的用处还是很多的 , 今天就来讲一下kinect 驱动在unbu ...

  2. js 自己项目中几种打开或弹出页面的方法

    自己项目中,几种打开或弹出页面的方法(部分需要特定环境下) var blnTop = false;//是否在顶层显示 ///动态生成模态窗体(通过字符串生成) ///strModalId:模态窗体ID ...

  3. Eclipse-设置格式化代码时不格式化注释

    在Eclipse里设置格式化代码时不格式化注释 今天格式化代码 发现直接format会把注释也一块格式化了,有时候会把好好的注释弄的很乱.甚为头疼. 查阅之后解决办法如下: Windows -> ...

  4. 一个有趣的异步时序逻辑电路设计实例 ——MFM调制模块设计笔记

    本文从本人的163博客搬迁至此. MFM是改进型频率调制的缩写,其本质是一种非归零码,是用于磁介质硬盘存储的一种调制方式.调制规则有两句话,即两个翻转条件: 1.为1的码元在每个码元的正中进行一次翻转 ...

  5. 2017-2018-2 20155203《网络对抗技术》Exp6 信息搜集与漏洞扫描

    1.实践目标 掌握信息搜集的最基础技能与常用工具的使用方法. 2.实践内容 (1)各种搜索技巧的应用 通过搜索引擎进行信息搜集敏感信息 在百度搜索栏中输入filetype:关键字 site:edu.c ...

  6. 20155313 杨瀚 《网络对抗技术》实验九 Web安全基础

    20155313 杨瀚 <网络对抗技术>实验九 Web安全基础 一.实验目的 本实践的目标理解常用网络攻击技术的基本原理.Webgoat实践下相关实验. 二.基础问题回答 1.SQL注入攻 ...

  7. PowerBI开发 第十二篇:钻取

    钻取是指沿着层次结构(维度的层次)查看数据,钻取可以变换分析数据的粒度.钻取分为下钻(Drill-down)和上钻(Drill-up),上钻是沿着数据的维度结构向上聚合数据,在更大的粒度上查看数据的统 ...

  8. fatal error: caffe/proto/caffe.pb.h: No such file or directory

    solution: $make clean $make all -j8

  9. BugkuCTF web2

    前言 写了这么久的web题,算是把它基础部分都刷完了一遍,以下的几天将持续更新BugkuCTF WEB部分的题解,为了不影响阅读,所以每道题的题解都以单独一篇文章的形式发表,感谢大家一直以来的支持和理 ...

  10. Anibei前端基础学习

    html.html5.CSS2.CSS3.JQuery.Vue.js学习,后端程序媛开始学习前端开发啦.