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

效果

 

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