Mapping private or protected properties with Code First (EFv4, CTP4)
If you’re specifying mapping in Code First in Entity Framework you’re essentially describing it with like this (assuming using EntityConfiguration
class).
this.Something(x => x.Foo).Bar();
This is nice, but if the property you wanna to use is either private or protected you are hitting wall as you can’t write such expression. I faced the same problem today. The easiest approach is to have the configuration class nested to entity itself. But that’s not a clean (or may not be doable) and I do want clean code.
Because I know, Entity Framework can use these properties – if you’re using designer it’s just setting some fields in Properties window. So I concluded, that the only problem is to how to push it into Code First. My focus was primarily for MapSingleType
method (but it’s doable for i.e. Property
method as well).
With the method you can write two types of mapping, one using EntityMap
class directly.
this.MapSingleType(x =>
EntityMap.Row(
EntityMap.Column(x.Foo, "FooColumn"),
EntityMap.Column(x.Bar, "BarColumn")))
.ToTable(new StoreTableName("Baz", "dbo"));
Or one using anonymous type.
this.MapSingleType(x => new
{
FooColumn = x.Foo,
BarColumn = x.Bar
})
.ToTable(new StoreTableName("Baz", "dbo"));
With the starting point set I decided the easiest way to specify private or protected properties will be using string. The only task is to create the Expression that’s else generated by compiler. After some juggling with the trees I created this extension method.
public struct ColumnPropertyMapping
{
public string Column { get; set; }
public string Property { get; set; }
public ColumnPropertyMapping(string column, string property)
: this()
{
this.Column = column;
this.Property = property;
}
}
public static EntityMap MapSingleType<TEntity>(this EntityConfiguration<TEntity> configuration, Expression<Func<TEntity, object>> initialMapping, params ColumnPropertyMapping[] additionalMappings)
where TEntity : class
{
if (additionalMappings == null)
throw new ArgumentNullException("additionalMappings");
List<Expression> newParameters = new List<Expression>();
var entity = initialMapping.Parameters[0];
Func<ColumnPropertyMapping, MethodCallExpression> makeColumnCall =
m =>
Expression.Call(
typeof(EntityMap),
"Column",
null,
Expression.Convert(
Expression.MakeMemberAccess(entity, typeof(TEntity).GetMember(m.Property, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).First()),
typeof(object)),
Expression.Constant(m.Column));
var callExpression = (initialMapping.Body as MethodCallExpression);
var newExpression = (initialMapping.Body as NewExpression);
if (callExpression != null)
{
newParameters.AddRange((callExpression.Arguments[0] as NewArrayExpression).Expressions);
}
else if (newExpression != null)
{
newParameters.AddRange(newExpression.Arguments.Select((e, i) => new ColumnPropertyMapping(newExpression.Members[i].Name, (e as MemberExpression).Member.Name)).Select(x => makeColumnCall(x)));
}
else
{
throw new ArgumentException("initialMapping");
}
newParameters.AddRange(additionalMappings.Select(x => makeColumnCall(x)));
var finalMapping = Expression.Lambda<Func<TEntity, object>>(
Expression.Call(
typeof(EntityMap),
"Row",
null,
Expression.NewArrayInit(
typeof(EntityMapColumn),
newParameters)),
entity);
return configuration.MapSingleType(finalMapping);
}
It’s method with similar signature as the original one, but taking extra collection of ColumnPropertyMapping
, my helper objects to represent the mapping as strings. I take the input – EntityMap
or anonymous object – peck up the important pieces and recreate the expression with added properties. I’m resulting to tree with EntityMap
, as it looked easier to create. So now you can create the mapping also for non-public properties (and also dynamic mapping is easier).
this.MapSingleType(x =>
EntityMap.Row(
EntityMap.Column(x.Foo, "FooColumn"),
EntityMap.Column(x.Bar, "BarColumn")),
new ColumnPropertyMapping("SomeColumn", "ImNotPublic"))
.ToTable(new StoreTableName("Baz", "dbo"));
// or
this.MapSingleType(x => new
{
FooColumn = x.Foo,
BarColumn = x.Bar
},
new ColumnPropertyMapping("SomeColumn", "ImNotPublic"))
.ToTable(new StoreTableName("Baz", "dbo"));
Enjoy, if you need it. 😃 I hope the CTP5 will address this “scenario”, thus I’ll not be forced to write it for other methods. And if not, stay tuned, I’ll definitely post it. 😎