使用 Entity Framework Core 存储计算属性

use*_*383 10 c# entity-framework-core .net-core

我将尝试用一个过于简单的例子来说明我的问题:想象一下我有一个这样的域实体:

public class Box
{
    public int Id { get; set; }
    public int Height { get; set; }
    public int Width { get; set; }
    public int Depth { get; set; }
    public int Volume => Height * Width * Depth;
}
Run Code Online (Sandbox Code Playgroud)

我正在根据其他所有属性进行计算(体积)。现在说我想使用 Entity Framework Core 存储这个类。当我保留实体时,有什么方法可以让 EF 核心将 Volume 的当前值存储在它自己的列中?

这是我的核心问题。我不允许分享我的实际代码,但这里有一些关于我的真实世界实体的更深入的信息(我们称之为“盒子”):

  • My Box 有 18 个属性,其中 6 个是子实体的集合,定义了与这些实体的一对多关系,因此您可以将其称为聚合根。
  • 每个属性,包括子实体集合,都构成一个“总计”,我们称之为“体积”,它作为公共属性公开。每当更改属性或添加或删除子实体时,体积都会更改。
  • 在我看来,我需要列出每个 Box,但我只需要显示 5 个属性,其中一个是卷,这就是为什么每次我坚持一个实体时我都想将它存储在数据库字段中。

关于如何解决这个问题的一些想法:

  1. 完全水合每个盒子,并计算每个盒子的体积,然后将其投影到摘要中。这包括将每个框的所有七个表与子实体连接起来,为每个框计算并投影到简化的视图模型。对我来说,这似乎是获得一个我在坚持时知道的数字的大量开销。
  2. 为 Box 和所有子实体创建一个“持久性 DTO”,然后在存储时将 Volume 的结果映射到 dto 上的卷自动属性。这似乎也只是存储一个数字的大量开销,而且它似乎与 EF 的工作方式完全不一致。我只想坚持我的实体。
  3. 我可以在 Box 上使用适当的 OO 并为每个属性创建私有字段和适当的 setter,只要调用 setter,就会更新私有卷字段。这还包括在 Box 上编写用于操作所有子实体集合的方法,并将这些方法呈现为只读集合。这将导致每个 setter 上的大量开销私有字段和代码重复,但似乎比上述替代方案更“谨慎”。
  4. 我可以将 Volume 转换为 CalculateVolume() 方法并使用 fluent API 创建 Volume 属性,然后在上下文的 SaveChanges() 覆盖中填充该属性。但是覆盖 SaveChanges 是我不喜欢做的那种 EF 劲爆。
  5. 我可以做这样的事情:
public class Box
{
    public int Id { get; set; }
    public int Height { get; set; }
    public int Width { get; set; }
    public int Depth { get; set; }
    public int Volume {
        get => CalculateVolume(); 
        private set => _volume = value; }
    private int _volume;
    private int CalculateVolume() => Height * Width * Depth;
}
Run Code Online (Sandbox Code Playgroud)

这似乎做我想做的事,但出于某种原因感觉像是在作弊,并污染了我的域实体。此外,我不确定这是否在所有情况下都有效,但在撰写本文时,这是我的首选解决方案。

我更希望能够使用 fluent API 来配置它。我注意到 PropertyBuilder.ValueGeneratedOnAdd() 方法描述说“该值可能由客户端值生成器生成,也可能由数据库作为保存实体的一部分生成。”,但我找不到任何示例客户端价值生成。

欢迎任何和所有合理的反馈。

编辑:只是为了澄清:实际计算非常复杂,并使用来自 7 个不同表的值。还涉及每个属性的权重。开头的 Box 示例过于简化,仅用于说明目的。我只想说,我需要在我的代码中保留计算。我只想存储结果。

bhm*_*ler 7

您可以使用 Fluent api 在 sql server 上计算它

class MyContext : DbContext
{
    public DbSet<Box> Box { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Box>()
            .Property(p => p.Volume)
            .HasComputedColumnSql("[Height] * [Width] * [Depth]");
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢您的回答。我不能这样做的原因是实际计算非常复杂并且使用来自 7 个不同表的值。还有涉及的每个属性的权重。可以说,我需要将计算保留在我的代码中。我只是想存储答案。 (4认同)

McK*_*bue 7

以下是我从 EF 人员那里得到的针对同一问题的回复:

从 EF Core 3.0 开始,EF 会在可能的情况下直接读取和写入支持字段。EF 可以配置为使用该属性,此时将从该属性读取计算值并因此写入数据库

 protected override void OnModelCreating(ModelBuilder modelBuilder)
 {
     modelBuilder
         .Entity<Box>()
         .Property(e => e.Volume)
         .UsePropertyAccessMode(PropertyAccessMode.Property);
 }
Run Code Online (Sandbox Code Playgroud)

或者

modelBuilder.UsePropertyAccessMode(PropertyAccessMode.PreferFieldDuringConstruction);
Run Code Online (Sandbox Code Playgroud)

阅读更多:https : //docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/break-changes#backing-fields-are-used-by-default

  • 只是为了澄清,这不适用于根本没有设置器的“计算”属性,就像在您的初始 Box 实体中一样:“public int Volume =&gt; Height * Width * Depth;” (3认同)