Entity Framework 约定

简介: Entity Framework 约定

约定,类似于接口,是一个规范和规则,使用Code First 定义约定来配置模型和规则。在这里约定只是记本规则,我们可以通过Data Annotaion或者Fluent API来进一步配置模型。约定的形式有如下几种:

  • 类型发现约定
  • 主键约定
  • 关系约定
  • 复杂类型约定
  • 自定义约定

零、类型发现约定

在Code First 中。我们定义完模型,还需要让EF上下文你知道应该映射那些模型,此时我们需要通过 DbSet 属性来暴露模型的。如果我们定义的模型由继承层次,只需要为基类定义一个DbSet属性即可(如果派生类与基类在同一个程序集,派生类将会被自动包含),代码如下:

public class Department
{
  public int DepartmentId { get; set; }
  public string Name { get; set; }
  public virtual ICollection<Blog> Blogs { get; set; }
}
public class EfDbContext : DbContext
{
  public EfDbContext()
  {
  }
  public DbSet<Department> Departments { get; set; }
}

当然,有时候我们不希望模型映射到数据库中,这时我们可以通过Fluent API 来忽略指定的模型映射到数据库中,代码写在EF上下文中:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Ignore<Department>();
}

一、主键约定

Code First 会根据模型中定义的***id***,推断属性为主键(如果类中没有id属性,会查找定义成***类名称+id***的属性,将这个属性作为主键)。如果主键类型是***int*** 或者 guid 类型,主键将会被映射为自增长标识列。例如我们上一小节中定义的类 Department,类中没有名称为id的属性,但是存在名称为类名称+id的属性***DepartmentId***,因此DepartmentId属性,将会被映射为自增长的主键。如果一个类中既没有id属性,也没有类名+id的属性,那么代码在运行时将会报错,因为EF没有找到符合要求的字段创建主键。


二、关系约定

在数据库中,我们可以通过多张表的关联查询出数据,这多张表之间的关联,就是他们的关系。同样,也可以在模型中定义这样的关系。EF中定义关系要使用到导航属性,通过导航属性可以定义多个模型之间的关系。大部分情况下我们会将导航属性和外键属性结合在一起使用。导航属性的命名规则如下:导航属性名称+主体主键名称 或者 主体类名+主键属性名称 或者 主体主键属性名。当EF检测出外键属性后,会根据外键属性是否为空来判断关系,如果外键可以为空,那么模型之间的关系将会配置成可选的,Code First 不会再关系上配置级联删除。看一个简单的代码:

public class Department
{
  public int DepartmentId { get; set; }
  public string Name { get; set; }
  public virtual ICollection<Student> Students { get; set; }
}
public class Student
{
  public int StudentId { get; set; }
  public string Name { get; set; }
  public int DepartmentId { get; set; }
  public virtual Department Department { get; set; }
}

三、复杂类型约定

在Code First 不能推断出模型中的主键,并且没有通过Data Annotations 或者Fluent API进行手动配置主键时,该模型将会自动被配置为复杂类型,检测复杂类型时要求该类型没有引用实体类型的属性。简单的说就是:一个复杂类型作为已存在对象的属性,EF会将复杂类型的类映射到已存在的表中,已存在的表包将包含这些列,而不是将复杂类型映射成另外单独的一张表。我们来看一下例子:

public class EfDbContext : DbContext
{
  protected override void OnModelCreating(DbModelBuilder modelBuilder)
  {
      modelBuilder.Entity<Order>().ToTable("Orders");
      modelBuilder.ComplexType<Order.Address>();
  }
  public DbSet<Order> Orders { get; set; }
}
public class Order
{
  public int Id;
  public string Name;
  public class Address
  {
      public string Street;
      public string Region;
      public string Country;
  }
}

四、自定义约定

当EF提供的默认约定都不符合我们要求的时候,我们可以使用自定义约定。自定义约定可以看作全局约定规则,将会运用到所有实体和属性,也可以显示实现应用到指定的模型上。


如果项目要求模型中有Id属性,就将Id作为主键映射,那么我们有两种选择来定义这个约定,首先我们而已选择Fluent API ,其次我们也可以选择自定义约定。自定义约定相对来说比Fluent API 要简单,只需一行代码即可解决。我们只需要在 OnModelCreating 方法中加入如下代码即可:

modelBuilder.Properties().Where(p => p.Name == "Id").Configure(p => p.IsKey());

注:当多个属性存在相同约定配置时,最后一个约定将覆盖前面所有相同的约定。

自定义约定包含一个约定接口 IConvention,IConceptualModelConvention 是概念模型接口,在模型创建后被调用,IStoreModelConvention 接口为存储模型接口,在模型创建之后用于操作对模型的存储,***自定义类约定***都必须在 OnModelCreating 方法中显式配置,例如我们要将模型中类型为DateTime的属性映射为datetime2,可进行如下配置:

public class DateTime2Convention : Convention
{
    public DateTime2Convention()
    {
        this.Properties<DateTime>().Configure(c => c.HasColumnType("datetime2"));
    }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Conventions.Add(new DateTime2Convention());
}

当我们自定义约定需要在另一个约定运行之前或者运行之后执行时,有可能会受到默认原定的影响,这时我们可以用到:*AddBefore AddAfter 方法,例如:将我们前面创建的约定放在内置约定发现逐渐约定之前运行。


protected override void OnModelCreating(DbModelBuilder modelBuilder)

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Conventions.AddBefore<IdKeyDiscoveryConvention>(new DateTime2Convention());
}

在开发过程中都会存在开发规范,例如对表名命名的规则,我们可以调用Types 方法该表表明约定,代码如下:

public string GetTableName(Type type)
{
    var result = Regex.Replace(type.Name, ".[A-Z]",m=>m.Value[0]+"_"+m.Value[1]);
    return result.ToLower();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Types().Configure(c => c.ToTable(GetTableName(c.ClrType)));
}

上述我们讲的都是针对全局的约定,我们在开发工程中大部分遇到的是针对符合特定条件的模型进行约定,此时我们就用到了自定义特性。我们先来看一段代码:

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

这段代码将类型为字符串的属性配置为非Unicode,下面我们建上面的特性应用到所有模型

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Properties().Where(x => x.GetCustomAttributes(false).OfType<NoUnicode>().Any())
        .Configure(c => c.IsUnicode(false));
}

添加该特性后,映射在数据库中的列将是 varchar 类型,而不是 nvarchar 类型。但是上述代码存在一个问题,如果匹配的不是字符串类型将会报错,因此我们将代码更新如下:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Properties().Where(c => c.GetCustomAttributes(false).OfType<NoUnicode>().Any())
        .Configure(c => c.IsUnicode(false));
    modelBuilder.Properties().Having(x => x.GetCustomAttributes(false).OfType<IsUnicode>().FirstOrDefault())
        .Configure((config, attr) => config.IsUnicode(attr.Uniconde));
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class NoUnicode : Attribute
{
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
internal class IsUnicode : Attribute
{
    public bool Uniconde { get; set; }
    public IsUnicode(bool isUnicode)
    {
        Uniconde = isUnicode;
    }
}
目录
相关文章
|
3月前
|
人工智能 自然语言处理 安全
ChatBI,用AI自然语言与数据对话
在数字经济快速发展的2025年,企业数据量激增,市场对快速决策和深度分析提出更高要求。本方案介绍如何通过阿里云Quick BI工具,结合AI能力,帮助商业分析师高效应对数据洪流,实现智能化分析、快速决策,提升业务洞察力与决策效率。
ChatBI,用AI自然语言与数据对话
|
11月前
|
监控 Java 应用服务中间件
Spring Boot整合Tomcat底层源码分析
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置和起步依赖等特性,大大简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是其与Tomcat的整合。
292 1
|
9月前
|
存储 Prometheus Cloud Native
分布式系统架构6:链路追踪
本文深入探讨了分布式系统中的链路追踪理论,涵盖追踪与跨度的概念、追踪系统的模块划分及数据收集的三种方式。链路追踪旨在解决复杂分布式系统中请求流转路径不清晰的问题,帮助快速定位故障和性能瓶颈。文中介绍了基于日志、服务探针和边车代理的数据收集方法,并简述了OpenTracing、OpenCensus和OpenTelemetry等链路追踪协议的发展历程及其特点。通过理解这些概念,可以更好地掌握开源链路追踪框架的使用。
884 41
关闭手机卡的流量的方法有哪些?
关闭手机卡的流量的方法主要有以下几种:
SpringBoot 项目启动初始化一个Map对象到内存
SpringBoot 项目启动初始化一个Map对象到内存
296 1
|
存储 测试技术 持续交付
团队配置管理规范:高效协作的秘诀与浅见
介绍软件配置管理规范的一些内容
373 2
|
XML API 数据库
API介绍
API介绍
526 1
|
分布式计算 Hadoop 开发工具
docker 部署 hadoop集群
docker 部署 hadoop集群
vue3 引用组件 父组件传递参数给子组件
vue3 引用组件 父组件传递参数给子组件
346 0