Other Links

Mapping private properties with EF 4.1 RC and Fluent mapping

EF 4.1 is now in RC phase and as a NHibernate user Iâ€™m curious to check the fluent API to map entities to database. One of the feature that I and Andrea miss most is the possibility to map private properties with fluent interface. It seems strange to map private properties at once, but it can be useful in DDD. Suppose you have these simple classes.

Figure 1: A really simple domain

We have two things to notice, the first is that the Category class has a private property called PrivateDetails, and the other is that the Products collection is protected, and you can add products from the outside thanks to the AddProduct() method.

1:publicclass Category

2: {

3:public Int32 CategoryId { get; set; }

4:publicstring Name { get; set; }

5:private String PrivateDetails { get; set; }

6:

7:private ICollection<Product> _products;

8:protectedvirtual ICollection<Product> Products

9: {

10: get { return _products ?? (_products = new HashSet<Product>()); }

11: set { _products = value; }

12: }

13:

14:publicvoid AddProduct(Product p)

15: {

16: Products.Add(p);

17: }

18:

19:publicvoid SetDetails(String details)

20: {

21: PrivateDetails = details;

22: }

23: }

The idea behind this is that you should access only the AGGREGATE roots, not manipulating directly the collection of Products, this forces the user of the class to use specific methods. Now a problem arise, how we can map this class with EF 4.1 fluent interface? The problem is generated from the Fluent interface, that permits only to specify properties with Lambda.

Figure 2: The HasMany() method accepts an Expression

As you can see if I specify that CategoryId is mapped to an Identity database column with the instruction

Property(c => c.CategoryId)

this technique is known as static reflection and is really useful in such scenarios, but â€¦ now I could not use the HasMany() methods to map a protected property.

Figure 3: How can I map a protected property if I could not use in a lambda?

This problem derives only from the Fluent Interface, because EF is internally capable to map private members of objects, then we can use a little trick. I want to be able to write code like this

1:this.HasMany<Category, Product>("Products");

This would solve all our problems, because with this statement Iâ€™m asking EF to map a collectino called Products. Fortunately writing such an extension method is quite simple, it is just a bunch of Expressions

This code seems complex but it is rather simple. It creates a parameter expression of the same type of the object, then grab a reference to the property from its name with reflection and with the PropertyInfo creates an Expression.Property. This expression (created in line 13) is the equivalent of c => c.Products lambda and it can be passed to the Expression.Lambda to create an Expression<Func<T, ICollection<U>>> object, expected from the HasMany() method.

With the same technique I can write an extension method that maps a private string property.

Thanks to those two methods now Iâ€™m able to write this mapping for the category class.

1:publicclass CategoryMapping : EntityTypeConfiguration<Category>

2: {

3:public CategoryMapping()

4: {

5: Property(c => c.CategoryId).HasDatabaseGeneratedOption(

6: DatabaseGeneratedOption.Identity);

7:

8:this.PropertyStr("PrivateDetails").HasColumnName("Details");

9:

10:this.HasMany<Category, Product>("Products");

11: ToTable("Category");

12: }

13: }

As you can see Iâ€™m able to map the PrivateDetails property and I can choose column name, and the Products property with no problem. Now I can use my model

1: var food = new Category { Name = "Foods" };

2: food.SetDetails("Details");

3: db.Categories.Add(food);

4: Product p = new Product() {Name = "Beer"};

5: food.AddProduct(p);

6:int recordsAffected = db.SaveChanges();

As you can ses Iâ€™m able to add product without the need to directly access the collection, and I can set a private property through a method (not so useful technique, but just to show that mapping private properties works). Running the sample I got

Figure 4: The fact that three entities were saved confirmed me that the mapping of the protected collection works.