11

Closed

Code First: Allow subclasses to map field to same database column with TPH inheritance

description

When using TPH inheritance, Code First requires every field defined in a subclass to be mapped to a unique database column. However, there are scenarios where it would be simpler for subclasses to map fields to the same column. For example:

public abstract class Vehicle { ... }

public class Car : Vehicle { ... }

public class Truck : Vehicle { ... }

public class Trike : Vehicle { public string WheelOrientation { get; set; } ... }

public class CanAmSpyder : Vehicle { public string WheelOrientation { get; set; } ... }

Given the above example with Code First TPH, the database table "Vehicle" would require two columns for wheel orientation: "TrikeWheelOrientation" and "CanAmSpyderWheelOrientation". But having a single "WheelOrientation" column that both the Trike subclass and CanAmSpyder subclass can map to seems cleaner.
Closed Apr 12, 2013 at 7:33 PM by mgirgin
Verified

comments

RoMiller wrote Oct 16, 2012 at 5:54 PM

Updating title to reflect that this isn't specific to Code First and applies to the designer too.

This is a restriction of the View Generation functionality in EF. It's something we'll definitely consider supporting but we aren't going to tackle it in EF6 (the View Gen code is extremely complicated).

banto7 wrote Oct 16, 2012 at 11:00 PM

I have previously been able to accomplish this with Model First in EF4.1.. just not with Code First

The following discussion seems to indicate that this is a Code First specific problem.
http://social.msdn.microsoft.com/Forums/en-US/adonetefx/thread/dda96990-130c-47e4-b961-c1e775e283ad

RoMiller wrote Oct 18, 2012 at 8:15 PM

@banto7 - Thank you for following up, I've readjusted the title back to Code First.

We are marking this for a future release as our EF6 release is very full. We would accept a contribution to fix it, but it's complicated code so it won't be an easy fix.

divega wrote Oct 21, 2012 at 3:49 PM

Additional information: Code First produces correct mapping for this model, but tries to introduce two columns with the same name in the storage model (which explains the exception).

Repro:
public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Student : Person
{
    public string Career { get; set; }
}

public class Teacher : Person
{
    public string Department { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var modelBuilder = new DbModelBuilder();
        modelBuilder.Entity<Person>();
        modelBuilder.Entity<Teacher>().Property(p => p.Department).HasColumnName("Data");
        modelBuilder.Entity<Student>().Property(p => p.Career).HasColumnName("Data");
        EdmxWriter.WriteEdmx(modelBuilder.Build(
            new DbProviderInfo("System.Data.SqlClient", "2008")), 
            XmlTextWriter.Create(Console.Out, new XmlWriterSettings() { Indent = true }));
    }
}
Output:
…
<StorageModels>
  <Schema Namespace="CodeFirstDatabaseSchema" Provider="System.Data.SqlClient" ProviderManifestToken="2008" Alias="Self"  
           xmlns="http://schemas.microsoft.com/ado/2009/11/edm/ssdl">
    <EntityType Name="Person">
      <Key>
        <PropertyRef Name="Id" />
      </Key>
      <Property Name="Id" Type="int" StoreGeneratedPattern="Identity" Nullable="false" />
      <Property Name="Name" Type="nvarchar(max)" Nullable="true" />
      <Property Name="Data" Type="nvarchar(max)" Nullable="true" />
      <Property Name="Data" Type="nvarchar(max)" Nullable="true" />
      <Property Name="Discriminator" Type="nvarchar" MaxLength="128" Nullable="false" />
    </EntityType>
    <EntityContainer Name="CodeFirstDatabase">
      <EntitySet Name="Person" EntityType="Self.Person" Schema="dbo" Table="People" />
    </EntityContainer>
  </Schema>
</StorageModels>
…

gichamba wrote Mar 24, 2013 at 9:05 AM

+100 this is a big problem when using TPH. I hope it gets fixed yesterday.

ajcvickers wrote Apr 1, 2013 at 8:05 PM

Fixed in 60cbc8e1660c

ShareAndEnjoy (Code First: Allow subclasses to map property to same database column with TPH)

EDM allows properties in different subclasses of a TPH hierarchy to map to the same database column and thereby "share" that column. Code First was building this mapping mostly correctly except that it was not fixing up the mappings to all point to the same column. This change fixes that.

Note that since the column can now be configured from multiple properties all configurations of the column must match or an exception is thrown. Configuration can be applied from any one property or from multiple properties so long as the configuration does not conflict.

gichamba wrote Apr 2, 2013 at 3:57 AM

Thanks Arthur Vickers and team for fixing this issue. I was in the middle of designing some complex entities and this bug was causing my tables to have many unnecessary columns with ugly names. Trying to design around this by adding a level of inheritance on my classes so as to share the common properties was making making my classes' inheritance hierarchy very messy.

I got information from @ajcvickers on twitter. The fact that it came on April 1 had me worried I could be getting pranked. Had to check to make sure it was true.

Again, thanks for fixing this issue.

wiruc wrote Oct 3, 2013 at 3:19 PM

The fix doesn't work for relationships. For below example the EF makes 2 foreign keys in table "Person" : Company_Id and Company_Id1. If we try to set the same name "CompanyId" for foreign keys through .MapKey in Fluent API, the EF will throw exception like "CompanyId: Name: Each property name in a type must be unique. Property name 'CompanyId' is already defined."
public class Company 
{
  public int Id {get; set;}
}

public class Person 
{
  public int Id {get; set;}
}

public class Manager : Person
{
  public virtual Company {get; set;}
}

public class Director : Person
{
  public virtual Company {get; set;}
}

ajcvickers wrote Oct 7, 2013 at 7:53 PM

@wiruc Have you tried adding properties for the FKs to your entities? This would allow EF to treat these relationships as "FK associations" rather than "independent associations" and should also allow the FKs to be mapped like other properties such that you don't get the error you are seeing.