Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic query handler that looks up entity by primary key #28

Open
andregizero opened this issue Apr 21, 2019 · 2 comments
Open

Generic query handler that looks up entity by primary key #28

andregizero opened this issue Apr 21, 2019 · 2 comments
Labels

Comments

@andregizero
Copy link

andregizero commented Apr 21, 2019

I'm using your IQueryHandler pattern with good results. Command and query handlers are basically my data access layer and I would like to always use a query handler when querying the database for something. Even if it is for simply looking up an object by its primary key. I have however been unable to get rid of the use of repositories for one use case: looking up a domain object by its primary key.

I can of course create a specific class that implements IQueryHandler for each domain class I want to be able to look up by primary key, but that seems unnecessary since I can do it in a generic way using a repository.

This is the generic repository I would like to replace:

private readonly MyDbContext context;
private readonly DbSet<T> dbSet;

public EfRepository(MyDbContext dbContext)
{
    this.context = dbContext;
    this.dbSet = dbContext.Set<T>();
}

public async Task<T> GetByIdAsync(params object[] id)
{
    return await dbSet.FindAsync(id);
 }

To replace this I defined the following query class:

public class EntityByPrimaryKeyQuery<T> : IQuery<T> where T : class
{
    public EntityByPrimaryKeyQuery(int id)
    {
        Id = id;
    }

    public int Id { get; set; }
}

I then defined the following query handler class:

public class EntityByPrimaryKeyQueryHandler<T>
    : IQueryHandler<EntityByPrimaryKeyQuery<T>, T>
    where T : class
{
    private readonly MyDbContext dbContext;

    public EntityByPrimaryKeyQueryHandler(MyDbContext dbContext)
    {
        this.dbContext = dbContext;
    }

    public async Task<T> HandleAsync(EntityByPrimaryKeyQuery<T> query)
    {
        var dbSet = dbContext.Set<T>();

        return await dbSet.FindAsync(query.Id);
    }
 }

I use Simple Injector and I normally register all my IQueryHandler implementations with one line:

container.Register(typeof(IQueryHandler<,>), AppDomain.CurrentDomain.GetAssemblies());

I then try to inject an instance of the IQueryHandler in my service MyService for a specific domain class like this:

IQueryHandler<EntityByPrimaryKeyQuery<MyDomainClass>, MyDomainClass> queryHandler

When I do this, Simple Injector gives me the following error:

The constructor of type MyService contains the parameter with name 'queryHandler' and type IQueryHandler<EntityByPrimaryKeyQuery<MyDomainClass>, MyDomainClass> that is not registered. Please ensure IQueryHandler<EntityByPrimaryKeyQuery<MyDomainClass>, MyDomainClass> is registered, or change the constructor of MyService . Note that there exists a registration for a different type IQueryHandler<TQuery, TResult> while the requested type is IQueryHandler`2[[EntityByPrimaryKeyQuery<EntityByPrimaryKeyQuery<MyDomainClass>,MyDomainClass>.'

Is what I am trying to achieve possible? If so, what am I doing wrong?

@dotnetjunkie
Copy link
Owner

This method:

container.Register(typeof(IQueryHandler<,>), AppDomain.CurrentDomain.GetAssemblies());

Has the following behavior (copied from the API documentation):

Registers all concrete, non-generic, public and internal types in the given set of assemblies that implement the given openGenericServiceType

In other words, this method only registers non-generic types, but EntityByPrimaryKeyQueryHandler<T> is generic.

There are multiple ways around this, such as using the Register overload that takes in an IEnumerable<Type> will using Container.GetTypesToRegister to retrieve the types, while supplying a TypesToRegisterOptions with IncludeGenericTypeDefinitions set to true.

The easiest solution, however, is to add the following registration to your configuration:

container.Register(typeof(IQueryHandler<,>), typeof(EntityByPrimaryKeyQueryHandler<>));

This will add an open-generic registration next to all the closed registrations made.

@andregizero
Copy link
Author

Thank you Steven, that works great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants