2

I would like to register the following items for DI using an open generic implementation and interface. I know the following example will not work, as well as other combinations I've tried with MakeGenericType, or GetGenericArguments. I would like to simply call AddRepository<MyDbContext> and then be able to inject my implementation into classes without explicitly having to register the type I am using.

Interface

public interface IRepository<TEntity>
{   
}

Implementation

public class Repository<TEntity, TContext> : IRepository<TEntity>
    where TEntity : class
    where TContext : DbContext
{
}

Registration

public static class RepositoryServiceCollectionExtensions
{
    public static IServiceCollection AddRepository<TContext>(
        this IServiceCollection services) where TContext : DbContext
    {
        services.TryAddScoped(
            typeof(IRepository<>),
            typeof(Repository< , TContext>));

        return services;
    }
}
2
  • Is there only a single Repository implementation? And a single DbContext, or multiple contexts? Commented Jul 24, 2018 at 7:45
  • There would a single context being used with multiple Repository implementations based on whatever type is specified when injected. Commented Jul 24, 2018 at 13:37

2 Answers 2

4

The dependency injection container Microsoft.Extensions.DependencyInjection and its abstraction layer does not support open generic factories. So you generally cannot achieve what you would like to do there. There’s also no support planned.

Unlike many those other dependency injection related features, this is also not really possible to patch by just providing the right wrapper or factory types. So you will actually have to change your design here.

Since you want to resolve IRepository<TEntity> and the only way to do this is by registering an equivalent open generic type, you will have to have some type Repository<TEntity> that implements your repository. That makes it impossible to retrieve the database context type from the generic type argument, so you will have to use a different way here.

You have different options to do that. For example, you could configure your Repository<TEntity> (e.g. using M.E.Options) with the context type and make that resolve the Repository<TEntity, TContext> dynamically. But since you have actual control over your database context, I would suggest either adding a marker interface or introducing another type for the context which you can then register with the container:

public class Repository<TEntity> : IRepository<TEntity>
{
    public Repository(IDbContext dbContextFactory)
    { … }
}

public class MyDbContext : DbContext, IDbContext
{ … }

Then, your extension method could look like this:

public static IServiceCollection AddRepository<TContext>(this IServiceCollection services)
    where TContext : DbContext, IDbContext
{
    services.AddTransient(typeof(IDbContext), sp => sp.GetService<TContext>());
    services.TryAddScoped(typeof(IRepository<>), typeof(Repository<>));
    return services;
}

Of course, this changes how your Repository implementation works, but I don’t actually assume that you need to know the TContext type other than to inject the database context type. So this will probably still work for you.


That being said, I have too agree with Chris Pratt, that you probably don’t need this. You say that you want to introduce the repository, because “coding stores and implementations for every entity is a time consuming task” but you should really think about whether you actually need that. A generic repository is very limited in what it can do, and mostly means that you are doing just CRUD operations. But exactly that is what DbContext and DbSet<T> already do:

In addition, DbContext is a “unit of work” and DbSet<T> is an IQueryable<T> which gives you a lot more control and power than a generic repository could possible give you.

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

1 Comment

I think your suggestion will best suite my needs, thank you!
2

You cannot have a partially open generic reference. It's all or nothing. In other words, you can try:

services.TryAddScoped(
        typeof(IRepository<>),
        typeof(Repository<,>));

But, if that doesn't work, you'll likely need to add a type param to your AddRepository method:

public static IServiceCollection AddRepository<TEntity, TContext>(this IServiceCollection services)
    where TEntity : class
    where TContext : DbContext
{
    services.TryAddScoped(
        typeof(IRepository<TEntity>),
        typeof(Repository<TEntity, TContext>));

    return services;
}

Of course, I think that breaks what you're ultimately trying to achieve here: registering repositories for all the entity types in one go. You can always use a bit of reflection find all entities in your assembly (they would need to share something in common: base class, interface, etc.) and then enumerate over them and use reflection to call AddScoped on your service collection for each.

All that said, the best thing you can do here is to actually throw all this away. You don't need the repositories. EF already implements the repository and unit of work patterns. When you use an ORM like EF, you're essentially making that your data layer instead of a custom class library you create. Putting you own custom wrapper around EF not only adds entropy to your code (more to maintain, more to test, and more than can break), but it can also mess up the way EF works in many cases, leading to less efficiency in the best cases and outright introducing bugs into your application in the worst cases.

2 Comments

Thanks, this is what I suspected and I have been exploring the idea of using a factory similar to how ASP.NET Core Logging works. To your other point regarding the use of Repositories, I don’t disagree, however I still find them useful. I typically find myself writing the same pattern of using a Store and Manager (i.e. IPersonStore, PersonStore, PersonManager) to abstract away data layer logic. Coding stores and implementations for every entity is a time consuming task and can be sped up a bit by using a Repository in place of the store.
Well, again, that's not actually true. The fastest and easiest path is going to be just using your context directly. If you're going to wrap something around it, there needs to be a good reason to do so. Take Identity for example; it has a UserStore, UserManager, SignInManager, etc. However, that's because 1) the store is abstracted (it may be EF or it may not) and 2) the managers add helper functionality over top of EF. If you repo is just spitting out entities you could retrieve directly from your DbSet, it's useless, and should go away.

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.