Mapping with Expressions

Friday, November 14, 2008

Once you know about the magic of Expression<T>, it’s hard not to make use of it.

Or perhaps, abuse it.

Here is an excerpt of a class I wrote that uses Expression<T> as a reflection helper.

public class PropertySpecifier<T>
{
    public PropertySpecifier(
                Expression<Func<T, object>> expression)
    {
        _expression = (expression.Body) as MemberExpression;
        if (_expression == null)
        {                
            // raise an error
        }
    }

    public string PropertyName
    {
        get
        {
            return _expression.Member.Name;
        }
    }

    // ...

    MemberExpression _expression;
}

I use the class to attach metadata to my flowchart shapes about which pieces of data they require via a RequiresField extension method.

.WithShape("FibrinolyticTherapy")
    .RequiresField(pd => pd.FibrinolyticAdministration)

The property specifier allows me to find, by name, the property the expression is touching, and also build dynamic getters and setters for the property. For my flowcharts,once each shape is associated with its required data, I can query the flowchart evaluation results to find out what pieces of information the flowchart requires the user needs to enter. This information can be used to selectively enable UI controls.

var controlsToEnable =
        results.SelectMany(r => r.RequiredFields)                
               .Join(mapper.Entries,
                        ps => ps.PropertyName,
                        e => e.Left.PropertyName,
                        (ps, e) => e.Right)
               .Select(ps => ps.GetValue<IAnswerControl>(_view));

Now, that particular piece of code is something I’m not too happy about, but more on that in a later post. This code is in a presenter class and joins the PropertySpecifier objects the flowchart requires with PropertySpecifer objects that reference a view class. The query essentially turns Expression<T> metadata into cold, hard Control references with no magic strings and strong typing all the way down. It’s also easy to write unit tests using some reflection to ensure all properties are mapped, and mapped correctly.

All that is needed is a property map to store the associations between model data and view controls. This starts with a PropertyMapEntry class.

public class PropertyMapEntry<TLeft, TRight>
{
    public PropertySpecifier<TLeft> Left;
    public PropertySpecifier<TRight> Right;
}

And a PropertyMapper.

public class PropertyMapper<TLeft, TRight>
{
    public PropertyMapper()
    {
        Entries = new List<PropertyMapEntry<TLeft, TRight>>();
    }

    public List<PropertyMapEntry<TLeft, TRight>> Entries { get; set; }
}

And a fluent-ish API to build the map.

static PropertyMapper<PnemoniaCaseData, IPnemoniaWorksheet> mapper =
    new PropertyMapper<PnemoniaCaseData, IPnemoniaWorksheet>()        
        .Property(cd => cd.ChestXRay.Length).MapsTo(v => v.ChestXRay)
        .Property(cd => cd.ClinicalTrial).MapsTo(v => v.ClinicalTrial)
        // ...
        ;

I’m pretty happy with how it worked out – at least it looks like this will produce v 1.0 of shipping product. Yet I still wonder if I’m using Expression<T> for evil or for good. What do you think?


Comments
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!