2

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);

Now, I'm hitting a snag with declaring CustomWhere(). I've tried several approaches:

    public class CustomBaseTable
    {
        public void CustomWhere1<T>(Expression<Func<T, bool>> p_expWhereClause) where T : CustomBaseTable
        public void CustomWhere2<T>(this T z_objTable, Expression<Func<T, bool>> p_expWhereClause) where T : CustomBaseTable
    }

    public static class CustomBaseTableExtension
    {
        public static void CustomWhere3<T>(this T z_objTable, Expression<Func<T, bool>> p_expWhereClause) where T : CustomBaseTable
    }

However, each one has a flaw as far as I'm concerned:

  • CustomWhere1 requires specifying <EntityTable> each time it's called, which takes up space and is redundant since the object calling the method has that same type: z_objEntity.CustomWhere<EntityTable>(t => t.Field1 == Value1);
  • CustomWhere2 requires passing the very object it's called on, also takes up space and is redundant too: z_objEntity.CustomWhere(z_objEntity, t => t.Field1 == Value1);
  • CustomWhere3 neatly avoids both flaws, but apparently requires creating a separate extension class. I'll go with it if I need to, but I fail to see why it's needed.

Is there a way to have that simple calling syntax without creating that Extension class?

Jean-David Lanz
  • 865
  • 9
  • 18

1 Answers1

2

Yes. You could use the curiously recurring template pattern, define EntityTable : CustomBaseTable<EntityTable>, and, thus, have EntityTable available as a generic parameter. Here is a minimal example (fiddle):

using System;
using System.Linq.Expressions;
                    
public class Program
{
    public static void Main()
    {
        var Value1 = "value1";
        var z_objEntity = new EntityTable();
            
        z_objEntity.CustomWhere1(t => t.Field1 == Value1); // compiles!
    }
    
    public class CustomBaseTable<T>
    {
         public void CustomWhere1(Expression<Func<T, bool>> p_expWhereClause)
         { 
             throw new NotImplementedException();
         }
    }
    
    public class EntityTable : CustomBaseTable<EntityTable>
    {
        public string Field1 { get; set; }
    }
}

That having been said, I do believe that the extension class is the simplest solution for your problem.


CustomWhere3 neatly avoid both flaws, but apparently requires creating a separate extension class. I'll go with it if I need to, but I fail to see why it's needed.

It's needed because you

  • want to define CustomWhere in your base class,
  • CustomWhere has a parameter whose type depends on the concrete derived class, and
  • C# does not (yet) have a "this" type.

Thus, we either need to

  • make the derived class available to the base class (which is what the code in my answer does) or
  • define CustomWhere somewhere else (which is what your extension method example CustomWhere3 does).
Heinzi
  • 167,459
  • 57
  • 363
  • 519
  • Good Lord, curious indeed! I don't think it would be workable for us, as I don't want to redeclare the existing `EntityTable` classes, and at any rate I agree that it looks less weird. Thanks! – Jean-David Lanz Mar 03 '21 at 18:10