Entity framework 4 – Part 2 – Relationships between non-public members

Part 1 – Getting started
Part 3 – Adding pluralization support

Ok, the mission of this post is to extend the code from the previous post (about dynamic and non strongly typed object-contexts) and add some relations between the entities. I will show you one way how you could setup relationships between entities where members are not public.

Download a complete running example.

The example now is as follows:
A Person is represented by an user-account which can have many whishlists and each list can have many wishes.

The client code – Using Entity store
Lets look at the code that consumes the Entity store and creates some wishes for “Little Ben”.

//Create the User: Little Ben
var userAccount =
    new UserAccount
    {
        Username =    "littleben",
        Password =    "ilovesnow",
        Email =    "ben@thelittles.com"
    };

//Create Little Ben's Wishlist for Christmas
var wishlist = new WishList { WishedBy = userAccount };
wishlist.WishSomething(new Wish { Description = "A new computer." });
wishlist.WishSomething(new Wish { Description = "A new lens." });

using (IEntityStore es = new EfChristmasEntityStore("Test"))
{
    es.EnsureCleanDatabaseExists();

    //Store the useraccount, the wishlist and it's wishes.
    //No need for: es.AddEntity(userAccount); or es.AddEntity(wish1)...(wish2);
    es.AddEntity(wishlist);

    es.SaveChanges();
}

To make the above work, you need to tell the ContextBuilder how to map entities. This is done using a fluent API which is refactoring friendly, no bloated Xml is required.

UserAccount – mappings
Lets look at the mappings for the UserAccount.

[Serializable]
public class UserAccountMapping
    : EntityConfiguration<UserAccount>
{
    public UserAccountMapping()
    {
        HasKey(u => u.Id);
        Property(u => u.Id).IsIdentity();

        Property(u => u.Email)
            .IsRequired()
            .IsNotUnicode()
            .MaxLength = 150;

        Property(u => u.Username)
            .IsRequired()
            .IsUnicode()
            .MaxLength = 20;

        Property(u => u.Password)
            .IsRequired()
            .IsUnicode()
            .MaxLength = 20;
    }
}

No rocket science there. The mappings for the WishList is the first entity that has a required relationship (doesn’t allow null values) to another entity, the UserAccount.

WishList – mappings

[Serializable]
public class WishListMapping : EntityConfiguration<WishList>
{
    public WishListMapping()
    {
        HasKey(wl => wl.Id);
        Property(wl => wl.Id).IsIdentity();

        Relationship<UserAccount>(wl => wl.WishedBy).IsRequired();
    }
}

Wish – mappings
The final mapping is the actual Wish-entity which is owned by a certain WishList. The Wishes are stored in an internal wish-collection in the WishList-entity and it does not have a public scope hence you can’t use Expressions to configure it. I have put together some code that allows you to use expressions for this, but you need to pass a string that defines the name of the member.

[Serializable]
public class WishMapping : EntityConfiguration<Wish>
{
    public WishMapping()
    {
        HasKey(wl => wl.Id);
        Property(wl => wl.Id).IsIdentity();

        Relationship<WishList>(w => w.BelongsToList).IsRequired();

        //Can't use "RelationshipFrom<WishList>(w => w.Wishes);" since Wishes in WishList has scope "protected".
        var wishesExpression = ObjectAccessor<WishList>.CreateExpression<ICollection<Wish>>("Wishes");
        RelationshipFrom<WishList>(wishesExpression);
    }
}

Lets register the mappings in the builder so that they are known when the context is being constructed.

The custom Entity store

public class EfChristmasEntityStore : EfEntityStore
{
    public EfChristmasEntityStore(string connectionStringName) : base(connectionStringName)
    {
    }

    protected override void ConfigureEntities()
    {
        ConfigureEntity(new UserAccountMapping());
        ConfigureEntity(new WishListMapping());
        ConfigureEntity(new WishMapping());
    }
}

There is also an alternative way to use the infrastructure in the client code, if you don’t want to setup an custom Entity store.

//You can also consume it like this (but then you need references to System.Data.Entity and Microsoft.Data.Entity.CTP):

var builder = new ObjectContextBuilder<ObjectContext>("Test");

builder.RegisterEntity(new UserAccountMapping());
builder.RegisterEntity(new WishListMapping());
builder.RegisterEntity(new WishMapping());

using (var ctx = builder.CreateContext())
{
    if (!ctx.DatabaseExists())
        ctx.CreateDatabase();

    ctx.EntitySet<WishList>().AddObject(wishList);
    ctx.SaveChanges();
}

Thats it. Happy coding. Download the code and try for your self.

//Daniel