Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A smarter Entity Framework Codefirst fluent API

I need to use Sql Server's "datetime2" type on all the DateTime and DateTime? properties of all my entity objects. This is normally done with the fluent API like this:

modelBuilder.Entity<Mail>().Property(c => c.SendTime).HasColumnType("datetime2");

However, I would prefer NOT to do this manually for each and every DateTime field in each and every entity type. (I do not have a common base type where all the DateTime properties can be placed, because the DateTime properties are specific to the entity types where they are defined).

The short question: What are my options ?

The long question: I was considering using reflection and made an atttempt, but it got very messy as is seems like the fluent API is not really suited for this kind of work because I had to call the generic fluent API methods via reflection. Here is my messy attempt:

    protected override void OnModelCreating(DbModelBuilder modelBuilder) {
        var genericEntityMethod = modelBuilder.GetType().GetMethod("Entity");
        var entityTypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsClass && !t.IsAbstract && t.GetInterface("IEntity") != null);
        foreach (var t in entityTypes) {
            var props = t.GetProperties().Where(p => 
                p.GetSetMethod() != null && 
                p.GetGetMethod() != null && 
                (p.PropertyType == typeof (DateTime) ||  
                p.PropertyType == typeof(DateTime?)));
            foreach (var propertyInfo in props) {
                var entityMethod = genericEntityMethod.MakeGenericMethod(t.GetType());
                var entityTypeConfiguration = entityMethod.Invoke(modelBuilder,null);
                var lambdaExpression = Expression.Lambda(Expression.MakeMemberAccess(Expression.Parameter(t), propertyInfo), Expression.Parameter(t));

                //var propertyMethod = entityTypeConfiguration.GetType().GetMethod("Property");  // Cant get this to work
                var propertyMethods = entityTypeConfiguration.GetType().GetMethods().Where(m => m.ReturnType == typeof(DateTimePropertyConfiguration)).ToList();
                var propertyMethod = propertyInfo.PropertyType == typeof (DateTime) ? propertyMethods[0] : propertyMethods[1];

               var dateTimePropertyConfiguration = propertyMethod.Invoke(entityTypeConfiguration, new object[] {lambdaExpression});
                var hasColumnTypeMethod = entityTypeConfiguration.GetType().GetMethod("HasColumnType");
                hasColumnTypeMethod.Invoke(dateTimePropertyConfiguration, new object[]{ "datetime2" });
            }
        }
        base.OnModelCreating(modelBuilder);
    }

This fails in this line:

                   var dateTimePropertyConfiguration = propertyMethod.Invoke(entityTypeConfiguration, new object[] {lambdaExpression});

with this error (Entities.Order is one of my entity objects having a DateTime property):

Object of type 'System.Linq.Expressions.Expression`1[System.Func`2[Entities.Order,System.Nullable`1[System.DateTime]]]' cannot be converted to type 'System.Linq.Expressions.Expression`1[System.Func`2[System.RuntimeType,System.Nullable`1[System.DateTime]]]

Can anyone help me out with this reflection approach or better, show me a simpler way to avoid having to manually write a lot of fluent api stuff?

like image 234
Stig Schmidt Nielsson Avatar asked Dec 20 '12 08:12

Stig Schmidt Nielsson


1 Answers

There is much better approach. Unfortunately the approach is based on custom conventions which will be available in EF6. You can try EF6 Alpha 2 and play with custom conventions yourselves - the walkthrough contains example with your exact requirement.

To solve your issue with reflection check this blog article. It describes the code for generating mappings with reflection (there should be related open source project).

like image 139
Ladislav Mrnka Avatar answered Oct 18 '22 20:10

Ladislav Mrnka