在派生类中使用实体框架构造函数

时间:2014-12-30 23:25:26

标签: asp.net linq entity-framework

我正在使用带有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 => 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代理调用。

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

更新/更多信息

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

实施例: 假设我有一个Car抽象类和一些派生类:Sedan,Truck,Van。 Car抽象类还有一个零件集合;另一个抽象类,它有自己的一组包含“定义”的派生类。当我将卡车运到我的工厂方法时,我想根据类型使用新车进行默认操作:卡车。

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。然后,我可以遍历新集合以查询外部源并返回每个项目的成本以填充其Cost属性。轿车可能有SteeringWheel和SunRoof,以及Van a SteeringWheel,CargoDoor和TowHitch。

如果不使用特定的派生类型初始化Parts列表,我必须将所有这些信息编码到工厂本身。随着层级的增加,这变得非常快。 (想象一辆汽车有一个零件集合,其中包含一系列具有数量和尺寸集合的材料。)

1 个答案:

答案 0 :(得分:1)

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

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