1

This question has the same context as that one. I'm copy-pasting the exposition part because it's relevant for both:

We have a project that uses legacy databases with which EntityFramework does not work.

So we have started building a poor-man EntityFramework, with a base class CustomBaseTable from which entity classes derive. Another class, CustomQueryBuilder, has methods that build SQL queries : BuildSelectQuery(CustomBaseTable p_objEntity) and so on.

Our first version builds queries quite nicely, but we're using rather crude objects that aren't really flexible (details withheld to save space).

I recently realized that using Expression objects would be much more efficient and flexible.

So I'd like to add a method to CustomBaseTable, that would work more or less like Where():

    EntityTable z_objEntity = new EntityTable();
    z_objEntity.CustomWhere(t => t.Field1 == Value1);
    CustomQueryBuilder z_objBuilder = new CustomQueryBuilder(DBTypeEnum.DataBaseType);
    string z_strQuery = z_objBuilder.BuildSelectQuery(z_objEntity);

End exposition.

I want CustomWhere() to store that expression in a property and use the latter in BuildSelectQuery(CustomBaseTable p_objEntity), to parse it into a keen SQL Where clause. (Of course it'll happen in a separate method that Update and Delete queries will use too.)

I've written a property in CustomBaseTable:

        public Expression<Func<CustomBaseTable, bool>> WhereClauseExpression

So CustomWhere() should do something like

        public static void CustomWhere<T>(this T z_objTable, Expression<Func<T, bool>> p_expWhereClause) where T : CustomBaseTable
        {
            z_objTable.WhereClauseExpression = p_expWhereClause;
        }

However, p_expWhereClause, being a Expression<Func<T, bool>> object, can't be converted to Expression<Func<CustomBaseTable, bool>>.

I've tried creating an ICustomBaseTable from which CustomBaseTable derives and make WhereClauseExpression a Expression<Func<ICustomBaseTable, bool>>, with the same results.

Inspired by this question, I tried

            z_objTable.WhereClauseExpression = LambdaExpression.Lambda<Func<CustomBaseTable, bool>>(p_expWhereClause.Body, p_expWhereClause.Parameters);

Again, the conversion failed.

I don't think I can make WhereClauseExpression a Expression<Func<T, bool>> without declaring T in CustomBaseTable<T>, which I don't want either.

Of course, a workaround is to have a method in CustomQueryBuilder that parses the expression into a Where clause, then store the latter in a WhereClause String property, but I'd rather generate all the SQL code in one go; it strikes me as cleaner.

Is there a way to store that Expression into a property, that will work for any derived class?

Jean-David Lanz
  • 865
  • 9
  • 18
  • Does this answer your question? [Covariance and contravariance real world example](https://stackoverflow.com/questions/2662369/covariance-and-contravariance-real-world-example) – Charlieface Mar 03 '21 at 18:12
  • If all else fails, storing it in `object` and casting it back as necessary always works. This of course requires that you know the *exact type*, and that also provides a clue as to why your attempt will not work: a `Func where T : CustomBaseTable` is just not the same thing at runtime as a `Func` for a derived `T`. You can turn it into one by inserting a cast *into the expression itself*, but that means 1) building a new delegate/expression based on the existing one and 2) losing the exact type, which may or may not matter. – Jeroen Mostert Mar 03 '21 at 18:14
  • @Charlieface: after reading it, I liloed "Expression", "Func", and "covariance" and chanced upon [Variance in Expression>](https://stackoverflow.com/questions/11452614/variance-in-expressionfunct-bool). Which says out loud that covariance is not supported for `Expression` types. At least it wasn't in 2012. – Jean-David Lanz Mar 04 '21 at 07:39
  • `Expression` is a class, which can't be variant, so you would need to make an interface for it which is variant – Charlieface Mar 04 '21 at 09:31
  • @Charlieface: I tried that (the sentence starting with "I've tried creating an `ICustomBaseTable`[...]") and it didn't work out either, but maybe I got something wrong. – Jean-David Lanz Mar 04 '21 at 15:54
  • And did you make it covariant? – Charlieface Mar 04 '21 at 16:22
  • I don't think I did. But eventually I went a totally different way. See the answer. – Jean-David Lanz Mar 08 '21 at 18:27

1 Answers1

1

In the end, the answer is: I couldn't... but it didn't matter.

As mentioned in the question, I tried

        public static void CustomWhere<T>(this T z_objTable, Expression<Func<T, bool>> p_expWhereClause) where T : CustomBaseTable
        {
            z_objTable.WhereClauseExpression = LambdaExpression.Lambda<Func<CustomBaseTable, bool>>(p_expWhereClause.Body, p_expWhereClause.Parameters);
        }

It still didn't work because the parameter in Parameters was type T instead of CustomBaseTable.

So after a bit of fiddling, CustomWhere() now looks like

        public static void CustomWhere<T>(this T z_objTable, Expression<Func<T, bool>> p_expWhereClause) where T : CustomBaseTable
        {
            z_objTable.WhereClauseExpression = Expression.Lambda<Func<CustomBaseTable, bool>>(p_expWhereClause.Body, p_expWhereClause.Parameters.Select(p => Expression.Parameter(typeof(CustomBaseTable), p.Name)));
        }

Not only does the conversion work perfectly, but even better, the Expression objects inside z_objTable.WhereClauseExpression, starting with Body, use T objects and not CustomBaseTable. What I needed was for the sub-expressions to use T, and that's what I got in the end.

Jean-David Lanz
  • 865
  • 9
  • 18