Implementing Complex Types in Entity Framework Code First
In Entity Framwork (EF) Code First development, a Complex Type is a class that serves as a container for properties but lacks its own identity (i.e., it does not have a primary key). Unlike standard entity types, complex types are not mapped to their own database tables. Instead, their properties are flattened and integrated directly into the tables of the entities that host them.
Defining a Complex Type
When Code First encounters a class without a key property that is referenced by an entity, it defaults to treating that class as a complex type. The properties of the complex type are merged into the host entity's table schema.
Consider a scenario where we have a User entity and a ContactInfo class. We want to store contact details within the user's record without creating a separate database table for them.
public class ContactInfo
{
public string Email { get; set; }
public string PhoneNumber { get; set; }
}
public class User
{
[Key]
public int UserId { get; set; }
public string Username { get; set; }
public ContactInfo ContactDetails { get; set; }
}
By omitting a [Key] attribute in ContactInfo, EF generates a User table containing columns such as UserId, Username, ContactDetails_Email, and ContactDetails_PhoneNumber.
Differentiating from Entities
If you add a [Key] property to ContactInfo, Entity Framework will treat it as a distinct entity. This forces the creation of a separate table in the database and establishes a relational link (usually a Foreign Key) between User and ContactInfo instead of flattening the properties.
Handling Nested Complex Types
Complexity arises when a complex type contains other objects. For instance, if ContactInfo includes a GPSCoordinates class, EF might fail to infer the mapping automatically because it cannot determine if the nested class should be treated as another entity or an extension of the complex type.
To explicitly guide the EF mapping engine, you should use the [ComplexType] attribute:
[ComplexType]
public class GPSCoordinates
{
public double Lat { get; set; }
public double Long { get; set; }
}
[ComplexType]
public class ContactInfo
{
public string Email { get; set; }
public GPSCoordinates OfficeLocation { get; set; }
}
By decorating both classes with [ComplexType], you inform Entity Framework that these objects have no independent lifecycle or identity. Consequently, all properties—including those nested within GPSCoordinates—will be mapped as columns directly into the User table. This approach keeps your database schema clean and avoids unnecessary join operations for data that effectively functions as a single unit of enformation.