Practical .NET
Making Complex Types Useful with Entity Framework 6 Custom Configurations
Complex Types let you reuse structures in your database design. But unless you've been very lucky around the names in your database, you probably couldn't use Complex Types -- until Entity Framework 6, that is.
I worked on an application for one of my clients that involved storing address information in a dozen tables in the database, then repeating the standard address structure (city, street, province-or-state, country) over and over again. Address information is the standard example of where Entity Framework (EF) should let you use Complex Types to manage that address structure as a single unit. With a Complex Type, if you need to add an apartment number property to your address structure, you would just update the Complex Type and all of your entities would pick up the change (you'd also have to add the necessary ApartmentNumber column to your tables, of course). From an object-oriented point of view, Complex Types are the right thing to do.
Unfortunately, because of the conventions of EF, unless you were creating a brand new table or had been very lucky with your naming conventions in the past, you couldn't use Complex Types. So no matter what the object-oriented benefits you'd get from using Complex Types, the reality of your legacy databases wouldn't let you grab those benefits -- until EF6 came along with its support for custom conventions and configurations.
Creating Complex Type
EF Complex Types let you define repeating structures in your database's tables as classes, then use those classes in your entities as nested classes (see my tip on the usefulness of nested classes outside of EF). For address information, you would begin by creating a class that defines the structure of your address information. EF has rules for discovering Complex Types, but adding the ComplexType attribute to your Address class reduces the chances of EF guessing wrong:
[ComplexType]
public class Address
{
public string City {get; set;}
public string Street { get; set; }
public string StateOrProvince { get; set; }
public string Country { get; set; }
}
There are some restrictions around Complex Types: they can't have a primary key (you can describe them as value objects in some object classification schemes), inherit from other classes, contain navigation properties, be collections, and (most important) you can't use them as standalone entities. But what you can do is use your Complex Type as a nested class in other entities. Here's a Customer entity that uses my Address Complex Type to define two different addresses:
public partial class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Address ShippingAddress { get; set; }
public Address BillingAddress { get; set; }
}
The Address class is still a class, and like any other class, it needs to be instantiated. If no Address data is provided for the class, EF won't instantiate the Address class, which could result in it throwing null value errors. To prevent that, you should add a constructor to your entity class holding the Complex Type and initialize the properties that use the Complex Type. For my Customer class, that code would look like this:
public partial class Customer
{
public Customer()
{
this.BillingAddress = new Address();
this.ShippingAddress = new Address();
}
public int Id { get; set; }
Configuring Entity Framework to Your Database
When you go to use Complex Type with an existing database, you'll probably find your existing database won't support it. EF naming conventions mean the database is going to expect the Customer table corresponding to my Customer entity to have columns with the names ShippingAddress_City, ShippingAddress_Street, BillingAddress_City, BillingAddress_Street and so on. If, for instance, my Customer table has columns called ShipAddress and BillToStreetAddress, I'll be out of luck.
That is, until the EF6 custom conventions and configurations come along to give you more control over how your EF model ties to your database. You just need a little bit of fluent code in your DbContext OnModelCreating method to resolve this problem.
The first step is to pick the class your convention will apply to, using the ModelBuilder Types collection. In my example, I want to control my Customer entity class, so I specify it in the Types collection:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Types<Customer>()
The next step is to specify what you want to configure on that type by calling the Configure method. The Configure method accepts a lambda expression and passes that expression a ConventionTypeConfiguration object. You use the ConventionTypeConfiguration object to tell EF what you want to configure. I want to override the configuration for one of my entity's properties, so I use the Property method on the ConventionTypeConfiguration object:
modelBuilder.Types<Customer>()
.Configure(ctc => ctc.Property( ...
The Property method also accepts a lambda expression that allows you to specify which property on the entity you want to configure (this lambda expression is passed the entity's description). I want to configure the State property within the Customer ShippingAddress, so I write this code:
modelBuilder.Types<Customer>()
.Configure(ctc => ctc.Property(cust => cust.ShippingAddress.Street) ...
Finally, you supply the configuration for the property. In this case, I tell EF what the column's name really is, using the HasColumnName method:
modelBuilder.Types<Customer>()
.Configure(ctc => ctc.Property(cust => cust.ShippingAddress.Street).HasColumnName("ShipAddress"));
I'll need another configuration to handle my BillToStreetAddress:
modelBuilder.Types<Customer>()
.Configure(ctc => ctc.Property(cust => cust.BillingAddress.Street).HasColumnName("BillToStreetAddress"));
This new ability to configure EF frees me to design my entities in an object-oriented way that solves my business problems. Once I've got the objects I want, I can do what's necessary to tie those well-designed entity objects to my database. And that's the right thing to do.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.