7

Background:

I have a web service that returns the rows in a table (table name supplied as a parameter) with Ids greater than a certain Id (also supplied as a parameter). We are assuming the Ids are sequential.

I am using Linq to SQL for database interaction so I want to return the new rows as:

List<WhateverObject>

Because we only know the table name at runtime, I can't use Linq in a normal fashion which has made things much more complex.

Question:

The code is below (and it works). How can I simplify it? It seems overly complex.

private object GetUpdateList(string tableName, int Id, DataClassesDataContext db)
{
    PropertyInfo pi = db.GetType().GetProperty(tableName);

    var table = pi.GetValue(db, null);

    // Get type of object within the table.
    Type genericType = table.GetType().GetGenericArguments()[0];

    // The Where method lives on the Enumerable type in System.Linq
    var whereMethods = typeof(System.Linq.Enumerable)
        .GetMethods(BindingFlags.Static | BindingFlags.Public)
        .Where(mi => mi.Name == "Where");

    // There are actually 2 where methods - we want the one with 2 parameters
    MethodInfo whereMethod = null;
    foreach (var methodInfo in whereMethods)
    {
        var paramType = methodInfo.GetParameters()[1].ParameterType;
        if (paramType.GetGenericArguments().Count() == 2)
        {
            // we are looking for  Func<TSource, bool>, the other has 3
            whereMethod = methodInfo;
            break;
        }
    }

    Func<object, bool> IdEquals = BuildEqFuncFor("Id", Id);

    whereMethod = whereMethod.MakeGenericMethod(genericType);
    var result = whereMethod.Invoke(table, new object[] { table, IdEquals });

    MethodInfo toListMethod = typeof(System.Linq.Enumerable).GetMethod("ToList").MakeGenericMethod(genericType);
    return toListMethod.Invoke(result, new object[] { result });
}

// Build lambda expression for use in Linq
private static Func<object, bool> BuildEqFuncFor(string prop, object val)
{
    // We know we are comparing integers here so cast them.
    // There is probably a more general solution.
    return t => (int)t.GetType().InvokeMember(prop, BindingFlags.GetProperty, null, t, null) > (int)val;
}

To come up with this solution I have had to reference the following questions:

Community
  • 1
  • 1
Ryan Kirkman
  • 4,051
  • 2
  • 25
  • 20
  • Not to throw off your question, but are you in a situation where it is impossible to refactor? It seems like a lot of work for something so simple. Does it need to be so generic? – Bryan Crosby Jul 26 '12 at 02:01
  • Just make it strongly typed? Pass the table name and the id. Select it from the database and return the requested rows. Return an `IEnumerable` and call it a day. – Bryan Crosby Jul 26 '12 at 02:12
  • Problem is I don't know the table name at compile time. I need it to return any one of the currently 30 tables (which could grow in the future). If it's dynamic, I can add tables and this code will still work. – Ryan Kirkman Jul 26 '12 at 02:21

1 Answers1

5

Try something like this:

private IList GetUpdateList(string tableName, int id, DataClassesDataContext db)
{
    System.Reflection.PropertyInfo pi = db.GetType().GetProperty(tableName);

    var table = pi.GetValue(db, null);

    // Get type of object within the table.
    Type genericType = table.GetType().GetGenericArguments()[0];

    var param = Expression.Parameter(genericType, "x");
    var predicateExpr = Expression.Lambda(
        Expression.GreaterThan(
            Expression.Property(param, "Id"),
            Expression.Constant(id)),
        param);

    return this
        .GetType()
        .GetMethod("GetUpdateListGeneric")
        .MakeGenericMethod(genericType)
        .Invoke(this, new[] { table, predicateExpr }) as IList;
}

private IList<T> GetUpdateListGeneric<T>(
    Table<T> table, 
    Expression<Func<T, bool>> predicate) where T : class
{
    return table.Where(predicate).ToList();
}
Ryan Kirkman
  • 4,051
  • 2
  • 25
  • 20
Jacob
  • 77,566
  • 24
  • 149
  • 228
  • Awesome! I just had to edit a few things (return type) and pull Expression.Parameter into a variable first and it works. – Ryan Kirkman Jul 26 '12 at 03:51