【问题标题】:Store computed property with Entity Framework Core使用 Entity Framework Core 存储计算属性
【发布时间】:2019-09-26 12:30:49
【问题描述】:

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

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;
}

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

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

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

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

  1. 完全水合每个盒子,计算每个盒子的体积,然后将其投影到一个摘要中。这包括将每个盒子的所有七个表与子实体连接起来,为每个盒子进行计算并投影到简化的视图模型。对我来说,要获得一个我在坚持时就知道的数字,这似乎是很多开销。
  2. 为 Box 和所有子实体创建一个“持久性 DTO”,然后在存储时将 Volume 的结果映射到 dto 上的卷自动属性。仅存储一个数字似乎也有很多开销,而且似乎与 EF 的工作方式完全不一致。我只想坚持我的实体。
  3. 我可以在 Box 上进行正确的 OO 并为每个属性创建私有字段和正确的设置器,以便在调用设置器时更新私有卷字段。这还包括在 Box 上编写用于操作所有子实体集合的方法,并将它们呈现为只读集合。这会导致在每个 setter 上产生大量开销的私有字段和代码重复,但看起来确实比上述替代方案更“谨慎”。
  4. 我可以将 Volume 转换为 CalculateVolume() 方法并使用 fluent API 创建一个 Volume-property,然后在上下文的 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;
}

这似乎确实符合我的要求,但由于某种原因感觉像是在作弊,并污染了我的域实体。此外,我不确定这实际上是否适用于所有情况,但这是我在撰写本文时首选的解决方案。

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

欢迎任何合理的反馈。

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

【问题讨论】:

    标签: c# .net-core entity-framework-core


    【解决方案1】:

    以下是我从 EF 人那里得到的关于同样问题的回复:

    Starting with EF Core 3.0, EF reads and writes directly to the backing field, where possible. EF can be configured to use the property instead, at which point the computed value will be read from the property and hence written to the database:

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

    modelBuilder.UsePropertyAccessMode(PropertyAccessMode.PreferFieldDuringConstruction);
    

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

    【讨论】:

      【解决方案2】:

      你可以使用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]");
          }
      }
      

      【讨论】:

      • 感谢您的回答。我不能这样做的原因是实际计算非常复杂,并且使用来自 7 个不同表的值。还对所涉及的每个属性进行加权。可以说,我需要将计算保留在我的代码中。我只想存储答案。
      猜你喜欢
      • 2018-08-12
      • 1970-01-01
      • 1970-01-01
      • 2020-04-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-02
      • 1970-01-01
      相关资源
      最近更新 更多