Search code examples
c#linqlambdalinq-expressions

Dynamic lambda expression for SingleOrDefault


I have a DataClassesDataContext containing a group of tables, and I am trying to do lambda expression filtering dynamically using only the name of the tables and the names of the fields. Basically I want to find for each table if a row with a specific ID already exists.

If I knew the table ahead of time, I would use :

if (dataClassesDataContext.MYTABLEXs.SingleOrDefault(m => m.MYTABLEX_ID == MyId)) 
    DoExists();

But as I am getting tables names MYTABLEX and MYTABLEY (and fields names MYTABLEX_ID and MYTABLEY_ID) as strings on the fly, I am trying to build the above filter at runtime.

I can access the table dynamically using :

Type tableType = Type.GetType(incommingtableName); // incommingtableName being looped over MYTABLEX, MYTABLEY , ...
var dbTable = dataClassesDataContext.GetTable(tableType);

But then I am stuck. How can I build a lambda expression that will behave something like :

if (dbTable.SingleOrDefault(m => m.incommingtableName_id == MyId)) 
    DoExists();

Any idea ?


Solution

  • You can build an expression in runtime. And also you would need to have generic version of SingleOrDefault method. Here is example:

    Type tableType = typeof (incommingtableName); // table type
    string idPropertyName = "ID"; // id property name
    int myId = 42; // value for searching
    
    // here we are building lambda expression dynamically. It will be like m => m.ID = 42;
    ParameterExpression param = Expression.Parameter(tableType, "m"); 
    MemberExpression idProperty = Expression.PropertyOrField(param, idPropertyName);
    ConstantExpression constValue = Expression.Constant(myId);
    
    BinaryExpression body = Expression.Equal(idProperty, constValue);
    
    var lambda = Expression.Lambda(body, param);
    
    
    // then we would need to get generic method. As SingleOrDefault is generic method, we are searching for it,
    // and then construct it based on tableType parameter
    
    // in my example i've used CodeFirst context, but it shouldn't matter
    SupplyDepot.DAL.SupplyDepotContext context = new SupplyDepotContext();
    var dbTable = context.Set(tableType);
    
    
    // here we are getting SingleOrDefault<T>(Expression) method and making it as SingleOrDefault<tableType>(Expression)
    var genericSingleOrDefaultMethod =
        typeof (Queryable).GetMethods().First(m => m.Name == "SingleOrDefault" && m.GetParameters().Length == 2);
    var specificSingleOrDefault = genericSingleOrDefaultMethod.MakeGenericMethod(tableType);
    
    // and finally we are exexuting it with constructed lambda
    var result = specificSingleOrDefault.Invoke(null, new object[] { dbTable, lambda });
    

    As possible optimization constructed lambda can be cached, so we wont need to build it each time, but it should work the same