3

I am trying to use the template pattern and C# generics to write a utility that will list the entities in the database for any DbSet within Any DbContext. I presume I need three generics:

public class lister<TDbSet, TContext, TEntity>

    where TDbSet   : DbSet<IPEntity>
    where TContext : DbContext
    were  TEntity  : IPEntity

(IPEntity is an abstract base class for all of our entity classes.) Everything seems to be happy except for trying to write a LINQ expression to get the result set. Since "TDbSwt" is actually a MEMBER of TContext, I cannot figure out if LINQ will let you do something like:

from x in TContext.TDbSet select x

It certainly does not like THAT line, whether or not I prefix TDbSet with TContext or not.

Anyone know how I could set this up? right now I have separate (very small, but still one for each entity) class for the entity and LINQ specifics, but as we grow from dozens to hundreds to perhaps thousands of entities, I would like to find a more compact and elegant solution.

Thanks.

6
  • 2
    What's the error you get? It looks like you're trying to use the type parameters as properties. You need to add a property of type TDbSet to the lister class and use an instance of that in your query. Commented May 1, 2014 at 14:53
  • What Lee said is correct. For example, if TEntity was MyEntity, TDbSet was DBSet<MyEntity> and TContext was DbContext, then your linq statement would read as from x in DbContext.DbSet<MyEntity> select x, which doesn't make sense. Commented May 1, 2014 at 15:56
  • As a side-note along with my answer, I think you need to review the purpose of generics and understand what can be done without generic parameters, just using simple polymorphism. For example, do you understand why there's no point to a generic parameter to a class if that generic type doesn't appear anywhere in the signature of a public member on that class? (Unless you use reflection or cast from the generic type to a concrete one, both of which are generally poor practice) Commented May 1, 2014 at 17:33
  • We want to have a test utility that can automatically exercise (do CRUD) on any entity we create, which includes validation attributes and such, without having to add even a very small extra class to a template-pattern utility for every new entity (which is what we have now, I am trying to replace it.) We would also very much like to do that with LINQ. If you know of a better way to minimize this work without a single class with generics of a ton of reflection code, I'm all ears. Thanks, Peter Commented May 1, 2014 at 18:16
  • @PeterHowe Could you explain in detail what requirement you have which the answer I posted doesn't fulfill? Commented May 1, 2014 at 21:31

2 Answers 2

3
from x in TContext.TDbSet select x

There's two reasons this won't work:

  1. TContext is a type, but the member you're trying to call on it isn't static, so you need an instance of TContext
  2. TDbSet is also a type. Just because TContext happens to have a member with the same name (or even the same type) as TDbSet doesn't mean you can start using TDbSet as a member name rather than a type.

What you want is probably something like this:

public class lister<TEntity>
    where  TEntity  : IPEntity
{
    private DbContext _context;
    private DbSet<TEntity> Set
    {
        get { return _context.Set<TEntity>(); }
    }

    public lister(DbContext context)
    {
        _context = context;
    }
}

Now within that class you can write:

from x in Set select x

And it will work as you expected.

Alternatively, you might want lister to itself instantiate the context. This is less likely to be what you should be doing, but I can't be sure without seeing your overall design. In this case, you'd instead want:

public class lister<TEntity, TContext> 
    where  TEntity  : IPEntity
    where  TContext  : DbContext, new()
{
    private TContext _context;
    private DbSet<TEntity> Set
    {
        get { return _context.Set<TEntity>(); }
    }

    public lister()
    {
        _context = new TContext();
    }
}

There may be some variation. You may want to pass in or instantiate your context from another method rather than the constructor, for example.

Sign up to request clarification or add additional context in comments.

Comments

0

As you can see you cannot use a dynamic name for a member with generics. This would not be statically verifiable (like the C# type system likes to be).

Fortunately, DbContext has a member Set<T>().

public class lister<TDbSet, TEntity>

    where TDbSet   : DbSet<TEntity>
    were  TEntity  : IPEntity

static DbSet<TEntity> Read(DbContext ctx) { return ctx.Set<TEntity>(); }

As you can see the context does not need to be generic.

3 Comments

The problem is that I don't know which DbContext will be used, as there are multiple bounded contexts with potentially overlapping DbSet properties. I want to just instantiate the class and have everything set correctly. I'm not understanding the static method you included. I need to discover the DbContext, not the DbSet.
@PeterHowe The DbContext either needs to be passed into the lister class (e.g. as a parameter to its constructor), or instantiated within the class. Which are you wanting to do?
It doesn't matter which context it is. Just pass in any context. Read does not care. Read does what you wanted: "a utility that will list the entities in the database for any DbSet within Any DbContext". Did I misunderstand? Your question is not extremely clear. If this does not help you, please clarify thoroughly.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.