【问题标题】:EF Core 2.0/2.1 - How to efficiently handle large, infrequently accessed columns?EF Core 2.0/2.1 - 如何有效地处理大型、不常访问的列?
【发布时间】:2018-09-03 08:32:15
【问题描述】:

我有一张如下表:

CREATE TABLE MyTable
(
  ID INT NOT NULL PRIMARY KEY,
  NAME VARCHAR(50) NOT NULL, 
  LARGEBLOB VARBINARY(MAX) NULL
)

实体定义为:

public class Entity
{
  public int Id {get;set;}
  public string Name {get;set;}
  public virtual byte[] LargeBlob {get;set;}
}

我 99% 的用例只涉及显示 ID 和 NAME。

1% 的时间我需要 LARGEBLOB。

有什么方法可以将 LargeBlob 标记为延迟加载以避免 大量浪费的数据传输?或者,有没有其他方法 达到同样的结果?

我尝试拆分为 2 个具有 1->[0|1] 关系的表,如下所示:

CREATE TABLE MyTable
(
  ID INT NOT NULL PRIMARY KEY,
  NAME VARCHAR(50) NOT NULL, 
  LARGEBLOBID INT NULL
)

CREATE TABLE MySubTable
(
  ID INT PRIMARY KEY,
  LARGEBLOB VARBINARY(MAX) NOT NULL
)

实体

public class Entity
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual LargeBlob LargeBlob { get; set; }
}

public class LargeBlob
{
    public int Id { get; set; }
    public virtual byte[] Blob { get; set; }
}

就延迟加载而言,这确实有效,但我尝试了各种反向关系/外键标签、HasOne、OwnsOne、OnDelete(Cascade) 的各种组合,但我无法实现我想要的想要达到。回顾一下,那就是:

  1. 仅当 LargeBlob 属性实际取消引用时才会加载 Blob。
  2. 如果 entity.LargeBlob 属性设置为新的 LargeBlob,则(现在“孤立”)旧的 LargeBlob 将从数据库中删除。
  3. 如果实体被删除,相关的大 blob 也会被删除。

快速更新:版本 &c

注意:我正在使用 VS 2017 15.6.2、.net core 2.0 和 EF core 2.1(至少有可能进行一些延迟加载)。 Nuget 包:

<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="2.1.0-preview1-final" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.1.0-preview1-final" PrivateAssets="All" />

【问题讨论】:

  • 使用 .net 核心,您所做的是一个很好的策略。删除级联的那个应该可以解决问题。什么是你不能做到的? (目前还没有延迟加载之类的东西,但是 2.1 会引入一些它docs.microsoft.com/en-us/ef/core/querying/…
  • 1.我能够实现。 3 我实际上并没有测试,但我很确定会工作。 2是我没有工作的那个。当我设置 Entity.LargeBlob = new LargeBlob() 时,旧的大 blob 留在了表中。关于 2.1 我其实在使用它,会编辑问题。另外会尝试@Ivan Stoev 的回答...
  • 是的,我专注于延迟加载,忘记了表拆分选项。他的回答很好,没什么可补充的:)

标签: entity-framework entity-framework-core ef-core-2.0


【解决方案1】:

我尝试拆分为 2 个具有 1->[0|1] 关系的表,如下所示

但是通过将 FK 放在 Entity 中,您实际上做了相反的 - [0|1]->1 关系。

要获得所需的关系,FK 必须位于 LargeBlog。它可以是一个单独的属性(列),但最合适的是将Id 属性用作PK 和FK(所谓的共享PK 关联)。您可以使用以下流畅的配置来做到这一点:

modelBuilder.Entity<Entity>()
    .HasOne(e => e.LargeBlob)
    .WithOne()
    .HasForeignKey<LargeBlob>(e => e.Id);

一旦你这样做了,因为这样做的全部目的是为了获得单独的可控(急切、显式或延迟可用)加载行为,可以看出单独的表并不是真正需要的——“实体”包含可以使用table splitting 将 blob 数据嵌入到同一个表中,只需在上述配置中添加以下内容即可:

modelBuilder.Entity<Entity>().ToTable("MyTable");
modelBuilder.Entity<LargeBlob>().ToTable("MyTable");

请注意,虽然最合乎逻辑的选择似乎是拥有类型,但不幸的是,当前拥有的类型总是被加载(类似于 EF6 复杂类型),因此它们不能用于实现可控的加载行为。

【讨论】:

    【解决方案2】:

    您应该只选择您需要节省带宽的列:

    var entity = await dbContext.Entities
      .Where(...)
      .Select(e => new
      {
        Id = e.Id,
        Name = e.Name,
        LargeBlob = null,
      })
      .FirstOrDefaultAsync();
    

    当您真正需要 LargeBlob 列时,手动加载它

    entity.LargeBlob = await dbContext.Entities
      .Where(e => e.Id == entity.Id)
      .Select(e => e.LargeBlob)
      .SingleOrDefaultAsync();
    

    您可以在不加载整个实体的情况下删除实体,只需 Id(以及并发令牌,如果存在于实体上)就足够了

    var entity = new Entity { Id = removeEntityId };
    dbContext.Entities.Remove(entity);
    await dbContext.SaveChangesAsync();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-04
      • 2018-12-17
      • 1970-01-01
      • 2018-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-21
      相关资源
      最近更新 更多