Micah Loffer
Micah Loffer

Reputation: 45

Using Entity Framework constructors in derived classes

I'm using Entity Framework Code First with a TPH layout. My DbSets reference the base (abstract) class, and then I operate on derived classes. EF picks up on this and automatically generates a Discriminator field in the table. I then want to initialize properties in specific ways depending on the child class. I currently do this in the constructor of the child class.

This all works great for saving to the database. However, when retrieving data the child constructors are running before data is loaded, resulting in both the constructor logic and database results showing up in the target collection.

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

After populating a record in the database, attempting to retrieve it results in both the constructor Annotations and the Annotation objects from the database showing up in the collection.

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();

Running: var i = context.Indicators.Single(x => x.Id == 1);

Returns:

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

This behavior seems bizarre and the only thing I can figure is that the constructor for the derived class is somehow called by the EF proxy required for lazy loading of the collection.

This is a very simplified version of the problem domain I'm working on. The ultimate goal is to have different types (derived classes) of Annotations initialized within the collection on Indicator based on the derived Indicator class. A factory or service would then fill in additional properties on the model using these "default" values within the Annotation derived class to select data from outside the application.

Update/Further Info

My intent is to store everything in the database. However, the type and number of derived types in the collection are unique to the derived class.

Example: Let's say I have a Car abstract class and a few derived classes: Sedan, Truck, Van. The Car abstract class also has a Collection of Parts; another abstract class with its own set of derived classes that contain "definitions". When I pass a Truck into my factory method, I would like to have a new Car to operate on with defaults according to the type: 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"; }
}

When I new up a Truck in my factory, my Parts Collection contains a Flatbed made of Steel and a SteeringWheel made of Polyurethane. I can then iterate over the new collection to query an external source and return the cost of each item to populate its Cost property. The Sedan may have a SteeringWheel and a SunRoof, and the Van a SteeringWheel, a CargoDoor and a TowHitch.

Without initializing the Parts list with specific derived types, I would have to code all of this information into the factory itself. This becomes unweildy rather quickly as tiers are added. (Imagine a Car that has a collection of Part that has a collection of Material that has collections of Quantity and Dimension.)

Upvotes: 2

Views: 557

Answers (1)

AaronLS
AaronLS

Reputation: 38365

Probably because EF can't differentiate between items it added vs those you added. As it adds each item, it probably starts by just making sure the List != null and doesn't create a new List. This allows for some flexibility/control in deriving the List. If you wanted to use some specialized List, but EF always began be create a new empty list before adding items, then it'd blow away your attempt to use some derived List or different type implementing IList/ICollection. I realize that's not what you're trying to do, but explaining why they might have chosen not to begin by clearing your list.

A bit of a hack, but you can create a constructor overload that takes a bogus parameter, since EF will use the default constructor. Do no default initialization in the default constructor. The overload you call explicitly when you want to generate a new entity and it will do the default initialization. If you need to do this up the inheritance hierarchy, then that constructor could use the base(someParam) syntax to call up to the parent class as well.

Upvotes: 1

Related Questions