MetaTable Expression Builder for Fetching Any Object Via Collection of Primary Keys
Basically, I wanted to build a method that gets an object from a data context whatever the primary key may look like, and whatever type that object may be. I wrote this code a little while ago when I was exploring the DynamicData libs. I was checking out some of the code in the libs, and noticed a very slick way one of devs was building expressions to grab back a data item based on a single key ID. I thought the idea of building up the expression completely ROCKED, but I wanted to expand it out to include composite primary keys. Below is my implementation of the composite primary key object fetcher. Hopefully, it will make your life a little easier.
(Needed to grab the MetaModel class)
using System.Web.DynamicData;
(Create a new MetaModel for your Context or Contexts -- make sure they are only registered once!! This can be an issue if you put this code in a static constructor, and you happen to have a generic class. That would cause the code to get executed each time you specify a new generic argument for type construction (sry, I digress))
Model = new MetaModel();
Model.RegisterContext(typeof([Your Context Type]));
(Get your meta table based on the type from your registered context -- need the metadata about the table to figure out the keys)
protected static MetaTable GetMetaTable<T>()
{
return dataModelMetaModel.GetTable(typeof (T));
}
(This is where the heavy lifting occurs. I commented what is going on. There is not much more to say. Enjoy!)
private static T FetchObject<T>(MetaTable metatable, IDictionary<string, object> pks)
where T : class
{
IQueryable query = metatable.GetQuery();
//build linq expression tree for where clause
ParameterExpression entityparam = Expression.Parameter(metatable.EntityType, "e");
List<Expression> internalExpressons = new List<Expression>();
Expression equalexpression;
foreach (MetaColumn member in metatable.PrimaryKeyColumns)
{
//convert primary key to proper type
PropertyInfo columnpropertyinfo = member.EntityTypeProperty;
MemberExpression columnexpresson = Expression.MakeMemberAccess(entityparam, columnpropertyinfo);
internalExpressons.Add(Expression.Equal(columnexpresson, Expression.Constant(pks[member.Name])));
}
// build up the primary key expressions
if (internalExpressons.Count > 1)
{
equalexpression = Expression.And(internalExpressons[0], internalExpressons[1]);
for (int i = 2; i < internalExpressons.Count; i++)
{
equalexpression = Expression.And(equalexpression, internalExpressons);
}
}
else
{
equalexpression = internalExpressons[0];
}
// Create the where clause
LambdaExpression wherelambda = Expression.Lambda(equalexpression, entityparam);
MethodCallExpression wherecall = Expression.Call(typeof (Queryable), "Where", new[] {metatable.EntityType},
query.Expression, wherelambda);
query = query.Provider.CreateQuery(wherecall);
// Call FirstOrDefault to make sure the connection is closed when finished
// saw some connections staying open when just grabbing the first object.
return query.Cast<T>().FirstOrDefault();
}
Twitter: @DavidJustice