Associations in EF Code First CTP5: Part 1 – Complex Types
Last week the CTP5 build of the new Entity Framework Code First has been released by data team at Microsoft. Entity Framework Code-First provides a pretty powerful code-centric way to work with the databases. When it comes to associations, it brings ultimate flexibility. I’m a big fan of the EF Code First approach and I am planning to explain association mapping with code first in a series of blog posts and this one is dedicated to Complex Types.
A Note For Those Who are New to Entity Framework and Code-FirstIf you choose to learn EF you've chosen well. If you choose to learn EF with Code First you've done even better. To get started, you can find a great walkthrough by Scott Guthrie here and another one by ADO.NET team here. In this post, I assume you already setup your machine to do Code First development and also that you are familiar with Code First fundamentals and basic concepts.In order to build a solid foundation for our discussion, we will start by learning about some of the core concepts around the relationship mapping. What is Mapping?Mapping is the act of determining how objects and their relationships are persisted in permanent data storage, in our case, relational databases.What is Relationship Mapping?A mapping that describes how to persist a relationship (association, aggregation, or composition) between two or more objects.Types of RelationshipsThere are two categories of object relationships that we need to be concerned with when mapping associations. The first category is based on multiplicity and it includes three types:
How Object Relationships are Implemented in POCO Object Models?When the multiplicity is one (e.g. 0..1 or 1) the relationship is implemented by defining a navigation property that reference the other object (e.g. an Address property on User class). When the multiplicity is many (e.g. 0..*, 1..*) the relationship is implemented via an ICollection of the type of other object.How Relational Database Relationships are Implemented?Relationships in relational databases are maintained through the use of Foreign Keys. A foreign key is a data attribute(s) that appears in one table and must be the primary key or other candidate key in another table. With a one-to-one relationship the foreign key needs to be implemented by one of the tables. To implement a one-to-many relationship we implement a foreign key from the “one table” to the “many table”. We could also choose to implement a one-to-many relationship via an associative table (aka Join table), effectively making it a many-to-many relationship.Introducing the ModelNow, let's review the model that we are going to use in order to implement Complex Type with Code First. It's a simple object model which consist of two classes: User and Address. Each user could have one billing address. The Address information of a User is modeled as a separate class as you can see in the UML model below: |
In object-modeling terms, this association is a kind of aggregation—a part-of relationship. Aggregation is a strong form of association; it has some additional semantics with regard to the lifecycle of objects. In this case, we have an even stronger form, composition, where the lifecycle of the part is fully dependent upon the lifecycle of the whole.
Fine-grained Domain ModelsThe motivation behind this design was to achieve Fine-grained domain models. In crude terms, fine-grained means “more classes than tables”. For example, a user may have both a billing address and a home address. In the database, you may have a single User table with the columns BillingStreet, BillingCity, and BillingPostalCode along with HomeStreet, HomeCity, and HomePostalCode. There are good reasons to use this somewhat denormalized relational model (performance, for one). In our object model, we can use the same approach, representing the two addresses as six string-valued properties of the User class. But it’s much better to model this using an Address class, where User has the BillingAddress and HomeAddress properties. This object model achieves improved cohesion and greater code reuse and is more understandable.Complex Types: Splitting a Table Across Multiple TypesBack to our model, there is no difference between this composition and other weaker styles of association when it comes to the actual C# implementation. But in the context of ORM, there is a big difference: A composed class is often a candidate Complex Type. But C# has no concept of composition—a class or property can’t be marked as a composition. The only difference is the object identifier: a complex type has no individual identity (i.e. no AddressId defined on Address class) which make sense because when it comes to the database everything is going to be saved into one single table.How to implement a Complex Type with Code FirstCode First has a concept of Complex Type Discovery that works based on a set of Conventions. The convention is that if Code First discovers a class where a primary key cannot be inferred, and no primary key is registered through Data Annotations or the fluent API, then the type will be automatically registered as a complex type. Complex type detection also requires that the type does not have properties that reference entity types (i.e. all the properties must be scalar types) and is not referenced from a collection property on another type. Here is the implementation: |
public class User public class Address { public DbSet<User> Users { get; set; } } |
With code first, this is all of the code we need to write to create a complex type, we do not need to configure any additional database schema mapping information through Data Annotations or the fluent API. Database SchemaThe mapping result for this object model is as follows: |
Complex Types are RequiredAs a limitation of EF in general, complex types are always considered required. To see this limitation in action, let's try to add a record to our database: |
using (var context = new EntityMappingContext()) { User user = new User() { FirstName = "Morteza", LastName = "Manavi", Username = "mmanavi" }; context.Users.Add(user); context.SaveChanges(); } |
Surprisingly, this code throws a System.Data.UpdateException at runtime with this message: |
Null value for non-nullable member. Member: 'Address'. |
If we initialize the address object, the exception would go away and user will be successfully saved into database: |
When we read back the inserted record from the database, EF will return an Address object with all the properties (Street, City and PostalCode) have null values. This means that if you store a complex type object with all null property values, EF returns a initialized complex type when the owning entity is retrieved from the database. |
Explicitly Register a Type as ComplexYou saw that in our model, we did not use any data annotation or fluent API code to designate the Address as a complex type, yet Code First perfectly detects it as a complex type based on Complex Type Discovery concept. But what if our domain model requires a new property called Id on Address class? This new Id property is just a scalar non-primary key property that represents let's say another piece of information about address. In this case, Code First actually can infer a key and therefore marks Address as an entity that has its own mapping table unless we specify otherwise. This is where explicit complex type registration comes into play. CTP5 defined a new attribute in System.ComponentModel.DataAnnotations namespace called ComplexTypeAttribute. All we need to do is to use this attribute on our Address class: |
[ComplexType] public class Address { public int Id { get; set; } public string Street { get; set; } public string City { get; set; } public string PostalCode { get; set; } } |
This will cause Address to remain as a complex type in our model. As always, we can do the same with fluent API. In CTP5 a new generic method has been added to ModelBuilder class which is called ComplexType and has the following signature (when working with fluent API, we don't really care about the method's return values): |
public virtual ComplexTypeConfiguration<TComplexType> ComplexType<TComplexType>() where TComplexType : class; |
Here is how we can register our Address type as complex in fluent API: |
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ComplexType<Address>(); } |
Best Practices When Working with Complex Types
|
public class User { public User() { Address = new Address(); } public int UserId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Username { get; set; } public Address Address { get; set; } } [ComplexType] public class Address { public string Street { get; set; } public string City { get; set; } public string PostalCode { get; set; } public bool HasValue { get { return (Street != null || PostalCode != null || City != null); } } } |
The interesting point is that we do not have to explicitly exclude HasValue property from the mapping. Since this property does not have a setter, EF Code First will be ignoring it based on a convention which makes sense since a read only property is most probably represents a computed value and does not need to be persist in the database. |
Complex Types and the New Change Tracking APIEF Code First CTP5 exposes a new set of change tracking information that enables us to access Original, Current & Stored values, and State (e.g. Added, Unchanged, Modified, Deleted) of our entities. The Original Values are the values the entity had when it was queried from the database. The Current Values are the values the entity has now. This feature also fully supports complex types: |
using (var context = new EntityMappingContext()) { var user = context.Users.Find(1); Address originalValues = context.Entry(user) .ComplexProperty(u => u.Address) .OriginalValue; Address currentValues = context.Entry(user) .ComplexProperty(u => u.Address) .CurrentValue; } |
The entry point for accessing the new change tracking API is DbContext's Entry method which returns an object of type DbEntityEntry. DbEntityEntry contains a ComplexProperty method that returns a DbComplexPropertyEntry object where we can access the original and current values: |
namespace System.Data.Entity.Infrastructure { public class DbEntityEntry<TEntity> where TEntity : class { public DbComplexPropertyEntry<TEntity, TComplexProperty> ComplexProperty<TComplexProperty> (Expression<Func<TEntity, TComplexProperty>> property); } } |
Limitations of This MappingThere are two important limitations to classes mapped as Complex Types:
SummaryIn this post we learned about fine-grained domain models which complex type is just one example of it. Fine-grained is fully supported by EF Code First and is known as the most important requirement for a rich domain model. Complex type is usually the simplest way to represent one-to-one relationships and because the lifecycle is almost always dependent in such a case, it’s either an aggregation or a composition in UML. In the next posts we will revisit the same domain model and will learn about other ways to map a one-to-one association that does not have the limitations of the complex types.References |