Home > database >  Generic virtual method and concrete Implementation?
Generic virtual method and concrete Implementation?

Time:02-11

I'm currently working on a piece of code, where Generics are driving me crazy. Maybe somebody can point out the issue.

All our DatabaseEntities have common Interfaces, one for example is IDatabaseEntityWithId.

let's say there is EntityA : IDatabaseEntityWithId. We have different, non-generic Forms / Controls. We also have Controls that should be shared, and do their Job independent of the concrete Type.

Now, coming from Java this is quite easy there, cause every control could just use for example List<?> and pass / Process the list without actually caring what is inside.

So, our Search-Control should also be able to "deal" with List<T> and IQueryable<T> without caring what T actually is.

The base implementation for an EntityList has the following methods, which our Search should utilize to generate the results, do pagination, etc.

public class EntityListControl{
  public virtual IQueryable<T> BaseQuery<T>(DBContext dbContext) where T : IDBEntityWithId => throw new NotImplementedException();
  public virtual List<string> AutocompleteSuggestionsQuickSearch() => throw new NotImplementedException();
  public virtual List<IDBEntityWithId> DoQuickSearch(string reference) => throw new NotImplementedException();
  public virtual IQueryable<T> DoTaggedSearchExtensions<T>(IQueryable<T> query, DbContext dbContext) where T : IDBEntityWithId  => throw new NotImplementedException();
}

The SearchControl has a reference to the currently loaded EntityListControl and basically should just act as orchestrator for the concrete methods, i.e. shortened it looks like:

class SearchControl{
   public void DoSearch(){
      String[] searchTags = this.SearchField.Text.Split(' ');
      using (DbContext db = DbContext.Factory()){
          var query = this.EntityListControl.BaseQuery<IDBEntityWithId>(db);
          
          //do other stuff.

          query = this.EntityListControl.DoTaggedSearchExtension<IDBEntityWithID>(query, db);
          
         //do other stuff

          List<IDBEntityWithId> result = query.Take(X).ToList();

         //do other stuff

          this.EntityListView.FactoryRows(result);
      }

You'll get the idea.

Now, for one of the EntityListControl-implementations, these methods might look like:

public class EntityList: UserControl{
    ...
}
...

public class EntityListA : EntityList{

   public override IQueryable<EntityA> BaseQuery<EntityA>(DbContext db){
       return db.EntityA.Include("EntityB").Include("EntityC");
   }

   public IQueryable<EntityA> DoTaggedSearchExtension<EntityA>(IQueryable<EntityA> query, DbContext db){
      //Problem is now here...
      return query.Where(x => x.PropertyOfEntityA == true);
   }
}

The Problem is now in the concrete implementation of DoTaggedSearchExtension. Visual Studio here clearly lines out, that T is EntityA and the query is IQueryable<EntityA> - but refuses to accept any properties that don't belong to IDBEntityWithId. Why is that?

The error Message even reads "EntityA does not contain a definition for 'property'...." - which is wrong, it is there... IntelliSense just proposes properties of the common interface.

Obviously in the generic EntityListControl, I can not be more concrete on the type of T, IBaseEntityWithId is one of the common interfaces I can ensure for all entities.

Ofc, I could leave away the whole generic-part and JUST work with interfaces, casting them to the actual type in the implementations, but that's not the point, isn't it?

Any suggestion on how to fix this particular example?

CodePudding user response:

The definition of DoTaggedSearchExtension identifies that T is a IDBEntityWithId

C# is strongly typed so it only allows methods defined on IDBEntityWithId

CodePudding user response:

You want the base class to be generic, not the methods!

public class EntityList<T> where T : IDBEntityWithId
{
  public virtual IQueryable<T> BaseQuery(DBContext dbContext) => throw new NotImplementedException();
  public virtual List<string> AutocompleteSuggestionsQuickSearch() => throw new NotImplementedException();
  public virtual List<IDBEntityWithId> DoQuickSearch(string reference) => throw new NotImplementedException();
  public virtual IQueryable<T> DoTaggedSearchExtensions(IQueryable<T> query, DbContext dbContext) => throw new NotImplementedException();
}

When you then implement this, you get the concrete types you expect

public class EntityListA : EntityList<EntityA>{

   public override IQueryable<EntityA> BaseQuery(DbContext db){
       return db.EntityA.Include("EntityB").Include("EntityC");
   }

   public IQueryable<EntityA> DoTaggedSearchExtension(IQueryable<EntityA> query, DbContext db){
      //Problem is now here... (Not any more it isnt!)
      return query.Where(x => x.PropertyOfEntityA == true);
   }
}

Live example for demo: https://dotnetfiddle.net/SiICNV (Compiles fine but had to comment out the EF-specific stuff)

  • Related