我是 DI 模式的新手。NETCore,我在将连接字符串连接到我的 DAL 时遇到问题。
我通过接受的答案和随后的评论遵循了此线程中给出的建议。
这是我的基类
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)
{
...
}
}
这是我的创业公司.cs
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddScoped<IRepository<IDataModel>>(c => new BaseRepository(Configuration));
}
但是,在我的服务层中,存储库类的实例化仍然要求将(IConfiguration config)作为参数传递。
PrivacyLevelRepository repo = new PrivacyLevelRepository();
如何将 IConfigur 直接加载到我的 DAL,而无需将其从控制器> BLL 传递> DAL。 这似乎效率极低,而且不正确。 因为 DAL 应该确定对象的连接,而不是控制器或服务层的连接。 他们应该与数据源无关,不是吗?
我认为这很简单,我只是在 DI/IoC 范式中看不到,但我无法弄清楚。
编辑:我没有使用实体框架,而是自定义数据层。
感谢任何帮助。
您可以遵循配置框架的选项模式。这允许您定义一个自定义类型,该类型保存您的配置设置(静态类型),同时仅限于实际的相关配置。
你可以像这样使用它:
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;
您似乎还不了解依赖注入背后的想法。依赖注入及其基本原则控制反转简单地说是避免使用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
方法中注册类型,然后期望在需要它们的地方满足依赖项。
有关更多信息,您绝对应该查看文档的依赖注入章节。
你根本不应该将IConfiguration
注入到你的类中。IConfiguration
允许访问所有配置值,而类只需要一个(或其中几个)。注入IConfiguration
是服务定位器反模式的配置等效项(但用于解析配置值)。它对使用者隐藏实际使用的配置值,并使类更难使用和测试。
最重要的是,此模型使验证配置文件的正确性变得更加困难,因为只有在应用程序中首次请求单个配置值时才进行验证,这可能是多次鼠标"点击"到应用程序中。
对此的解决方案是在启动时加载并验证配置值,并仅注入一个类所需的配置值,仅此而已。这允许系统快速失败,并从类的 API 中非常清楚地表明它需要什么配置值。显然,您可以将配置值打包到单个值对象中,最新的 .NET 版本使这比以前简单得多,这真的很好。
您应该防止的另一件事是使用基类。基类通常成为具有帮助程序方法和横切关注点的不断变化和不断增长的代码块。它们的导数变得更难测试,因为对基类的硬依赖性。
当你将连接字符串直接注入到PrivacyLevelRepository
时,不需要有一个带有GetSQLConnectionString
的基类,因为仓库已经有可用的连接字符串。拥有此基类可能还有其他原因,例如,因为您想进行日志记录或实现安全功能,但我的建议是不要为此使用基类。装饰和拦截是更有效的方法,因为它们允许全班忘记这些横切问题,甚至允许一个更加模块化和灵活的系统。
更新
这是配置它的方法
string conStr = config["Data:DefaultConnetion:ConnectionString"];
services.AddScoped<IRepository<IDataModel>>(c => new PrivacyLevelRepository(conStr));
我还建议防止应用程序组件依赖于内置IOptions<T>
接口,因为这会产生相当不方便的后果,如此处所述。
正如 Steven 所提到的,不要让您的应用程序组件依赖于IOptions<T>
。
从 IConfigurationRoot 访问连接字符串的更合适的方法是按如下方式完成:
string connectionString = configuration.GetConnectionString("DefaultConnection"); //substitute "DefaultConnection" for your named connection.
其中"DefaultConnection"是appsettings.json中连接字符串的对象键
定义一个具有所需属性的类:
public class ConfHelper
{
public string ConnString { get; set; }
}
在 Startup.Configure Services 中,您可以执行以下操作来从配置中读取设置:
services.AddSingleton(new ConfHelper() {ConnString = Configuration["ConnectionStrings:Default"]});
您可以在 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; }
}
除了呼叫服务。添加单例 还可以根据约束将服务注册为"瞬态"和"作用域"。