使用.NETCore进行DAL和连接字符串的依赖注入

时间:2017-03-28 00:10:09

标签: .net dependency-injection asp.net-core

我是使用.NETCore的DI模式的新手,我无法将连接字符串连接到DAL。

我按照this thread中给出的建议,通过接受的答案和随后的评论。

这是我的基类

public class BaseRepository : IRepository<IDataModel>
{
    private readonly IConfiguration config;

    public BaseRepository(IConfiguration config)
    {
        this.config = config;
    }

    public string GetSQLConnectionString()
    {
        return config["Data:DefaultConnetion:ConnectionString"];
    }

这是继承基类

的存储库类的片段
public class PrivacyLevelRepository : BaseRepository, IRepository<PrivacyLevelDM>
{
    public PrivacyLevelRepository(IConfiguration config) : base(config) { }
    public void Add(PrivacyLevelDM dataModel)
    {
         ...
    }
}

这是我的startup.cs

public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services.
        services.AddMvc();
        services.AddScoped<IRepository<IDataModel>>(c => new BaseRepository(Configuration));
    }

但是,在我的服务层中,存储库类的实例化仍然要求将(IConfiguration配置)作为参数传递。

 PrivacyLevelRepository repo = new PrivacyLevelRepository();

如何将IConfiguration直接加载到我的DAL,而不必从Controller&gt;传递它; BLL&gt; DAL。这似乎非常低效,而且不正确。因为DAL应该确定对象的连接,而不是控制器或服务层。他们应该不知道数据源,不是吗?

我认为这很简单,我只是没有在DI / IoC范例中看到,但我无法弄明白。

编辑:我没有使用Entity Framework,而是使用自定义数据层。

Thanx寻求帮助。

4 个答案:

答案 0 :(得分:8)

您可以使用配置框架关注options pattern。这允许您定义保存配置设置(静态类型)的自定义类型,同时仅限于您的实际相关配置。

你可以像这样使用它:

public void ConfigureServices(IServiceCollection services)
{
    // register the `Data:DefaultConnection` configuration section as
    // a configuration for the `DatabaseOptions` type
    services.Configure<DatabaseOptions>(Configuration.GetSection("Data:DefaultConnection"));

    // register your database repository
    // note that we don’t use a custom factory where we create the object ourselves
    services.AddScoped<IRepository<IDataModel>, BaseRepository>();
}

这假定类型为DatabaseOptions

public class DatabaseOptions
{
    public string ConnectionString { get; set; }
}

然后,您可以将DatabaseOptions注入BaseRepository

public class BaseRepository
{
    private readonly DatabaseOptions _options;

    public BaseRepository(IOptions<DatabaseOptions> databaseOptions)
    {
         _options = databaseOptions.Value;
    }
}

当然,如果您有BaseRepository的子类型,您还需要注册这些子类并将选项传递给基类:

// register the repository as well in the `ConfigureServices` method
services.AddScoped<PrivacyLevelRepository>();
public class PrivacyLevelRepository : BaseRepository, IRepository<PrivacyLevelDM>
{
    public PrivacyLevelRepository(IOptions<DatabaseOptions> databaseOptions)
        : base(databaseOptions)
    { }
}
  

我正在实例化并使用像我一直拥有的回购。我不知道如何使用我没有实例化的类。我如何让这个对象知道它取决于PrivacyLevelRepository

PrivacyLevelRepository repo = new PrivacyLevelRepository();
returnValue = repo.GetAllByDomainID(DomainID).ToList();
return returnValue;

您似乎还不了解依赖注入背后的想法。依赖于其基本原则Inversion of Control的依赖注入简单地说是避免使用new来创建对象。您没有主动依赖实现(在您的示例中为PrivacyLevelRepository),而是放弃了责任,只依靠外部系统为您提供所需的依赖项。

因此,不是创建新的PrivacyLevelRepository,而是注入由其他地方创建的实例。这就失去了对依赖项实现的耦合。一个非常实际的例子是PrivacyLevelRepository取决于IOptions<DatabaseOptions>的方式。作为该存储库的使用者,您不需要关心如何使这样的对象能够创建存储库实例。您甚至不需要知道如何创建存储库实例。

因此,PrivacyLevelRepository的消费者应该遵循与存储库本身相同的想法:存储库不知道如何获取这些数据库选项;它只取决于构造实体传递这样的对象。而我的消费者,我假设一个控制器,也应该这样做:

public class MyController
{
    private readonly PrivacyLevelRepository _privacyLevelRepository;

    public MyController(PrivacyLevelRepository privacyLevelRepository)
    {
         // instead of *creating* a repository, we just expect to get one
         _privacyLevelRepository = privacyLevelRepository;
    }

    public IActionResult SomeRoute()
    {
         var domainId = "whatever";
         var data = _privacyLevelRepository.GetAllByDomainID(domainId).ToList();
         return View(data);
    }
}

当然,某些东西必须在某些时候创建依赖项。但是如果你完全接受依赖注入 - 哪个ASP.NET Core不仅非常容易,而且主动需要你这样做才能完全工作 - 那么你不需要关心那个部分。您只需在ConfigureServices方法中注册类型,然后期望在您需要的地方满足相关性。

有关详细信息,请务必查看文档的dependency injection chapter

答案 1 :(得分:5)

您不应该将IConfiguration 注入所有到您的课程中。 IConfiguration允许访问所有配置值,而类只需要一个(或其中一些)。注入IConfiguration是等同于Service Locator anti-pattern的配置(但是用于解析配置值)。它隐藏了消费者使用的实际配置值,使得类更难以使用和测试。

最重要的是,此模型使得验证配置文件的正确性变得更加困难,因为只有在应用程序中第一次请求时,才会验证各个配置值,这可能是许多鼠标“点击”进入申请表。

解决方案是在启动时加载并验证配置值 并仅注入一个类所需的配置值,仅此而已。这允许系统故障快速,并从类的API中清楚地了解它需要什么配置值。显然,您可以将配置值组合到一个Value Object中,而.NET Core使这更加简单,这非常好。

您应该防止的另一件事是使用基类。基类经常变得不断变化,并且越来越多的代码块带有辅助方法和交叉关注点。由于对基类的严重依赖,它们的衍生物变得更难测试。

当您将连接字符串直接注入PrivacyLevelRepository时,不需要具有GetSQLConnectionString的基类,因为存储库已经具有可用的连接字符串。可能还有其他原因导致您拥有此基类,例如因为您想要记录或实现安全功能,但我的建议是不要使用基类。取而代之的是使用装饰和拦截,因为它可以保持“派生”不会忽视这些横切关注的问题,甚至允许更加模块化和灵活的系统。

<强>更新

这是配置它的方法

string conStr = config["Data:DefaultConnetion:ConnectionString"];

services.AddScoped<IRepository<IDataModel>>(c => new PrivacyLevelRepository(conStr));

无论你做什么,都不要让你的应用程序组件依赖于IOptions<T>,因为这会产生一些不良后果,如here所述。

答案 2 :(得分:1)

正如Steven所提到的,不要让你的应用程序组件依赖IOptions<T>

从IConfigurationRoot访问连接字符串的更合适方法如下:

string connectionString = configuration.GetConnectionString("DefaultConnection"); //substitute "DefaultConnection" for your named connection.

答案 3 :(得分:0)

定义一个具有您所需属性的类:

public class ConfHelper
{
   public string ConnString { get; set; }
}

比在Startup.Configure Services中,您可以执行以下操作以从Configuration中读取设置:

services.AddSingleton(new ConfHelper() {ConnString = Configuration["ConnectionStrings:Default"]});

您可以将ConnectionString保留在appSettings.json中

"ConnectionStrings": {
    "Default": "Persist Security Info=False;User ID=USER;Password=PASS;Initial Catalog=DBNAME;Data Source=localhost;"
}

您可以期望该平台在代码中的任何位置将对象注入构造函数中。

public class HomeController : ControllerBase {
    private readonly ConfHelper _conf;
    public HomeController(ConfHelper conf) { _conf = conf; }
}

除了调用services.AddSingleton,您还可以根据自己的约束将服务注册为Transient和Scoped。