【问题标题】:ASP.Net Core Open Partial Generic Dependency InjectionASP.Net Core 开放部分泛型依赖注入
【发布时间】:2018-07-25 01:08:07
【问题描述】:

我想使用开放的通用实现和接口为 DI 注册以下项目。我知道下面的例子不起作用,以及我用MakeGenericType 或GetGenericArguments 尝试过的其他组合。我想简单地调用AddRepository<MyDbContext>,然后能够将我的实现注入到类中,而无需显式注册我正在使用的类型。

界面

public interface IRepository<TEntity>
{   
}

实施

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

注册

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

        return services;
    }
}

【问题讨论】:

  • 只有一个Repository 实现吗?还有一个 DbContext,还是多个上下文?
  • 根据注入时指定的任何类型,单个上下文将与多个存储库实现一起使用。

标签: asp.net-core dependency-injection


【解决方案1】:

依赖注入容器Microsoft.Extensions.DependencyInjection 及其抽象层不支持开放的泛型工厂。所以你通常无法在那里实现你想做的事情。还有no support planned。

与许多其他依赖注入相关的功能不同,这也无法通过提供正确的包装器或工厂类型来修补。所以你实际上必须在这里改变你的设计。

由于您要解析IRepository&lt;TEntity&gt;,而唯一的方法是注册一个等效的开放泛型类型,因此您必须有一些类型Repository&lt;TEntity&gt; 来实现您的存储库。这使得无法从泛型类型参数中检索数据库上下文类型,因此您必须在此处使用不同的方式。

你有不同的选择来做到这一点。例如,您可以使用上下文类型配置您的 Repository&lt;TEntity&gt;(例如使用 M.E.Options)并使其动态解析 Repository&lt;TEntity, TContext&gt;。但是由于您可以实际控制数据库上下文,我建议您添加标记接口或为上下文引入另一种类型,然后您可以在容器中注册:

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

public class MyDbContext : DbContext, IDbContext
{ … }

然后,您的扩展方法可能如下所示:

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

当然,这会改变您的 Repository 实现的工作方式,但我实际上并不认为您需要知道 TContext 类型,而不是注入数据库上下文类型。所以这可能仍然对你有用。


话虽如此,我也同意 Chris Pratt 的观点,你可能不需要这个。你说你想引入存储库,因为“为每个实体编写存储和实现是一项耗时的任务”,但你真的应该考虑一下你是否真的需要 .通用存储库的功能非常有限,主要意味着您只是在执行 CRUD 操作。但这正是 DbContext 和 DbSet&lt;T&gt; 已经在做的事情:

此外,DbContext 是一个“工作单元”,DbSet&lt;T&gt; 是一个 IQueryable&lt;T&gt;,与通用存储库相比,它为您提供了更多的控制权和权力。

【讨论】:

  • 我认为您的建议最适合我的需要,谢谢!
【解决方案2】:

您不能有部分打开的通用引用。要么全有,要么全无。换句话说,你可以试试:

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

但是,如果这不起作用,您可能需要在 AddRepository 方法中添加一个类型参数:

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

当然,我认为这打破了您最终想要实现的目标:一次性为所有实体类型注册存储库。您始终可以使用一些反射来查找程序集中的所有实体(它们需要共享一些共同点:基类、接口等),然后枚举它们并使用反射在您的服务集合上调用 AddScoped每个。

说了这么多,在这里你能做的最好的事情就是把这一切都扔掉。您不需要存储库。 EF 已经实现了存储库和工作单元模式。当您使用像 EF 这样的 ORM 时,您实际上是在使 that 成为您的数据层,而不是您创建的自定义类库。将您自己的自定义包装器放在 EF 周围不仅会为您的代码增加熵(更多需要维护,更多需要测试,而且不能破坏),而且它还会在许多情况下弄乱 EF 的工作方式,导致效率降低最好的情况,在最坏的情况下直接将错误引入您的应用程序。

【讨论】:

  • 谢谢,这是我的怀疑,我一直在探索使用类似于 ASP.NET Core Logging 工作方式的工厂的想法。对于您关于使用存储库的另一点,我不反对,但我仍然发现它们很有用。我通常发现自己编写了相同的模式,使用 Store 和 Manager(即 IPersonStore、PersonStore、PersonManager)来抽象出数据层逻辑。为每个实体编写存储和实现是一项耗时的任务,可以通过使用存储库代替存储来加快速度。
  • 好吧,这又不是真的。最快和最简单的方法就是直接使用你的上下文。如果你要在它周围包裹一些东西,那么这样做需要有一个好的理由。以身份为例;它有一个 UserStore、UserManager、SignInManager 等。但是,这是因为 1) 存储是抽象的(可能是 EF,也可能不是)和 2) 管理器在 EF 之上添加了辅助功能。如果你的回购只是吐出你可以直接从你的DbSet 检索的实体,它是没用的,应该消失。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-17
  • 1970-01-01
  • 2019-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多