Map multiple entities to one table
Code first allows you to map multiple entities to the same table. The entity must follow the following rules:
- The object must be a one-to-one relationship.
- The object must share a public key.
Observe the following two entities:
public class Person { [Key] public int PersonId { get; set; } public int SocialSecurityNumber { get; set; } public string FirstName { get; set; } public string LastName { get; set; } 1541178949 public byte[] RowVersion { get; set; } public PersonPhoto Photo { get; set; } } public class PersonPhoto { [Key, ForeignKey("PhotoOf")] public int PersonId { get; set; } public byte[] Photo { get; set; } public string Caption { get; set; } public Person PhotoOf { get; set; } }
There is a one-to-one relationship between them, and the primary key data type is the same, so we can map them to the same table in the same database, just specify the table name:
[Table("People")] public class Person [Table("People")] public class PersonPhoto
PS: I mapped the data according to the above model, but an error will be reported when the database is generated:
The entity types "personphoto" and "person" cannot share the table "people" because they are not in the same type of hierarchy, or the matched primary keys between them do not have a valid one-to-one foreign key relationship.
Then I changed the model:
[Table("People")] public class Person { [Key, ForeignKey("Photo")] public int PersonId { get; set; } public int SocialSecurityNumber { get; set; } public string FirstName { get; set; } public string LastName { get; set; } 1541178949 public byte[] RowVersion { get; set; } public PersonPhoto Photo { get; set; } } [Table("People")] public class PersonPhoto { [Key, ForeignKey("PhotoOf")] public int PersonId { get; set; } public byte[] Photo { get; set; } public string Caption { get; set; } public Person PhotoOf { get; set; } }
The ing is successful. The table structure after the ing is successful.
However, when inserting data, the photo attribute in the person class cannot be blank; otherwise, an error is returned:
Invalid Data. The link is missing. Check stateentries to identify the source that violates the constraints.
Personphoto pH = new personphoto () {caption = "personal photo", photo = new byte [8]}; // The person p1 = new person () can be successfully inserted () {firstname = "Jhon", lastname = "Micheal", socialsecuritynumber = 123, photo = Ph}; // No value assigned to photo, insertion failure person P2 = new person () {firstname = "Jhon", lastname = "Micheal", socialsecuritynumber = 123 };
Map an object to multiple tables
Now let's look at it backwards. We can map an entity to multiple tables. This can be achieved using the map method of fluent API. Data annotations cannot be used because data annotations does not have the attribute subset concept.
We will map the people table to the tables A and B in the database.
public class PersonInfo { [Key] public int PersonId { get; set; } public int SocialSecurityNumber { get; set; } public string FirstName { get; set; } public string LastName { get; set; } 1541178949 public byte[] RowVersion { get; set; } public byte[] Photo { get; set; } public string Caption { get; set; } }
modelBuilder.Entity<PersonInfo>().Map(m => { m.ToTable("A"); m.Properties(p => p.FirstName); m.Properties(p => p.LastName); m.Properties(p => p.RowVersion); m.Properties(p => p.SocialSecurityNumber); }).Map(m => { m.ToTable("B"); m.Properties(p => p.Photo); m.Properties(p => p.Caption); });
Table structure generated
As you can see, code first automatically creates a primary key and a foreign key for the two tables. In the generated table, only the primary keys of the primary table (table A) are auto-incrementing.
Note: Do not skip any attributes when map is used! Otherwise, code first will automatically create the third table and save the missing attributes.
The above personinfo11 is the third table automatically created by code first, because the socialsecuritynumber attribute is missing during map.
Inherited class ing TPH (Table per hierarchy)
TPH: both the base class and the derived class are mapped to the same table. You can use the validation column to identify whether the base class is a child type. This is the table ing method used by the default code first rule.
public class Lodging { public int LodgingId { get; set; } [Required] [MaxLength(200)] [MinLength(10)] public string Name { get; set; } public string Owner { get; set; } public decimal MilesFromNearestAirport { get; set; } public int DestinationId { get; set; } } public class Resort : Lodging { public string Entertainment { get; set; } public string Activities { get; set; } }
Generated data structure
Therefore, all attributes are mapped to the same table, including entertainment and activities in the derived class, and a column is added: discriminator. EF uses this column to identify the data source. We can insert data to test:
var lodging = new Lodging { Name = "Rainy Day Motel", }; var resort = new Resort { Name = "Top Notch Resort and Spa", MilesFromNearestAirport = 30, Activities = "Spa, Hiking, Skiing, Ballooning", }; using (var context = new BreakAwayContext()) { context.Lodgings.Add(lodging); context.Lodgings.Add(resort); context.SaveChanges(); }
We can see that EF uses discriminator to distinguish lodging from resort.
Use fluent API to customize TPH delimiter Fields
If you think that the default discriminator column name is not intuitive enough, you can use the fluent API to configure the type and name of the discriminator column (Data annotations are not marked and can be used to customize TPH ).
modelBuilder.Entity<Lodging>().Map(m => { m.ToTable("Lodgings"); m.Requires("LodgingType").HasValue("Standard"); }).Map<Resort>(m => { m.Requires("LodgingType").HasValue("Resort"); });
The requires parameter is the name of the column you want. hasvalue is used to specify the value in the authentication column.
If the base class has only one derived class, we can also set the Data Type of the validation column to the bool value:
modelBuilder.Entity<Lodging>().Map(m => { m.ToTable("Lodging"); m.Requires("IsResort").HasValue(false); }) .Map<Resort>(m => { m.Requires("IsResort").HasValue(true); });TPT (Table per type)
TPH puts all the classes in the same table, while TPT stores the attributes of the base class in a separate table, and stores the additional attributes defined in the derived class in the other table, use the foreign key to connect to the master table.
We can display the name of the table generated by the specified derived class:
[Table("Resorts")] public class Resort : Lodging { public string Entertainment { get; set; } public string Activities { get; set; } }
We can see that two tables are generated. The "resort" ing table of the derived class in the model only includes its own two attributes: Entertainment, activities, the base-class ing table only contains its own attributes. In resorts, The lodgingid is the primary key and is also associated with the table lodgings as a foreign key.
TPC (Table per concrete type)
TPC is similar to TPT. The base class and the derived class are mapped to different tables. The difference is that the derived class also contains fields of the base class. TPC can only be configured using FLUENT APIs.
modelBuilder.Entity<Lodging>().Map(m => { m.ToTable("Lodgings"); }).Map<Resort>(m => { m.ToTable("Resorts"); m.MapInheritedProperties(); });
The resorts table mapped by the derived class resort in the generated database also contains the fields in lodging.
PS: I don't know why. In TPC mode, EF does not generate WORD growth for the table by default.
EF code first learning notes: Table ing