I reproduced a way to access the database with .net Framework but it doesn't work in .net Core. I found a way to fix it and followed what was said but I get the error that the connectionString is not initialized. Therefore, I don't know how to get it working.
Code I'm using:
public class DataAccess
{
private string _connectionString;
public DataAccess(string connectionString)
{
_connectionString = connectionString;
}
public List<PropertyModel> LoadData()
{
var data = new List<PropertyModel>();
using(IDbConnection cnn = new SqlConnection(_connectionString))
{
data = cnn.Query<PropertyModel>(#"select *
from dbo.PropertyModel;").ToList();
}
return data;
}
}
In Controller:
private DataAccess data;
public PropertyController(IOptions<ConnectionConfig> connectionConfig)
{
var connection = connectionConfig.Value;
string connectionString = connection.Analysis;
data = new DataAccess(connectionString);
}
public IActionResult Index()
{
var test = data.LoadData();
return View();
}
In StartUp:
services.Configure<ConnectionConfig
(Configuration.GetSection("MVCCoreAppDB"));
And I created a POCO class:
public class ConnectionConfig
{
public string Analysis { get; set; }
}
I followed this.
appsettings.json:
"ConnectionStrings": {
"MVCCoreAppDB": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=MVCCoreAppDB;Integrated Security=True;Connect Timeout=60;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"},
You are calling the wrong section from configuration.
services.Configure<ConnectionConfig>(Configuration.GetSection("ConnectionStrings"));
You would also need to update the model
public class ConnectionConfig {
public string MVCCoreAppDB { get; set; }
}
That said I would suggest you change your design to populate the model upfront and register it with the service collection for injection
In StartUp:
services.AddScoped<DataAccess>(_ =>
new DataAccess(Configuration.GetConnectionString("MVCCoreAppDB"))
);
And inject the data access explicitly into the controller
private readonly DataAccess data;
public PropertyController(DataAccess data) {
this.data = data;
}
public IActionResult Index() {
var test = data.LoadData();
return View();
}
Reference Explicit Dependencies Principle
Related
I'll try to add more information as requested:
appsettings.json
"ConnectionStrings": {
"Debug": "server=Server;user id=myuser;password=password;port=3306;database=database1;",
"Demo": "server=Server;user id=myuser;password=password;port=3306;database=database2;"
}
Both servers are the same, but two different databases (1 and 2 for example).
Startup.cs:
services.AddTransient<AppDb>(_ => new Controllers.AppDb(Configuration["ConnectionStrings:Database1"]));
services.AddTransient<AppDb>(_ => new Controllers.AppDb(Configuration["ConnectionStrings:Database2"]));
I have both this services for the ConnectionStrings, currently the Database isn't working.
AppDb.cs
namespace ProjectDatabase.Controllers
{
public class AppDb
{
public MySqlConnection Connection { get; }
public AppDb(string connectionString)
{
Connection = new MySqlConnection(connectionString);
}
public void Dispose() => Connection.Dispose();
}
}
I have this that calls and connects to the database, but it's only connecting to Database1.
ClientsController
namespace ProjectDatabase.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ClientsController : ControllerBase
{
public ClientsController(AppDb db)
{
Db = db;
}
// GET api/clients
[HttpGet]
public async Task<IActionResult> GetLatest()
{
await Db.Connection.OpenAsync();
var query = new clientsQuery(Db);
var result = await query.LatestClientsAsync();
return new OkObjectResult(result);
}
public AppDb Db { get; }
}
}
I place the Get Request for the Database1.
Question: I already have connected to Database1 and would like to connect to Database2 one at a time, how can achieve this ? How can I tell the app to Get the data from Database2 ? I already have the 2nd Connection String how can i access both of them ?
Edited for more clarity.
If i understand correctly, you want to switch between connection that depends on environment.
So you can look into asp.net configuration.
Create 2 env files and then put your connection strings into them.
For example:
appsettings.Development.json
"ConnectionStrings": {
"MySql": "Dev DB connection string"
},
appsettings.Demo.json
"ConnectionStrings": {
"MySql": "Demo DB connection string"
},
and your setup code will looks like this
services.AddTransient(_ => new
Controllers.AppDb(Configuration["ConnectionStrings:MySql"]));
So after that manipulations you just need to switch environment and you will get right connection string.
Read about how to setup multi environment configuration you can here:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/environments
Startup.cs:
services.AddTransient<AppDb>(_ => new Controllers.AppDb(Configuration["ConnectionStrings:Debug"], Configuration["ConnectionStrings:Demo"]));
AppDb.cs
public class AppDb
{
public MySqlConnection conDebug { get; }
public MySqlConnection conDemo { get; }
public AppDb(string connectionStringDebug, string connectionStringDemo)
{
conDebug = new MySqlConnection(connectionStringDebug);
Hashtable hasDB = new Hashtable();
hasDB["debug"] = conDebug;
}
public void Dispose() => conDebug.Dispose();
}
}
Solved.
namespace ProjectDatabase.Controllers
{
public class AppDb
{
public MySqlConnection Connection { get; }
public MySqlConnection Con { get; }
public AppDb(string connectionString)
{
Connection = new MySqlConnection(connectionString);
Con = new MySqlConnection(connectionString);
}
public void Dispose() => Connection.Dispose();
}
}
This is my AppDb.cs, my controller for the connection, what if I add a second MySqlConnection and then in my ClientsController I specify the connection to be open:
namespace ProjectDatabase.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ClientsController : ControllerBase
{
public ClientsController(AppDb db)
{
Db = db;
}
// GET api/clients
[HttpGet]
public async Task<IActionResult> GetLatest()
{
await Db.Connection.OpenAsync();
var query = new clientsQuery(Db);
var result = await query.LatestClientsAsync();
return new OkObjectResult(result);
}
public AppDb Db { get; }
}
}
Instead of having await Db.Connection.OpenAsync() I could create another Controller and do await Db.Con.OpenAsync() which will be my 2nd Connection to the my 2nd database.
Is this something that will not cause me any problems or has any cons to it ?
I have a use case where I will need multiple connection strings in my data access layer and will use anyone depending on the input.
Currently, I have 2 connection strings which I have added in JSON and then I am injecting both.
Is there any other solution to inject all the connection strings at once because in future with the introduction of any new DB I have to add one more connection string in JSON and then again inject it?
StartUp class:
private static void Main(string[] args)
{
ServiceCollection serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
IServiceProvider serviceProvider =
serviceCollection.BuildServiceProvider();
serviceProvider.GetService<StudentApp>().Start();
}
private static void ConfigureServices(IServiceCollection
serviceCollection)
{
IConfigurationRoot configuration = GetConfiguration();
Database database1 = new SqlDatabase(configuration.GetSection("Configuration:ConnectionString1").Value;
Database database2 = new SqlDatabase(configuration.GetSection("Configuration:ConnectionString2").Value;
// Here I am doing Multiple injections
serviceCollection.AddSingleton(database1);
serviceCollection.AddSingleton(database2);
serviceCollection.AddOptions();
serviceCollection.Configure<AppSettings(configuration.GetSection("Configuration"));
serviceCollection.AddSingleton(configuration);
serviceCollection.AddTransient<IStudentDataAccess,StudentDataAccess>();
serviceCollection.AddTransient<StudentApp>();
}
private static IConfigurationRoot GetConfiguration()
{
return new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: true)
.Build();
}
StudentApp Class:
private readonly IStudentDataAccess _dataAccess;
private readonly AppSettings _config;
private readonly Database _database1;
private readonly Database _database2;
public StudentApp(IStudentDataAccess dataAccess,IOptions<AppSettings>
config, Database database1, Database database2)
{
_dataAccess= dataAccess;
_config = config.Value;
_database1 = database1;
_database2 = database2;
}
public void Start()
{
int count= _dataAccess.GetStudentCount(deptId);
}
DataAccess classes:
public interface IStudentDataAccess
{
int GetStudentCount(int deptId);
}
public class StudentDataAccess : IStudentDataAccess
{
private readonly AppSettings _config;
private readonly Database _database1;
private readonly Database _database2;
public StudentDataAccess (IOptions<AppSettings> config, Database
database1,Database database2)
{
_config = config.Value;
_database1 = database1;
_database2 = database2;
}
public int GetStudentCount(int deptId)
{
// Execute queries either by Database1 or 2.
}
}
Database class used is from Microsoft.Practices.EnterpriseLibrary.Data.
How can I avoid creating multiple Singleton classes for different connection strings?
Any help?
You can keep your connection strings as an array in your appsettings.json:
{
...
"ConnectionStrings": [
{
"Name": "ConnectionString1",
"Value": "some value"
},
{
"Name": "ConnectionString1",
"Value": "some value"
}
]
}
and map them to some class using Options pattern:
public class ConnectionStringOptions
{
public ConnectionString[] ConnectionStrings { get; set; }
}
public class ConnectionString
{
public string Name { get; set; }
public string Value { get; set; }
}
And then, you can have an interface like this one:
public interface IDatabaseProvider
{
IEnumerable<Database> GetDatabases();
Database GetDatabase(string name);
}
with the implementation like this
public class DatabaseProvider : IDatabaseProvider
{
private readonly ConnectionStringOptions _options;
public DatabaseProvider(IOptions<ConnectionStringOptions> optionsAccessor)
{
this._options = optionsAccessor.Value;
}
public IEnumerable<Database> GetDatabases()
{
foreach (ConnectionString connectionString in this._options.ConnectionStrings)
yield return new SqlDatabase(connectionString.Value);
}
public Database GetDatabase(string name)
{
string connectionString = this._options.ConnectionStrings.SingleOrDefault(x => x.Name == name).Value;
return new SqlDatabase(connectionString);
}
}
Now you just register the IDatabaseProvider:
serviceCollection.AddTransient<IDatabaseProvider, DatabaseProvider>()
and inject it in your services as needed. E.g:
public class StudentApp
{
private readonly IEnumerable<Database> _databases;
public StudentApp(IStudentDataAccess dataAccess, IDatabaseProvider databasesProvider)
{
//Or get just the one you want by name
this._databases = databasesProvider.GetDatabases();
// ...
}
// ...
}
Update: Code snippets for Options pattern:
serviceCollection.Configure<ConnectionStringOptions>(configuration.GetSection("ConnectionStringsā€¯));
Here is my method :
public async task<model> GetMemberList(CancellationToken cancelToken, string connString)
{
try
{
await Task.Run(() =>
{
using (var dbContext = DbContext.Create(connString))
{
// Code Goes Here....
}
}, cancelToken);
}
catch
{
Throw New Exception();
}
}
In here i used "using" keyword to get dbContext. In every methods I did this because we have different connection string. I hope this is not a bad way to write methods in entity framwork. I realized this when I was going to write unit test for each methods in the business layer. I want to write a constructor to get dbcontext in generic way. I can use Dependency Injection to do that but i don't know how to do that. Can someone give me a way to do it?
Create and Interface IDbFactory
public interface IDbFactory
{
DbContext GetConnection();
}
Create a class DbFactory
public class DbFactory : IDbFactory
{
public DbContext GetConnection()
{
var connectionString = [get this from web.config]
return new DbContext.Create(connectionString);
}
}
Inject dependacny for IDbFactory in the constructor then
public async task<model> GetMemberList(CancellationToken cancelToken)
{
try
{
await Task.Run(() =>
{
using (var db = _dbFactory.GetConnection())
{
// Code Goes Here....
}
}, cancelToken);
}
catch
{
Throw New Exception();
}
}
Hope it helps
If you just need to hide the logic of building connections string, you can use Factory pattern as it is. In this example building of connection string depends of clientId and is encapsulted in factory. You can mock it as you like in your unit tests for SomeService.
public class CompositionRoot
{
private readonly IContainer _root;
public CompositionRoot()
{
var builder = new ContainerBuilder();
builder.RegisterType<SomeService>();
builder.RegisterType<DbContextFactory>().As<IDbContextFactory>();
_root = builder.Build();
}
public T GetService<T>()
{
return _root.Resolve<T>();
}
}
public interface IDbContextFactory
{
DbContext Get(int clientId);
}
public class DbContextFactory : IDbContextFactory
{
public DbContext Get(int clientId)
{
// place here any logic you like to build connection string
var connection = $"Data Source={clientId}db";
return new DbContext(new SqlConnection(connection), true);
}
}
public class SomeService
{
private readonly IDbContextFactory _dbFactory;
public SomeService(IDbContextFactory dbFactory)
{
_dbFactory = dbFactory ?? throw new ArgumentNullException(nameof(dbFactory));
}
public async Task<Model> GetMemberList(CancellationToken cancelToken, int clientId)
{
using (var dbContext = _dbFactory.Get(clientId))
{
// Code Goes Here....
}
return null;
}
}
I'm trying out asp.net out and I'm having issues doing the simplest thing, connecting to a database. I have no clue how to call my context via dependency injection to use in my repository, if anyone can help me out it would be great. Here is my code:
Model:
public class User
{
...attrs...
}
Interface / Repo:
public interface UserRepository
{
List<User> GetUsers();
}
public class UserRepositoryImpl : UserRepository
{
public List<User> GetUsers()
{
//I NEED THE CONTEXT FOR "db" HERE!
using (MySqlConnection con = db.GetConnection())
{
List<User> list = new List<User>();
con.Open();
MySqlCommand cmd = new MySqlCommand("SELECT * FROM user", con);
using (MySqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
list.Add(new User()
{
...read stuff...
});
}
}
return list;
}
}
}
My Context:
public class MyContext
{
public string ConnectionString { get; set; }
public MyContext(string connectionString)
{
this.ConnectionString = connectionString;
}
public MySqlConnection GetConnection()
{
return new MySqlConnection(ConnectionString);
}
}
Service registration:
services.Add(new ServiceDescriptor(typeof(MyContext), new MyContext(Configuration.GetConnectionString("DefaultConnection"))));
Most tutorials either force me to use EF and others don't do the dependency injection, if anyone could guide me or point me to the right direction I would be really greatful! Thanks in advance!
EDIT: I got it to work using this in my controller after creating a constructor that takes in the context for my db:
[Produces("application/json")]
[Route("api/user")]
public class UserController : Controller
{
[HttpGet]
public List<User> AllUsers()
{
MyContext db = HttpContext.RequestServices.GetService(typeof(MyContext)) as MyContext;
UserRepositoryImpl repo = new UserRepositoryImpl(db);
return repo.GetUsers();
}
}
I still believe this can be improved though, is there a better way to do this? Thanks
EDIT2: Finally got it to work using this controller
[Produces("application/json")]
[Route("api/user")]
public class UserController : Controller
{
MyContext db;
public UserController(MyContext db)
{
this.db = db;
}
[HttpGet]
public List<User> AllUsers()
{
UserRepository repo = new UserRepository(db);
return repo.GetUsers();
}
}
Thanks to Tseng's answer!
I'm using Entity Framework 5 with Code First Migrations. I have a DataStore class which derives from DbContext:
public class DataStore : DbContext, IDataStore
{
public int UserID { get; private set; }
public DataStore(int userId, string connectionString) : base(connectionString)
{
UserID = userId;
}
public virtual IDbSet<User> Users { get; set; }
// Rest of code here
}
And a factory class which creates instances of the DataStore class:
public class DataStoreFactory : Disposable, IDataStoreFactory
{
private DataStore _database;
private int _userId;
private string _connectionString;
public DataStoreFactory(int userId, string connectionString)
{
_userId = userId;
_connectionString = connectionString;
}
public IDataStore Get()
{
_database = new DataStore(_userId, _connectionString);
return _database;
}
protected override void DisposeCore()
{
if (_database != null) _database.Dispose();
}
}
These classes have their constructor parameters injected at runtime with Unity. So far so good, everything works great!
The problem arises when we get to migrations: because my DataStore context class doesn't have a default constructor, I need to supply an implementation of IDbContextFactory<T> so that Code First Migrations can instantiate it:
public class MigrationDataStoreFactory : IDbContextFactory<DataStore>
{
public DataStore Create()
{
// Need to inject connection string so we can pass it to this constructor
return new DataStore(0, "CONNECTION_STRING_NEEDED_HERE");
}
}
The issue is that I can't figure out how I can inject the connection string into this class. I can't create a new constructor with a connection string parameter like this:
public class MigrationDataStoreFactory : IDbContextFactory<DataStore>
{
public string _connectionString { get; set; }
public MigrationDataStoreFactory(string connectionString)
{
_connectionString = connectionString;
}
public DataStore Create()
{
return new DataStore(0, new DateTimeProvider(() => DateTime.Now), _connectionString);
}
}
If I do, I get the following exception thrown by Migrations at runtime:
[InvalidOperationException: The context factory type 'MigrationDataStoreFactory' must have a public default constructor.]
System.Data.Entity.Infrastructure.DbContextInfo.CreateActivator() +326
System.Data.Entity.Infrastructure.DbContextInfo..ctor(Type contextType, DbProviderInfo modelProviderInfo, AppConfig config, DbConnectionInfo connectionInfo) +106
System.Data.Entity.Infrastructure.DbContextInfo..ctor(Type contextType) +52
System.Data.Entity.Migrations.DbMigrator..ctor(DbMigrationsConfiguration configuration, DbContext usersContext) +202
System.Data.Entity.Migrations.DbMigrator..ctor(DbMigrationsConfiguration configuration) +66
System.Data.Entity.MigrateDatabaseToLatestVersion`2.InitializeDatabase(TContext context) +50
// Truncated stack trace, but you get the idea
Aside from that, this class is not instantiated by Unity anyway; it seems to just be called by convention by Code First Migrations somehow, so even if I could do that it wouldn't really help...
Everything works fine if I hard-code the connection string in that method, but I don't want to do that, for obvious reasons.
Can anyone help please?
For those for whom upgrading to Entity Framework 6 is viable, there's a new overload of the migration initialization that makes this much easier:
// Parameters:
// useSuppliedContext:
// If set to true the initializer is run using the connection information from the
// context that triggered initialization. Otherwise, the connection information
// will be taken from a context constructed using the default constructor or registered
// factory if applicable.
public MigrateDatabaseToLatestVersion(bool useSuppliedContext);
Using this, you can run migrations with an injected DbContext as follows:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyDbContext, MyMigrationConfiguration>(useSuppliedContext: true));
using (var context = kernel.Get<MyDbContext>())
context.Database.Initialize(false);
Here's the approach I eventually used, using the custom IDatabaseInitializer<T> code from this answer, which helped me out a great deal.
First we add another constructor to the DataStore class (DbContext) which doesn't require the connection string parameter:
public class DataStore : DbContext, IDataStore
{
public int UserID { get; private set; }
// This is the constructor that will be called by the factory class
// if it is initialised without a connection string parameter
public DataStore(int userId)
{
UserID = userId;
}
public DataStore(int userId, string connectionString) : base(connectionString)
{
UserID = userId;
}
public virtual IDbSet<User> Users { get; set; }
// Rest of code here
}
Then we do the same for the factory class:
public class DataStoreFactory : Disposable, IDataStoreFactory
{
private DataStore _database;
private int _userId;
private string _connectionString;
// This is the constructor that will be called by the
// MigrationDataStoreFactory class
public DataStoreFactory(int userId)
{
_userId = userId;
}
public DataStoreFactory(int userId, string connectionString)
{
_userId = userId;
_connectionString = connectionString;
}
public IDataStore Get()
{
// If we have a connection string, construct our context with it,
// if not, use the new constructor
if(_connectionString != null)
_database = new DataStore(_userId, _dateTimeServices, _connectionString);
else
_database = new DataStore(_userId, _dateTimeServices);
return _database;
}
protected override void DisposeCore()
{
if (_database != null) _database.Dispose();
}
}
This is the custom initializer code:
public class MigrateDatabaseToLatestVersionWithConnectionString<TContext, TMigrationsConfiguration> : IDatabaseInitializer<TContext>
where TContext : DbContext
where TMigrationsConfiguration : DbMigrationsConfiguration<TContext>, new()
{
private readonly DbMigrationsConfiguration _config;
public MigrateDatabaseToLatestVersionWithConnectionString()
{
_config = new TMigrationsConfiguration();
}
public MigrateDatabaseToLatestVersionWithConnectionString(string connectionString)
{
// Set the TargetDatabase for migrations to use the supplied connection string
_config = new TMigrationsConfiguration {
TargetDatabase = new DbConnectionInfo(connectionString,
"System.Data.SqlClient")
};
}
public void InitializeDatabase(TContext context)
{
// Update the migrator with the config containing the right connection string
DbMigrator dbMigrator = new DbMigrator(_config);
dbMigrator.Update();
}
}
Our custom context factory (which is only ever called by Code First Migrations) can now carry on using the DataStore constructor which doesn't require a connection string:
public class MigrationDataStoreFactory : IDbContextFactory<DataStore>
{
public DataStore Create()
{
return new DataStore(0);
}
}
As long as we set the database initializer to our custom initializer and pass in the connection string (which in my case is done in Global.asax), migrations will use the correct connection:
Database.SetInitializer<DataStore>(new MigrateDatabaseToLatestVersionWithConnectionString<DataStore, MyMigrationsConfiguration>(INJECTED_CONNECTION_STRING_HERE));
Hope all that makes senseā€”feel free to ask for clarification in the comments.
First define your database settings interface for example IDBConnectionSettings.
In the app.config add the connection string:
<connectionStrings>
<add name=" ConnectionString "
connectionString="Integrated Security=SSPI; Persist Security Info=False; InitialCatalog=DB; Data Source=(local);"
providerName="System.Data.SqlClient" />
</connectionStrings>
To retrieve the connection string from your Settings file or your app.config you need for example to do that:
public class DBConnectionSettings()
{
get ConnectionString
{
var connections = ConfigurationManager.ConnectionStrings;
// From app.config you will get the connection string
var connectionString = connections["ConnectionString"].ConnectionString;
return connectionString;
}
}
Now you have to register the Interface somewhere in your code before using it.
unityContainer.Register<IDBConnectionSettings>();
You can use it anywhere with resolve in your case.
public class MigrationDataStoreFactory : IDbContextFactory<DataStore>
{
public string _connectionString { get; set; }
public MigrationDataStoreFactory(UnityContainer unityContainer)
{
_connectionString = unityContainer.Resolve<IDBConnectionSettings>().ConnectionString;
}
public DataStore Create()
{
return new DataStore(0, new DateTimeProvider(() => DateTime.Now), _connectionString);
}
}
Update for default constructor
Make a static method or put this code in the default constructor in this way you do not have to give any params.
var fileMap = new ExeConfigurationFileMap { ExeConfigFilename = Application.StartupPath + Path.DirectorySeparatorChar + #"app.config" }; // application name must be
using (var unityContainer = new UnityContainer())
{
var configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
var unitySection = (UnityConfigurationSection)configuration.GetSection("unity");
unityContainer.LoadConfiguration(unitySection, "ConnectionString");
{
unityContainer.Resolve<IDBConnectionSettings>();
....
....
I hope this will solve your problem! thanks