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;
    }
}
相關文章
相關標籤/搜索