Entity Framework Relationship Mapping – Best of both worlds (EF4.1/CTP5 Code only & fluent API)
Author: Stuart // Category: ASP.NET, CTP5, EF4.1, Entity FrameworkThis is a quick post as I don’t see myself finding the time to finish my series on scalable code using Entity Framework 4.1 (currently CTP5) anytime soon.
Disclaimer: This is not an excuse for poor architecture and your design should have your concerns seperated well as you can get into trouble with partially loaded entities saving back to the db.
One of my favourite things about the code only persistance ignorant approach now available in EF4.1 is the ability to manage my relationships a little ad-hoc.
Why does the word Ad-Hoc sound so hacky and why do I like it?
I like to have a self documenting model. I don’t like the next guy to have to dig around to work out relationships or pour over database design diagrams for hours. I also don’t like persistance awareness that comes with the designer because of the extra database traffic it generates for simple crud operations when dealing with the persistance ignorant web.
So that leaves me with a dilema. Do I map my relationships properly in my model (easy for the next guy), or do I make my objects lame so you don’t have to load aggregate roots all the time for simple options (more scalable).
Now it seems we can do both. For example, I’m going to use one of the conveniently simple scenarios that is so commonly used to sell these things. The simple blog model.
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public List Comments { get; set; }
}
public class Comment
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public Post ParentPost { get; set; }
}
So how do I add a comment to this scenario?
I need to load the post and then save the post with the new comment to ensure the comment has the correct PostId in the databsae (or generated reference table if you let EF do it for you). That stinks. Especially if comments are being created extremely frequently and are coming from a website. The post is not stored anywhere after the page it’s on it is served. Using caching can lead to persistence clashes.
So I need to load the post just to add a comment. Not so bad in the above example, but if Post has varchar(max) fields and is a large complex object in a complex model, this starts to get very ugly and slow.
A lot of developers go for lame objects to resolve this, which is cool and I totally understand why, but this can lead to performance issues on the way out of the database as the generated sql ain’t exaclty optimised. I’ve found EF does it better when the model’s in tact properly. It also makes code harder to read and understand and makes it much easier to corrupt data in many cases.
Now we can have our cake and eat it too. All we need to do is add the foreign key property to the comment object and then tell the fluent api it references the object.
public class Comment
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
// Add post id
public int PostId { get; set; }
public Post Post { get; set; }
}
Fluent mapping
public class CommentMapping : EntityTypeConfiguration<Comment>
{
public CommentMapping()
{
HasKey(x => x.Id);
HasRequired(x => x.Post).WithMany().HasForeignKey(x => x.PostId);
ToTable("Comment");
}
}
This tells EF what I need it to know. Now I can just go ahead and create a comment object anywhere, anytime and save it to the database. Next time I query the post or comments, the relationship will be perfectly in tact.
This is a big deal for me. It means in MVC and jquery scenarios I can easily create small listitem objects on larger more complex objects without loading full heirarchies first. I just set PostId, save it and forget (like with lame objects).
However I can still query the data with
from c in Post.Comments where c.user = "me" select c
This is especially useful if your model goes 3 or 4 levels down a heirarchy and you want to selectively populate ViewModels to make it scalable.
There are a few things to look out for, like enforcing foreign keys on your DB and designing your app to make it clear the new object can’t query back to the post, but his solves a long running problem I’ve had with linq to sql, EF1 and EF4 when working on the web.