【问题标题】:Using Entity Framework constructors in derived classes在派生类中使用实体框架构造函数
【发布时间】:2014-12-30 23:25:25
【问题描述】:

我在 TPH 布局中使用 Entity Framework Code First。我的 DbSet 引用基(抽象)类,然后我对派生类进行操作。 EF 注意到这一点并自动在表中生成一个 Discriminator 字段。然后,我想根据子类以特定方式初始化属性。我目前在子类的构造函数中这样做。

这一切都非常适合保存到数据库。但是,当检索数据时,子构造函数在数据加载之前运行,导致构造函数逻辑和数据库结果都显示在目标集合中。

public abstract class Indicator
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Annotation> Annotations { get; set; }

    protected Indicator()
    {
        Annotations = new List<Annotation>();
    }
}

public class MyIndicator : Indicator
{
    public MyIndicator()
    {
        Annotations = new List<Annotation>
        {
            new MyAnnotation() { Name = "First" },
            new MyAnnotation() { Name = "Second" },
            new MyAnnotation() { Name = "Third" }
        };
    }
}

public abstract class Annotation
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class MyAnnotation : Annotation {}

在数据库中填充一条记录后,尝试检索它会导致构造函数 Annotations 和数据库中的 Annotation 对象都显示在集合中。

Indicator newI = new MyIndicator { Name = "Custom collection" };
int count = 0;
var names = new List<string> { "Test 1", "Test 2", "Test 3" };
foreach (var a in newI.Annotations)
{ // overwite properties from "default" collection
    a.Name = names[count++];
}
context.Indicators.Add(newI);
context.SaveChanges();

跑步:var i = context.Indicators.Single(x =&gt; x.Id == 1);

返回:

Annotations = {
    [0] { Id = 0, Name = "First" } // constructor
    [1] { Id = 0, Name = "Second" }
    [2] { Id = 0, Name = "Third" }
    [3] { Id = 1, Name = "Test 1" } // database entries
    [4] { Id = 2, Name = "Test 2" }
    [5] { Id = 3, Name = "Test 3" }
}

这种行为看起来很奇怪,我唯一能想到的是派生类的构造函数被延迟加载集合所需的 EF 代理以某种方式调用。

这是我正在处理的问题域的一个非常简化的版本。最终目标是在基于派生指标类的指标集合内初始化不同类型(派生类)的注释。然后,工厂或服务将使用 Annotation 派生类中的这些“默认”值填充模型的其他属性,以从应用程序外部选择数据。

更新/更多信息

我的意图是将所有内容存储在数据库中。但是,集合中派生类型的类型和数量对于派生类来说是唯一的。

示例: 假设我有一个 Car 抽象类和一些派生类:Sedan、Truck、Van。 Car 抽象类也有一个 Collection of Parts;另一个具有自己的包含“定义”的派生类集的抽象类。当我将 Truck 传递给我的工厂方法时,我希望根据类型使用默认值操作一辆新车:Truck。

class abstract Car
{
    ICollection<Part> Parts { get; set; }
    decimal Cost { get { return Parts.Sum(c => c.Cost) ?? 0; } }
}
class Truck : Car {
    public Truck() {
        Parts = new ICollection<Parts> {
            new SteeringWheel(),
            new Flatbed()
        }
    }
}

class abstract Part {
    string Material { get; set; }
    decimal Cost { get; set; }
}
class SteeringWheel : Part {
    public SteeringWheel() { Material = "Polyurethane"; }
}
class Flatbed : Part {
    public Part() { Material = "Steel"; }
}

当我在工厂新建一辆卡车时,我的零件系列包含一个由钢制成的平板和一个由聚氨酯制成的 SteeringWheel。然后我可以遍历新集合以查询外部源并返回每个项目的成本以填充其成本属性。轿车可能有 SteeringWheel 和 SunRoof,而厢式货车可能有 SteeringWheel、CargoDoor 和 TowHitch。

如果不使用特定的派生类型初始化零件列表,我将不得不将所有这些信息编码到工厂本身中。随着层的增加,这变得相当笨拙。 (想象一辆车,它有一个 Part 集合,一个 Material 集合,又包含 Quantity 和 Dimension 集合。)

【问题讨论】:

  • 我认为您必须选择:持久化 硬编码列表。为什么不将all 列表项存储在数据库中?我不明白你什么时候需要哪些物品,但混合它们似乎是一个非常糟糕的主意。例如:您还必须防止硬编码的项目被持久化。
  • 我更新了这篇文章,希望能澄清一点。

标签: asp.net linq entity-framework


【解决方案1】:

可能是因为 EF 无法区分它添加的项目与您添加的项目。当它添加每个项目时,它可能首先确保 List != null 并且不创建新列表。这允许在派生列表时具有一定的灵活性/控制。如果你想使用一些专门的列表,但 EF 总是在添加项目之前开始创建一个新的空列表,那么它会吹掉你尝试使用一些派生列表或实现 IList/ICollection 的不同类型的尝试。我知道这不是您想要做的,而是解释为什么他们可能选择不从清除您的列表开始。

有点小技巧,但是您可以创建一个带有虚假参数的构造函数重载,因为 EF 将使用默认构造函数。在默认构造函数中不进行默认初始化。当您要生成新实体时显式调用的重载,它将执行默认初始化。如果您需要在继承层次结构上执行此操作,那么该构造函数也可以使用 base(someParam) 语法来调用父类。

【讨论】:

  • 我已经考虑过了。但是,这使我无法使用泛型工厂,因为泛型类型必须使用默认构造函数。不能把你的蛋糕也吃掉吧?
  • 这是一个不错的解决方案。记住调用 InitializeDefaults 有点烦人,但肯定比伪造的构造函数更可取。
  • @MicahLoffer 您的工厂可以抽象出该调用。 PersonFactory.Create() { var person = new PersonEntity(); base.Create(person); return person; } where abstract virtual BaseFactory.Create(IDefaultable newEntity) { newEntity.InitializeDefaults(); } 买不起,但记住总是调用基本方法会更容易一些,因为这是一种非常常见的做法。也就是说,如果我站在你的立场上,我可能最终会再重构几次,然后我才会对结果感到满意并满意它并没有不必要的复杂。
猜你喜欢
  • 1970-01-01
  • 2021-11-11
  • 2016-07-19
  • 2018-07-21
  • 2015-11-10
  • 2020-02-07
  • 2012-11-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多