EF Core – Table / Entity Splitting

參考

Docs – Advanced table mapping

 

Table Splitting

Table Splitting 指的是把多個 Entity 映射到同一個 Table。

When to use it?

假設我們有一個 Order Entity,Order 有很多信息:CustomerInfo, ShippingInfo, PaymentInfo, TotalAmount 等等等。

如果把所有信息都寫進 Order Entity 就會很亂。

比較好的管理方式是創建多幾個 Entity:CustomerInfo, ShippingInfo, PaymentInfo,把信息分門別類,各自保管。

然後 Order 和這些 Entity 做一對一關係,這樣管理就不亂了。

雖然管理是好了,但這同時也會導致數據庫多出幾個表,多表就要 join,join 就慢,結果管理好了性能卻差了😔。

要解決這個問題就需要用到 Table Splitting,它可以把多個 Entity 映射到同一個 Table,這樣就沒有 join 導致的性能問題了。

Step by step

我們看看具體怎麼做。

Entity

public class Order
{
  public int Id { get; set; }
  public CustomerInfo CustomerInfo { get; set; } = null!;
  public ShippingInfo ShippingInfo { get; set; } = null!;
  public decimal Amount { get; set; }
}

public class CustomerInfo
{
  public int Id { get; set; }
  public Order Order { get; set; } = null!;
  public string Name { get; set; } = "";
  public string Phone { get; set; } = "";
}

public class ShippingInfo
{
  public int Id { get; set; }
  public Order Order { get; set; } = null!;
  public string Line1 { get; set; } = "";
  public string Line2 { get; set; } = "";
  public string PostalCode { get; set; } = "";
  public string Country { get; set; } = "";
}

有 Order, CustomerInfo, ShippingInfo 3 個 Entity。

one-to-one relationships 

public class ApplicationDbContext() : DbContext()
{
  public DbSet<Order> Orders => Set<Order>();

  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
    modelBuilder.Entity<Order>(
      builder =>
      {
        builder.ToTable("Order");
        builder.Property(e => e.Amount).HasPrecision(19, 2);
        builder.HasOne(e => e.CustomerInfo).WithOne(e => e.Order).HasForeignKey<CustomerInfo>(e => e.Id);
        builder.HasOne(e => e.ShippingInfo).WithOne(e => e.Order).HasForeignKey<ShippingInfo>(e => e.Id);
      });

    modelBuilder.Entity<CustomerInfo>(
      builder =>
      {
        builder.ToTable("OrderCustomerInfo");
        builder.Property(e => e.Name).HasMaxLength(256);
        builder.Property(e => e.Phone).HasMaxLength(256);
      });

    modelBuilder.Entity<ShippingInfo>(
      builder =>
      {
        builder.ToTable("OrderShippingInfo");
        builder.Property(e => e.Line1).HasMaxLength(256);
        builder.Property(e => e.Line2).HasMaxLength(256);
        builder.Property(e => e.PostalCode).HasMaxLength(256);
        builder.Property(e => e.Country).HasMaxLength(256);
      });
  }
}

create Order

using var db = new ApplicationDbContext();
db.Orders.Add(new()
{
  Amount = 100,
  CustomerInfo = new()
  {
    Name = "Derrick",
    Phone = "+60 16-773 7062",
  },
  ShippingInfo = new()
  {
    Line1 = "22, Jalan Perak 7",
    Line2 = "Taman Mutiara Rini",
    Country = "Malaysia",
    PostalCode = "81300"
  }
});
db.SaveChanges();

效果

config Table Splitting

只要把 CustomerInfo 和 ShippingInfo 的 ToTable 改成 "Order" 就可以了

modelBuilder.Entity<CustomerInfo>(
  builder =>
  {
    // builder.ToTable("OrderCustomerInfo");
    builder.ToTable("Order");
    builder.Property(e => e.Name).HasMaxLength(256);
    builder.Property(e => e.Phone).HasMaxLength(256);
  });

modelBuilder.Entity<ShippingInfo>(
  builder =>
  {
    // builder.ToTable("OrderShippingInfo");
    builder.ToTable("Order");
    builder.Property(e => e.Line1).HasMaxLength(256);
    builder.Property(e => e.Line2).HasMaxLength(256);
    builder.Property(e => e.PostalCode).HasMaxLength(256);
    builder.Property(e => e.Country).HasMaxLength(256);
  });

效果

有幾個點需要注意:

  1. Column 撞名字

    Entity property name 是分開的,不可能會撞名字,但數據庫是放在一起的,column name 是有可能撞名字的。

    如果名字一樣,類型一樣,value 也一樣的話,那它會 share column,不然就會報錯。

    我們可以添加 column prefix 避免撞名字。

    builder.Property(e => e.Line1).HasMaxLength(256).HasColumnName("ShippingInfo_Line1");
  2. 記得 Include

    數據庫是同表,但是 Entity 是分開的,

    在 query 的時候記得要 Include related Entity。

    var order = db.Orders
      .Include(e => e.CustomerInfo)
      .Include(e => e.ShippingInfo)
      .First();

    由於是同表,Include 並不會 translate to left join command。

遇到 RowVersion

如果 Entity 需要 RowVersion 的話,其它所有 Entity 也都跟着需要,不然會遇到鬼。

其它 Entity 可以用 Shadow Property。 

 

Entity Splitting

Entity Splitting 指的是把一個 Entity 映射到多個 Table。

When to use it?

我也不清楚🤔,只知道這樣會導致 join 表,對性能不好。

Step by step

我們看看具體怎麼做。

Entity

public class Order
{
  public int Id { get; set; }
  public decimal Amount { get; set; }
  public string CustomerName { get; set; } = "";
  public string CustomerPhone { get; set; } = "";
}

CustomerName 和 CustomerPhone 需要映射到另一個 Table。

config Entity Splitting

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
  modelBuilder.Entity<Order>(
    builder =>
    {
      builder.ToTable("Order");
      builder.Property(e => e.Amount).HasPrecision(19, 2);
      builder.Property(e => e.CustomerName).HasMaxLength(256);
      builder.Property(e => e.CustomerPhone).HasMaxLength(256);
      builder.SplitToTable("OrderCustomerInfo", tableBuilder =>
      {
        tableBuilder.Property(e => e.CustomerName).HasColumnName("Name");
        tableBuilder.Property(e => e.CustomerPhone).HasColumnName("Phone");
      });
    });
}

關鍵就是 SplitToTable 方法

create Order

using var db = new ApplicationDbContext();
db.Orders.Add(new()
{
  Amount = 100,
  CustomerName = "Derrick",
  CustomerPhone = "+60 16-773 7062",
});
db.SaveChanges();

效果

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章