how to add provider name to the DbContext programmatically - ef-code-first

I have been struggling to figure out a way to add the Oracle client provider name pro-grammatically. My Context class looks like this..
public class SystemFeatureContext : DbContext
{
public SystemFeatureContext(DbContextConfiguration configuration)
{
Database.Connection.ConnectionString = configuration.ConnectionString;
}
}
I get the DbContextConfiguration through my infrastructure, but trying to figure out a way to assign the provider name. If I put the whole connection string in my web.config/app.config it just works fine when passed thru the base constructor.
<connectionStrings>
<add name="Context" connectionString="DATA SOURCE=abcd;USER ID=xxxx;PASSWORD=xxxxx$1;PERSIST SECURITY INFO=True;POOLING=False;" providerName="Oracle.DataAccess.Client" />
public class SystemFeatureContext : DbContext
{
public SystemFeatureContext():base("Name=Context")
{
}
}
But my situation demand to create the context programmatically. I tried implmenting IDBConnectionFactory but it throws error that it did not find metadata.
public class OracleConnctionFactory : IDbConnectionFactory
{
private readonly string oracleConnString;
public OracleConnctionFactory(string connString)
{
oracleConnString = connString;
}
public System.Data.Common.DbConnection CreateConnection(string oracleConnString)
{
oracleConnString = this.oracleConnString;
var connectionStringBuilder = new EntityConnectionStringBuilder();
connectionStringBuilder.ProviderConnectionString = oracleConnString;
connectionStringBuilder.Provider = "Oracle.DataAccess.Client";
connectionStringBuilder.Metadata = "";
return new EntityConnection(connectionStringBuilder.ToString());
}
}
Any help would be appreciated.

Related

ASP.NET code first doesn't create DB or Tables

I'm trying to add a new connection to database in an ASP.NET application, and I'm trying to use code-first initialization.
For now, that are my connection strings:
<connectionStrings>
<add name="DefaultConnection"
connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-Auth-20211221042302.mdf;Initial Catalog=aspnet-Auth-20211221042302;Integrated Security=True"
providerName="System.Data.SqlClient" />
<add name="DriveMeConnection"
connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\DriveMe-App-20211231001122.mdf;Initial Catalog=DriveMe-App-20211231001122;Integrated Security=True;MultipleActiveResultSets=True;App=EntityFramework"
providerName="System.Data.SqlClient" />
</connectionStrings>
The first, DefaultConnection, was generated for the ASP.NET authentication, and works well.
The second, DriveMeConnection, is the database I want to create from my code-first model.
So, I created a new DbContext as usual:
public class DatabaseContext : DbContext
{
public DbSet<Azienda> Aziende;
public DbSet<ClientePrivato> ClientiPrivati;
public DbSet<Fornitore> Fornitori;
public DbSet<Auto> ParkAuto;
public DbSet<Guidatore> Guidatori;
public DbSet<Pratica> Pratiche;
public DatabaseContext() : base("name=DriveMeConnection")
{
}
}
(there's no difference if I use base("name=DriveMeConnection") or base("DriveMeConnection")).
When I am trying to read something, for example
using (DatabaseContext db = new DatabaseContext())
{
List<Fornitore> lst = db.Fornitori.ToList();
}
Fornitori table was not created. In this case, the whole database wasn't created.
I tried to add
Database.SetInitializer<DatabaseContext>(new CreateDatabaseIfNotExists<DatabaseContext>());
inside the DatabaseContext constructor, but nothing changes.
I tried to add
this.Database.CreateIfNotExists();
inside the DatabaseContext constructor, the database was created, but the tables aren't there.
I tried to force initialization with:
using (DatabaseContext db = new DatabaseContext())
{
db.Database.Initialize(force: true);
}
This creates the database, but it is still empty without tables.
What did I miss?
I finally solved, without migrations that I don't need for now, I only call this function at startup.
public static void FirstConfiguration()
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<DatabaseContext>());
var context = new DatabaseContext();
context.Database.Initialize(true);
}
And in the DbContext, the dataset need to be properties with the get and set method:
public class DatabaseContext : DbContext
{
public DbSet<Azienda> Aziende { get; set; }
public DbSet<ClientePrivato> ClientiPrivati { get; set; }
public DbSet<Fornitore> Fornitori { get; set; }
public DbSet<Auto> ParkAuto { get; set; }
public DbSet<Guidatore> Guidatori { get; set; }
public DbSet<Pratica> Pratiche { get; set; }
public DatabaseContext() : base("name=DriveMeConnection")
{
}
}

System.Data.SqlClient not found in Repository Constructor

I'm trying to build a data layer in my application based on a .NET Core class library using Dapper. The data classes look like this:
//FieldRepository.cs
using Dapper.Contrib.Extensions;
public class FieldRepository : IRepository<TblField>
{
private IDbConnection connection;
public FieldRepository(string connectionString)
{
connection = new SqlConnection(connectionString);
}
public IEnumerable<TblField> GetAll()
{
return connection.GetAll<TblField>();
}
}
//IRepository.cs
public interface IRepository<T>
{
IEnumerable<T> GetAll();
}
//TblField
public class TblField
{
public string FieldText { get; set; }
public int Id { get; set; }
}
Then I tried to run a test against these classes as below:
public void ThereShouldBeFields()
{
var repo = new FieldRepository("valid connection string");
var fields = repo.GetAll();
fields.Should().NotBeNull();
}
When I ran this test, I got a FileNotFound exception at the FieldRepository constructor for System.Data.SqlClient, version 4.2.0.0, which is installed in the data layer project.
I know I'm missing something simple here, but what is it?
Looks like it was because the reference needed to be added to both the Test project and the Data layer project.
The more you know!

Asp.net Identity DbContext / Repository Issue

I am using Asp.Net identity within my MVC app. I can see that this has it's own ApplicationDbContext - albeit it is connected to the same SQL db as my own DbContext I am using elsewhere.
So I am trying to access some of my own data via my own code within the AccountController - it does not seem to work I presume because of some confusion over which DBContext it thinks is active?
My Code :
public class AccountController : Controller
{
private ApplicationSignInManager _signInManager;
private ApplicationUserManager _userManager;
private PostageManager postmgr;
public AccountController()
{
}
public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager, PostageManager _postmgr)
{
UserManager = userManager;
SignInManager = signInManager;
postmgr = _postmgr;
}
public ApplicationSignInManager SignInManager
{
get
{
return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
}
private set
{
_signInManager = value;
}
}
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
// GET: /Account/Register
[AllowAnonymous]
public ActionResult Register()
{
//create select list items for countries drop down
List<SelectListItem> countries;
countries = postmgr.GetCountries().Select(item => new SelectListItem
{
Value = item.Country,
Text = item.Country
}).ToList();
countries.Insert(0, new SelectListItem { Value = string.Empty, Text = "Select delivery country or region...", Selected = true });
RegisterViewModel mode = new RegisterViewModel
{
Countries = countries
};
return View();
}
}
}
PostageManager is just a class that sits over my DAL to fetch some data (which uses repository pattern) - I'm using just a kind of pass through method to grab a list of countries, and using it in exactly the same way I have in other controllers which works fine. Underneath that class is my repository code that is linked to my default connection string (DBContext). It's balking at the following line with a null reference exception, I think postmgr is null :
countries = postmgr.GetCountries().Select(item => new SelectListItem
In reverse to get access to the identity data within my own controllers I have done the following :
public BasketController(BasketManager _mgr, PostageManager _postmgr, ProductManager _prodmgr)
{
mgr = _mgr;
postmgr = _postmgr;
prodmgr = _prodmgr;
shopper = Cart.GetShopperId();
this.applicationDbContext = new ApplicationDbContext();
this.userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(this.applicationDbContext));
}
protected ApplicationDbContext applicationDbContext { get; set; }
protected UserManager<ApplicationUser> userManager { get; set; }
Which as far as I understand it points the identity code to use the right DbContext - I looked at doing this in reverse in my AccountController but can't fathom it out.
I basically just want to be able to use my own code that grabs my own data from within the Identity controllers to help pass extra data etc through to the views.
I might be wrong but most probably postmgr field is not initialized from constructor and that is why you have this error.
Explanation:
By default Asp will try to create controller instance by constructor without parameters. If Asp can't find constructor without parameters it will try to call constructor with parameters, but to make it possible you have to configure IoC in your app. As your controler has constructor without parameters it will be selected by Asp. So all 3 fields are empty.
But in properties SignInManager and UserManager you try to take value from field or from OwinContext. As field is empty your code will take value from OwinContext. OwinContext is quite complex and smart tool that create its context automatically based on configuration provided in Startup.Auth.cs file or any other file under App_Start folder.
I think I have figured it out - added the following to my NinjectControllerFactory :
ninjectKernel.Bind<IAuthenticationManager>().ToMethod(c => HttpContext.Current.GetOwinContext().Authentication); //.InRequestScope();
ninjectKernel.Bind<IUserStore<ApplicationUser>>().To<UserStore<ApplicationUser>>();
ninjectKernel.Bind<UserManager<ApplicationUser>>().ToSelf();
ninjectKernel.Bind<IRoleStore<IdentityRole, string>>().To<RoleStore<IdentityRole, string, IdentityUserRole>>();
ninjectKernel.Bind<RoleManager<IdentityRole>>().ToSelf();
And changed my constructor to :
public AccountController(PostageManager _postmgr)
{
postmgr = _postmgr;
}

How do I reference config elements values in other config elements?

For example, I created a provider service that uses a database. In web.config, how do I set the provider's connection string to the main application connection string, defined in <ConnectionStrings>?
Matt's answer is pretty much there, with a couple of tweaks.
If you're happy to have it outside of the configuration code itself, once you've picked up your provider configuration, you can just talk to the main connection strings section directly from your provider classes:
var provider = ConfigurationManager.GetSection("ProviderConfiguration")
as ProdviderSettingsSection;
ConnectionString connStr =
WebConfigurationManager.ConnectionStrings[provider.ConnectionString];
If you want to wrap it all up in the provider you'll need a backing field for your properties:
public class ProvderSettingsConfigElement : ConfigurationElement {
private m_ConnectionString;
[ConfigurationProperty("connectionString")]
public string ConnectionString{
// Probably want to check m_ConnectionString for IsNullOrEmpty
get{ return WebConfigurationManager.ConnectionStrings[m_ConnectionString]; }
set{ m_ConnectionString = value;}
}
}
You could create a custom config element that reads the configuration for main app config.
Don't take this line for line but something like...
public class ProviderConfiguration : ConfigurationSection
{
#region Constructors
public ProviderConfiguration () { }
#endregion
#region Public Properties
[ConfigurationProperty("ProviderConnection")]
public ProvderSettingsConfigElement ProvderConnection
{
get { return (ProvderSettingsConfigElement)this["ProviderConnection"]; }
}
#endregion
}
public class ProvderSettingsConfigElement : ConfigurationElement
{
#region Constructors
public ProvderSettingsConfigElement () { }
public WebSecuritySettingsDataProviderElement(string name, string type)
{
ConnectionString = ConfigurationManager.Get("mainAppConnString");
}
#region Public Properties
[ConfigurationProperty("connectionString")]
public string ConnectionString{get; set;}
}

IOC with multiple databases that use same interface (StructureMap or any other DI Framework)

We've been experimenting with StructureMap, and I'm having trouble grasping how to handle situations where a single interface has multiple implementations. The code below shows an example where we have two databases that are both accessible from a single service.
public class SomeController : Controller
{
private ISomeService _service;
private IClientRepository _repository;
protected IContext _masterContext;
protected IContext _clientContext;
public SomeController(ISomeService service, ISomeRepository repository
, IContext masterCon, IContext clientCon)
{
_service = service;
_repository = repository;
_masterContext = masterCon;
_clientContext = clientCon;
}
}
public class SomeService : ISomeService
{
private IContext _masterContext;
private IContext _clientContext;
public SomeService(IContext masterContext, IContext clientContext)
{
masterContext = _masterContext;
clientContext = _clientContext;
}
}
public class ClientRepository : IClientRepository
{
private IContext _clientContext;
public ClientRepository(IContext clientContext)
{
_clientContext = clientContext;
}
}
public class MasterContext : IContext
{
public MasterContext(String connString)
//<snip, snip> implement 3rd party data context
}
public class ClientContext : IContext
{
public ClientContext(String connString)
//<snip, snip> implement 3rd party data context
}
StructureMap worked GREAT when we had a single context (database), but how do I tell it how to resolve the 2nd? Note: in most situations we wouldn't have a service handling 2 databases (but may have a controller handling 2 connections, i.e. 2 repositories accessing 2 different databases), but it still doesn't seem to make it easier.
I'm half ready to just give up on using an IoC framework and go back to poor man's DI.
Is it not possible to have an IClientContext and an IMasterContext, possibly inheriting from IContext. My feeling is that the code would be doing one of two very different things depending on whether you were talking to the 'Master' or 'Client' database.
In Unity you can have named registrations, allowing you to effectively register more than a class for a given interface. So you could do (typing by heart, check the actual Unity documentation if interested):
container.RegisterType<IContext, MasterContext>("Master");
container.RegisterType<IContext, ClientContext>("Client");
and then the constructor for SomeService would be:
public SomeService(
[Dependency("Master")]IContext masterContext,
[Dependency("Client")]IContext clientContext)
{
//...
}
The drawback is that in this way your service class is no longer independent of the DI framework used, but depending on the project that may be ok.
This can be a little difficult if you're relying on StructureMap to resolve the dependencies automatically. The first solution (and what I'd err towards) is to make use of marker interfaces like Richard mentions in his answer then just register them. You can then explicitly specify whether you want your client or master context there.
The second way is to make use of named registrations, then specify the constructor params explicitly.
ForRequestedType<IContext>().AddInstances(
i => {
i.OfConcreteType<ClientContext>().WithName("Client");
i.OfConcreteType<MasterContext>().WithName("Master");
});
ForRequestedType<SomeController>().TheDefault.Is.ConstructedBy(
i => new SomeController(i.GetInstance<ISomeService>(),
i.GetInstance<IClientRepository>(),
i.GetInstance<IContext>("Master"),
i.GetInstance<IContext>("Client")));
Not particularly nice but it does the job and ultimately if it's only in one or two places it might be OK.
If you want to resolve differently on namespace / assembly you could try something like this:-
ForRequestedType<IContext>().AddInstances(
i => {
i.OfConcreteType<ClientContext>().WithName("Client");
i.OfConcreteType<MasterContext>().WithName("Master");
}).TheDefault.Is.Conditional(c => {
c.If(con => con.ParentType.Namespace.EndsWith("Client"))
.ThenIt.Is.TheInstanceNamed("Client");
c.If(con => con.ParentType.Namespace.EndsWith("Master"))
.ThenIt.Is.TheInstanceNamed("Master");
c.TheDefault.Is.OfConcreteType<ClientContext>();
});
Where the predicate on ParentType can refer to Assembly (or whatever you want really)
In case someone stumble in this problem, you can achieve it using factory pattern.
Service extension
public static class ServiceFactoryExtensions
{
public static void RegisterSqlFactory(this IServiceCollection serviceCollection)
{
serviceCollection.Configure<MsSqlOption>(option => option.ConnectionString = "Mssql connection string");
serviceCollection.Configure<MySqlOption>(option => option.ConnectionString = "Mysql connection string");
serviceCollection.Configure<PostgreOption>(option => option.ConnectionString = "Postgrel connection string");
serviceCollection.AddSingleton<ISqlDatabase, MsSql>();
serviceCollection.AddSingleton<ISqlDatabase, Postgre>();
serviceCollection.AddSingleton<ISqlDatabase, MySql>();
serviceCollection.AddSingleton<Func<IEnumerable<ISqlDatabase>>>(serviceProvider => () => serviceProvider.GetService<IEnumerable<ISqlDatabase>>());
serviceCollection.AddSingleton<ISqlDatabaseFactory, SqlDatabaseFactory>();
}
}
Factory class
public class SqlDatabaseFactory : ISqlDatabaseFactory
{
private readonly Func<IEnumerable<ISqlDatabase>> _factory;
public SqlDatabaseFactory(Func<IEnumerable<ISqlDatabase>> factory)
{
_factory = factory;
}
public ISqlDatabase CreateSql(SqlType sqlType)
{
var databases = _factory();
var sqlDatabase = databases.FirstOrDefault(x => x.DatabaseName == sqlType);
if (sqlDatabase == null)
throw new NotImplementedException($"Sql type {nameof(sqlType)} is not implemented");
return sqlDatabase;
}
}
Sql classes
public class MsSql : ISqlDatabase
{
public SqlType DatabaseName => SqlType.MsSql;
public string Connecionstring { get; private set; }
public MsSql(IOptions<MsSqlOption> option)
{
Connecionstring = option.Value.ConnectionString;
}
}
public class Postgre : ISqlDatabase
{
public SqlType DatabaseName => SqlType.Postgre;
public string Connecionstring { get; private set; }
public Postgre(IOptions<PostgreOption> option)
{
Connecionstring = option.Value.ConnectionString;
}
}
public class MySql : ISqlDatabase
{
public SqlType DatabaseName => SqlType.MySql;
public string Connecionstring { get; private set; }
public MySql(IOptions<MySqlOption> option)
{
Connecionstring = option.Value.ConnectionString;
}
}
public interface ISqlDatabase
{
string Connecionstring { get; }
SqlType DatabaseName { get; }
}
public enum SqlType
{
MsSql,
Postgre,
MySql
}
Usage
internal class Program
{
static void Main(string[] args)
{
var serviceCollection = new ServiceCollection();
serviceCollection.RegisterSqlFactory();
var provider = serviceCollection.BuildServiceProvider();
var sqlFactory = provider.GetService<ISqlDatabaseFactory>();
var mySql = sqlFactory.CreateSql(SqlType.MySql);
var msSql = sqlFactory.CreateSql(SqlType.MsSql);
var postgre = sqlFactory.CreateSql(SqlType.Postgre);
Console.WriteLine($"Database Type : {mySql.DatabaseName}, Connectionstring: {mySql.Connecionstring}");
Console.WriteLine($"Database Type : {msSql.DatabaseName}, Connectionstring: {msSql.Connecionstring}");
Console.WriteLine($"Database Type : {postgre.DatabaseName}, Connectionstring: {postgre.Connecionstring}");
Console.ReadKey();
}
}
Output
Dependencies:
.Net Core 3.1
Microsoft.Extensions.DependencyInjection;
Microsoft.Extensions.Options;
System
System.Collections.Generic
System.Linq;

Resources