Entity Framework 约定

约定,相似于接口,是一个规范和规则,使用Code First 定义约定来配置模型和规则。在这里约定只是记本规则,咱们能够经过Data Annotaion或者Fluent API来进一步配置模型。约定的形式有以下几种:数据库

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

零、类型发现约定

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

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上下文中:ui

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

1、主键约定

Code First 会根据模型中定义的id,推断属性为主键(若是类中没有id属性,会查找定义成类名称+id的属性,将这个属性做为主键)。若是主键类型是int 或者 guid 类型,主键将会被映射为自增加标识列。例如咱们上一小节中定义的类 Department,类中没有名称为id的属性,可是存在名称为类名称+id的属性DepartmentId,所以DepartmentId属性,将会被映射为自增加的主键。若是一个类中既没有id属性,也没有类名+id的属性,那么代码在运行时将会报错,由于EF没有找到符合要求的字段建立主键。this

2、关系约定

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

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; }
}

3、复杂类型约定

在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;
  }
}

4、自定义约定

当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,可进行以下配置:ip

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

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

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;
    }
}
相关文章
相关标签/搜索