How To Reference A Property By Name Without Using String Constants

by Jon Davis 13. June 2010 02:39

I’m working on some code here where I’m loading an entity using a dictionary. I’m not using .NET 4.0 so I’m not going to get to use the dynamic keyword and its IDictionary implementation of properties reflection.

My original code looked something sort of like this:

public void Populate(IDictionary<string, object> values) 
{
foreach (var kvp in values) {
var key = kvp.Key;
var value = kvp.Value;
switch (key) 
{
case "FirstName":
this.FirstName = value;
break;
case "LastName":
this.LastName = value;
break;
// and so on and so forth
}
}
}

This looks sloppy. First of all, I don’t want to have to declare each and every property again for its setter just to support IDictionary. Secondly, what if I rename my properties? Then I’d have to go back and update this switch…case statement again. I want to use a strongly-typed property reference, so that if I rename the property I can use the IDE’s built-in refactoring functionality with which all uses of that property are automatically updated. I don’t get that benefit if I’m using string constants.

I’d come across some interesting tutorials in the past that addressed this problem, and I thought I’d give it my own go based on their insight. My revised solution is not by any means original. (Nothing I ever do is, I suppose.)

To make a long story short, I flexed some recently learned LINQ expression muscle on a member evaluator expression to obtain the strongly typed property’s name. In my implementation (not entirely shown here) I actually do need to verify the property by name because I have a bunch of setter logic that I don’t expose here, such as replacing the value being an IEnumerable<string> with logic that clears an ObservableCollection<string> and populates it with the value. Anyway, here’s the basic flow:

public override void Populate(IDictionary<string, object> values)
{
foreach (var kvp in values) {
if (key == base.ResolvePropertyName(() => this.SpecialProperty))
{
// this.SpecialProperty = value;
// do something special with setter here
}
else if (key == base.ResolvePropertyName(() => this.AnotherSpecialProperty))
{
// this.AnotherSpecialProperty = value;
// do something special with setter here
}
else 
{
base.Populate(key, value);
}
}
}
/// and in the base class ....
/// <summary>
/// Returns the name of the property. Syntax:
/// <code>
/// var propertyName = ResolvePropertyName(() =&gt; this.MyProperty)
/// </code>
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
protected string ResolvePropertyName(Expression<Func<T>> e)
{
if (e.NodeType == ExpressionType.Lambda)
{
return ResolvePropertyName(((LambdaExpression)e).Body);
}
if (e.NodeType == ExpressionType.MemberAccess)
{
return ((MemberExpression)e).Member.Name;
}
throw new InvalidOperationException("Given expression is not type MemberAccess.");
}
private static Dictionary<Type, List<PropertyInfo>> TypeProperties
= new Dictionary<Type, List<PropertyInfo>>();
/// <summary>
/// Populates a property of the current entity with the the given key/value pair.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public void Populate(string property, object value)
{
var t = this.GetType();
List<PropertyInfo> pis;
if (!TypeProperties.ContainsKey(t))
{
TypeProperties[t] = t.GetProperties().ToList();
}
pis = TypeProperties[t];
var pi = pis.Find(pif => pif.Name == property);
if (pi == null) throw new KeyNotFoundException("Property does not exist: \"" + property + "\"");
pi.SetValue(this, value, null);
}

 

The Populate(key,value) method is basic .NET 2.0 reflection with some sorta-kinda basic caching.  The method above it is using LINQ expressions to extract the member access invocation as expressed in the LINQ statement.

I wrote a unit test to invoke this and stepped through it with the debugger. Seems to work fine.

After further tweaking, I migrated the base class functions to a utility class so that these functions can be used with anything, not just with objects that inherit a common base class. I also improved the reflection caching of Populate() so that the properties are properly hashed on their property names.

public static class ObjectUtility
{
/// <summary>
/// Returns the name of the property. Syntax:
/// <code>
/// var propertyName = obj.ResolvePropertyName(() =&gt; obj.MyProperty)
/// </code>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <param name="expr"></param>
/// <returns></returns>
public static string ResolvePropertyName<T>(this object obj, Expression<Func<T>> expr)
{
return ResolvePropertyName(expr);
}
/// <summary>
/// Returns the name of the property. Syntax:
/// <code>
/// var propertyName = ObjectUtility.ResolvePropertyName(() =&gt; obj.MyProperty)
/// </code>
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
public static string ResolvePropertyName(Expression e)
{
if (e.NodeType == ExpressionType.Lambda)
{
return ResolvePropertyName(((LambdaExpression)e).Body);
}
if (e.NodeType == ExpressionType.MemberAccess)
{
return ((MemberExpression)e).Member.Name;
}
throw new InvalidOperationException("Given expression is not type MemberAccess.");
}
private static Dictionary<Type, Dictionary<string, PropertyInfo>> TypeProperties
= new Dictionary<Type, Dictionary<string, PropertyInfo>>();
/// <summary>
/// Populates a property of an object with the the given key/value pair.
/// </summary>
/// <param name="obj"></param>
/// <param name="property"></param>
/// <param name="value"></param>
public static void Populate(this object obj, string property, object value)
{
var t = obj.GetType();
if (!TypeProperties.ContainsKey(t))
{
TypeProperties[t] = new Dictionary<string, PropertyInfo>();
var lst = t.GetProperties().ToList();
foreach (var item in lst)
{
if (!TypeProperties[t].ContainsKey(item.Name))
{
TypeProperties[t][item.Name] = item;
}
}
}
var pisdic = TypeProperties[t];
var pi = pisdic.ContainsKey(property) ? pisdic[property] : null;
if (pi == null) throw new KeyNotFoundException("Property does not exist: \"" + property + "\"");
pi.SetValue(obj, value, null);
}
}

 

 

Gemli.Data: Basic LINQ Support

by Jon Davis 6. June 2010 04:13

In the Development branch of Gemli.Data, I finally got around to adding some very, very basic LINQ support. The following test scenarios currently seem to function correctly:

var myCustomEntityQuery = new DataModelQuery<DataModel<MockObject>>();
// Scenarios 1-3: Where() lambda, boolean operator, method exec
var linqq = myCustomEntityQuery.Where(mo => mo.Entity.MockStringValue == "dah");
linqq = myCustomEntityQuery.Where(mo => mo.Entity.MockStringValue != "dah");
// In the following scenario, GetPropertyValueByColumnName() is an explicitly supported method
linqq = myCustomEntityQuery.Where(mo=>((int)mo.GetPropertyValueByColumnName("customentity_id")) > -1);
// Scenario 4: LINQ formatted query
var q = (from mo in myCustomEntityQuery
where mo.Entity.MockStringValue != "st00pid"
select mo) as DataModelQuery<DataModel<MockObject>>;
// Scenario 5: LINQ formatted query execution with sorted ordering
var orderedlist = (from mo in myCustomEntityQuery
where mo.Entity.MockStringValue != "def"
orderby mo.Entity.MockStringValue
select mo).ToList();
// Scenario 6: LINQ formatted query with multiple conditions and multiple sort members
var orderedlist = (from mo in myCustomEntityQuery
where mo.Entity.MockStringValue != "def" && mo.Entity.ID < 3
orderby mo.Entity.ID, mo.Entity.MockStringValue
select mo).ToList();

This is a great milestone, one I’m very pleased with myself for finally accomplishing. There’s still a ton more to do but these were the top 50% or so of LINQ support scenarios needed in Gemli.Data.

Unfortunately, adding LINQ support brought about a rather painful rediscovery of critical missing functionality: the absence of support for OR (||) and condition groups in Gemli.Data queries. *facepalm*  I left it out earlier as a to-do item but completely forgot to come back to it. That’s next on my plate. *burp*


 

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen

About the author

Jon Davis (aka "stimpy77") has been a programmer, developer, and consultant for web and Windows software solutions professionally since 1997, with experience ranging from OS and hardware support to DHTML programming to IIS/ASP web apps to Java network programming to Visual Basic applications to C# desktop apps.
 
Software in all forms is also his sole hobby, whether playing PC games or tinkering with programming them. "I was playing Defender on the Commodore 64," he reminisces, "when I decided at the age of 12 or so that I want to be a computer programmer when I grow up."

Jon was previously employed as a senior .NET developer at a very well-known Internet services company whom you're more likely than not to have directly done business with. However, this blog and all of jondavis.net have no affiliation with, and are not representative of, his former employer in any way.

Contact Me 


Tag cloud

Calendar

<<  November 2018  >>
MoTuWeThFrSaSu
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789

View posts in large calendar