views:

48

answers:

1

I'm using some code from a book to implement a generic repository class for EF data access. the code uses the following two methods to get a single entity by its int id primary key, assuming all DB objects have an int PK. However, I am using an imported DB with mainly natural keys, and I wish to preserve all the FK relationships as they are, i.e. I don't want to redesign the DB to use single column int PK's.

How can I adapt the code below to work with multi-column keys?

protected Expression<Func<T, bool>> CreateGetExpression<T>(int id)
{
    ParameterExpression e = Expression.Parameter(typeof(T), "e");
    PropertyInfo propInfo = typeof(T).GetProperty(KeyPropertyName);
    MemberExpression m = Expression.MakeMemberAccess(e, propInfo);
    ConstantExpression c = Expression.Constant(id, typeof(int));
    BinaryExpression b = Expression.Equal(m, c);
    Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(b, e);
    return lambda;
}

public override T Get<T>(int id)
{
    return List<T>().FirstOrDefault(CreateGetExpression<T>(id));           
}

I want my Get method to look like this:

public override T Get<T>(params object[] keyValues)
{
    return List<T>().FirstOrDefault(CreateGetExpression<T>(keyValues));           
}
+1  A: 

Well, you basically need to build up an expression tree with multiple equality checks. You can take the code you've got for building a single equality check, and build up multiple ones, one for each key. Then you need to combine them using Expression.AndAlso multiple times - so if you've got individual equality checks e1, e2 and e3 you might use:

var e = Expression.AndAlso(Expression.AndAlso(e1, e2), e3);

One thing to note: you'll need to use a single ParameterExpression for the whole final expression - so you'll need to adapt your "build a single check" method to take a ParameterExpression as a parameter... and you don't need to use Expression.Lambda until the very end. So the overall steps will be something like:

  • Create a ParameterExpression
  • For each key, create a BinaryExpression using Expression.Equals, using the same ParameterExpression you've just created.
  • Combine the equality expressions with multiple calls to Expression.AndAlso
  • Call Expression.Lambda at the very end to create an Expression<Func<T, bool>>
Jon Skeet
Thanks Jon. Looks like I'm in for some recursive fun. I'm almost tempted to just implement single value keys, and be done with it, but this is more exciting :-)
ProfK
@ProfK: I don't see why it should be recursive, to be honest...
Jon Skeet
I think by recursive he means the application not working properly, resulting in cursing multiple times (i.e. recursing) and then throwing the monitor out the window. Happens to me all the time... :)
ClarkeyBoy
@Jon, I haven't tackled implementing a solution yet - right now I just override the Get method in type specific repositories - but code to nest `AndAlso` calls like this `Expression.AndAlso(Expression.AndAlso(e1, e2), e3)` loudly suggests recursion, to me at least.
ProfK
@ClarkeyBoy, I suppose lots of cursing is par for the course using a DB with inconsistently structured natural keys. Although maybe less cursing than adding consistent surrogate keys and redefining all my FK's to use them.
ProfK
@ProfK: It suggests iteration to me rather than recursion, if you have a collection of expressions. Start with one expression as "current", and iterate over the collection setting `current = Expression.AndAlso(current, next)`
Jon Skeet
Ah, of course, thanks @Jon. I had the wrong view of the AndAlso function as adding an expression term, not returning one. Maybe I should look at these things more carefully when starting out.
ProfK