Currently in the process of building a multi tired application using EF Core for my data access layer and I feel like I'm running in to a problem and I'm not 100% certain I can use it at the moment.
Essentially I'm designing my application into the following components
windows assembly
asp.net mvc core web app
windows service x2
unit test
in good design I'm putting as much of my domain in my assembly so that I can reuse it as much as possible, but this is where I'm running into issues. I'm currently unable to use EF in my unit test app.
I am currently overriding OnConfiguring to set the database connection string but when I attempt to use the context in a unit testI continually get the following exception message: "Instance failure"
My context is dirt simple right now and only has one entity and looks as follows:
public partial class CdiContext : DbContext
{
private string ConnectionString { get; set; }
private bool IsService { get; set; }
public CdiContext(string connectionString, bool isService)
{
ConnectionString = connectionString;
IsService = isService;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(ConnectionString);
}
public DbSet<Region> Regions { get; set; }
}
In an effort to rule out the unit test as the problem I've also created a simple console application and even it throws the same exception so I'm really lost as to how to proceed.
CdiContext context = new CdiContext(#"Data Source=localhost\\SQLEXPRESS;Initial Catalog=herp-interface;Persist Security Info=True;User ID=sa;Password=herpaderp;Pooling=true", true);
var regions = context.Regions.ToList();
Console.ReadLine();
The question is what am I doing that's wrong where I am unable to use a EF context from a windows assembly in any type of project aside from an ASP.NET Core MVC app?
This error - "Instance failure" - occurs when you copy/paste the connection string from your aspnetcore app, and still have escaped backslashes.
Outside of ASP.Net Core, ie a console/windows service etc, the connection string in the app.config does not need to escape the backslashes.
EG
<add name="DefaultConnection" connectionString="Server=localhost\\SQLEXPRESS...
rather then
<add name="DefaultConnection" connectionString="Server=localhost\SQLEXPRESS...
Related
I have an ASP.NET Core application, which is structured in three layers i.e.:
Data access layer (Entity Framework)
Business logic layer
ASP.NET MVC web application
As it is right now, the configuration works, and I can access my database in my web application library. However, when I constructed my data access layer with EF Db first, I got a generic class, which looks something like this:
public partial class ClassContext: DbContext
{
public ClassContext(DbContextOptions<ClassContext> options)
: base(options)
{
}
public virtual DbSet<Entity> Entity{ get; set; }
....
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("ConnString");
}
}
}
As you can see, the connection string is hardcoded into my OnConfiguring method, which is not recommended.
Therefore, I followed the following "guide" here, which suggest me to use the built in DI, to pass the connection string from my Web library.
This I did:
public void ConfigureServices(IServiceCollection services)
{
//Add connectionstring to EF
services.AddDbContext<ClassContext>(options => options.UseSqlServer(Configuration.GetConnectionString("ConnectionStringFromAppSettings")));
}
According to the "guide" the connectionstring should be provided to the constructor of my ClassContext class - and it sort of does, however not with any connection string..
This means, that optionsBuilder.IsConfigured evaluates false, and wants to use the hard coded connectionstring.
Therefore, I would very much like to know, if I use the DI incorrect, since i cannot access the connection string in my ClassContext class
Update
I removed the OnConfiguring() method, and do now inject the context into my service class constructor in the business logic layer the following way:
public MasterService(ClassContext context)
{
MinorService = new MinorService(context);
}
public Stuff AddStuffIntoDatabase(Stuff test)
{
//business logic going before here
MinorService.addstuffMethod(test)
}
However, now I get the following error, when I want to do an operation in my database:
No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider.
This did not happen before, when I configured the connectionstring wrongly in my OnConfiguring() method.
What about the appsettings.json? Is it like this?
"ConnectionStrings": {
"ConnectionStringFromAppSettings": "your_connection_string"
}
Configuration.GetConnectionString always search for the section "ConnectionStrings"
https://learn.microsoft.com/it-it/dotnet/api/microsoft.extensions.configuration.configurationextensions.getconnectionstring?view=dotnet-plat-ext-3.1
I'm converting a working ASP.Net MVC website to an ASP.NET Core website. I'm trying to make the application work without App/Web.config (as seems to be the default for aspnetcore apps) but my EntityFramework connection to SQL Server is broken. After quite some time I get the following error:
SqlException: A network-related or instance-specific error occurred
while establishing a connection to SQL Server. The server was not
found or was not accessible. Verify that the instance name is correct
and that SQL Server is configured to allow remote connections.
(provider: SQL Network Interfaces, error: 26 - Error Locating
Server/Instance Specified)
Because I have no App.config / Web.config in the ASP.Net Core website I'm using a DbConfiguration class to tell EF to use SQL Server:
public class SupportManagerDbConfiguration : DbConfiguration
{
public SupportManagerDbConfiguration()
{
SetDefaultConnectionFactory(new SqlConnectionFactory());
SetProviderServices(SqlProviderServices.ProviderInvariantName, SqlProviderServices.Instance);
}
}
[DbConfigurationType(typeof(SupportManagerDbConfiguration))]
public class SupportManagerContext : DbContext
{
public SupportManagerContext(string nameOrConnectionString) : base(nameOrConnectionString)
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<SupportManagerContext, Migrations.Configuration>());
}
public DbSet<User> Users { get; set; }
}
public class User : Entity
{
public virtual string DisplayName { get; set; }
[Required]
public virtual string Login { get; set; }
}
public class Program
{
public static void Main(string[] args)
{
var db = new SupportManagerContext("Server=(local);Database=SupportManager;Integrated Security=true");
var user = db.Users.First();
db.Dispose();
}
}
This works fine in the 'old' ASP.Net website as long as I keep the ConnectionString in the Web.config (which of course includes the providerName as well), even with the DbConfiguration class added and the EntityFramework section removed from the old website's Web.config. When I remove the ConnectionString the same thing happens in the old website.
So basically it probably boils down to the providerName that's missing, but I'm having a hard time finding anything specific to this problem.
After a lot of trial and error, the solution was very subtle.
SqlConnectionFactory has a second constructor with parameter 'baseConnectionString'. If I would set that to Server=(local);Integrated Security=True then it would suddenly work. Documentation on MSDN notes the following for the parameterless constructor:
Creates a new connection factory with a default BaseConnectionString property of 'Data Source=.\SQLEXPRESS; Integrated Security=True; MultipleActiveResultSets=True;'
Apparently not overriding the baseConnectionString is somehow preventing this from working as I would have expected. However, this still differs from configuration as I would've done it through Web/App.config.
In the old project's Web.config the DefaultConnectionFactory was set to LocalDbConnectionFactory. This seems really awkward since I'm trying to use an actual SQL server database, but it has always worked when used in Web.config. As such, changing the DbConfiguration class as follows provides the same configuration through code:
public class SupportManagerDbConfiguration : DbConfiguration
{
public SupportManagerDbConfiguration()
{
SetDefaultConnectionFactory(new LocalDbConnectionFactory("mssqllocaldb"));
SetProviderServices(SqlProviderServices.ProviderInvariantName, SqlProviderServices.Instance);
}
}
And finally, I'm somewhat satisfied with the solution since it at least mimics the old situation, even though it's still not as easy as I'd expected.
I have an issue getting a DbContext to correctly pull my connection string from my local.settings.json
Context:
This is an Azure function project
The main problem code is in System.Data.Entity.Internal.AppConfig
Although I have a local.settings.json file this is not dotnet core. It's .net 4.6.1
Error message:
'The connection string 'ShipBob_DevEntities' in the application's configuration file does not contain the required providerName attribute."'
Json configuration:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"AzureWebJobsDashboard": ""
},
"ConnectionStrings": {
"ShipBob_DevEntities": {
"ConnectionString": "metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string='data source=***;initial catalog=***;persist security info=True;User Id=***;Password=***;;multipleactiveresultsets=True;application name=EntityFramework'",
"providerName": "System.Data.EntityClient"
}
}
}
Configuration versions tested:
Moving the provider name into the actual ConnectionString token value : same error ocurrs
Setting the provider attribute inside the ConnectionString attribute to EntityClient: this did nothing
Making ShipBob_DevEntities a string value = to the value of ConnectionString : this throws new errors the likes of which are
keyword metadata is not supported
I tried using an ADO connection string which throws a code first exception which seems to occur when your connection string is incorrect in a database first approach.
I've taken the liberty to decompile EntityFramework.dll using dotPeek and have traced the problem down to System.Data.Entity.Internal.LazyInternalConnection.TryInitializeFromAppConfig. Inside this method there is a call to LazyInternalConnection.FindConnectionInConfig which spits out a ConnectionStringSettings object that has it's ProviderName value set to null. Unfortunately I am unable to debug the AppConfig.cs class which it seems to use to generate this value so I am stuck.
So far I have consulted these two articles. One of which states to put the provider name as it's own token; however, this is not working.
https://github.com/Azure/azure-functions-cli/issues/193
https://github.com/Azure/azure-functions-cli/issues/46
Does anyone know the correct format to use in local.settings.json for an Entity Framework connection?
I went through several similar questions and answers here. Many of them are either misleading or assuming everybody is on the same level and understands how the azure functions are working. there is no answer for newbies like me. I would like to summarize here my solution step by step. I dont think that provided answer is the best option because it forces you to change the auto generated edmx files which can be overwritten by mistake or next update of your edmx from database. Also best option here is to use Connection strings instead of App settings in my opinion.
most important thing is that we understand local.settings.json file
IS NOT FOR AZURE. it is to run your app in the local as the name is
clearly saying. So solution is nothing to do with this file.
App.Config or Web.Config doesnt work for Azure function connection strings. If you have Database Layer Library you cant overwrite connection string using any of these as you would do in Asp.Net applications.
In order to work with, you need to define your connection string on the azure portal under the Application Settings in your Azure function. There is
Connection strings. there you should copy your connection string of your DBContext. if it is edmx, it will look like as below. There is Connection type, I use it SQlAzure but I tested with Custom(somebody claimed only works with custom) works with both.
metadata=res:///Models.myDB.csdl|res:///Models.myDB.ssdl|res://*/Models.myDB.msl;provider=System.Data.SqlClient;provider
connection string='data source=[yourdbURL];initial
catalog=myDB;persist security info=True;user
id=xxxx;password=xxx;MultipleActiveResultSets=True;App=EntityFramework
After you set this up, You need to read the url in your application and provide the DBContext. DbContext implements a constructor with connection string parameter. By default constructor is without any parameter but you can extend this. if you are using POCO class, you can amend DbContext class simply. If you use Database generated Edmx classes like me, you dont want to touch the auto generated edmx class instead of you want to create partial class in the same namespace and extend this class as below.
This is auto generated DbContext
namespace myApp.Data.Models
{
public partial class myDBEntities : DbContext
{
public myDBEntities()
: base("name=myDBEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
}
this is the new partial class, you create
namespace myApp.Data.Models
{
[DbConfigurationType(typeof(myDBContextConfig))]
partial class myDBEntities
{
public myDBEntities(string connectionString) : base(connectionString)
{
}
}
public class myDBContextConfig : DbConfiguration
{
public myDBContextConfig()
{
SetProviderServices("System.Data.EntityClient",
SqlProviderServices.Instance);
SetDefaultConnectionFactory(new SqlConnectionFactory());
}
}
}
After all you can get the connection string from azure settings, in your Azure Function project with the code below and provide to your DbContext
myDBEntities is the name you gave in the azure portal for your connection string.
var connString = ConfigurationManager.ConnectionStrings["myDBEntities"].ConnectionString;
using (var dbContext = new myDBEntities(connString))
{
//TODO:
}
So the solution ended up being trivial. The ProviderName attribute specified in local.settings.json MUST be camel case.
From the original git hub discussions :
https://github.com/Azure/azure-functions-cli/issues/46
Shows the provider name as being pascal case
https://github.com/Azure/azure-functions-cli/issues/193
Shows the provider name being camel case in pseudo code
It was very easy to miss but your config section must be exactly as follows
"ConnectionStrings": {
"ShipBob_DevEntities": {
"ConnectionString": "metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string='data source=***;initial catalog=***;persist security info=True;User Id=***;Password=***;;multipleactiveresultsets=True;application name=EntityFramework'",
"ProviderName": "System.Data.EntityClient"
}
}
These points are important:
Make sure your connection string has metadata information
If copying your string from an xml config, make sure you unescape apostrophes
Make sure the ProviderName attribute is camel case
Make sure the provider name is System.Data.EntityClient
Fix for missing providername in deployment
Note, this answer assumes you are trying to use the parameterless constructor of a DbContext. If you are creating new code you can easily follow the second upvoted answer
I figured out a way to circumvent the provider name issue while still retaining the use of the portal config and thus deployment slots. It involves setting the default connection string of db context using static properties
private static string _connectionString = "name=ShipBob_DevEntities";
static ShipBob_DevEntities()
{
if(!string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("AzureFunction")))
{
var connectionString = System.Environment.GetEnvironmentVariable("EntityFrameworkConnectionString");
if (!string.IsNullOrEmpty(connectionString))
{
_connectionString = connectionString;
}
}
}
public ShipBob_DevEntities()
: base(_connectionString)
{
this.Configuration.LazyLoadingEnabled = false;
}
This involves the developer to create an app setting in the azure portal as a flag. In my case it is AzureFunction. This makes sure that our code is only run in an azure function and all other clients of this DbContext, whether they be web apps, windows apps, etc, can still continue behaving as expected. This also involves adding your connection string to the azure portal as an AppSetting and not an actual connection string. Please use the full connection string including them metadata information but without the provider name!
EDIT
You will need to edit your auto generated .tt file t4 template to make sure this code does not get overridden if you are using db first.
Here is a link on the T4 syntax: https://learn.microsoft.com/en-us/visualstudio/modeling/writing-a-t4-text-template
And here is an explanation on EF T4 templates: https://msdn.microsoft.com/en-us/library/jj613116(v=vs.113).aspx#1159a805-1bcf-4700-9e99-86d182f143fe
I encountered the similar issue before, I would use the following approach for achieving my purpose, you could refer to it:
local.settings.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=brucchstorage;AccountKey=<AccountKey>",
"AzureWebJobsDashboard": "DefaultEndpointsProtocol=https;AccountName=brucchstorage;AccountKey=<AccountKey>",
"sqldb-connectionstring": "Data Source=.\\sqlexpress;Initial Catalog=DefaultConnection;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
},
"ConnectionStrings": {
"Bruce_SQLConnectionString": "Data Source=.\\sqlexpress;Initial Catalog=DefaultConnection;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
}
}
For retrieving the connection string:
var connString = ConfigurationManager.AppSettings["sqldb-connectionstring"];
//or var connString = ConfigurationManager.ConnectionStrings["Bruce_SQLConnectionString"].ConnectionString;
using (var dbContext = new BruceDbContext(connString))
{
//TODO:
}
Or you could init your no-argument constructor for your DbContext as follows:
public class BruceDbContext:DbContext
{
public BruceDbContext()
: base("Bruce_SQLConnectionString")
{
}
public BruceDbContext(string connectionString) : base(connectionString)
{
}
}
Then, you could create the instance for your DbContext as follows:
using (var dbContext = new BruceDbContext(connString))
{
//TODO:
}
Moreover, you could refer to Local settings file for Azure Functions.
Here are two approaches that work for me:
Approach 1
Add the connection string to the App Settings (respectively local.settings.json) in the following format:
metadata=res:///xxx.csdl|res:///xxx.ssdl|res://*/xxx.msl;provider=System.Data.SqlClient;provider connection string='data source=xxx.database.windows.net;initial catalog=xxx;user id=xxx;password=xxx;MultipleActiveResultSets=True;App=EntityFramework'`
Go to the class that extends DbContext ("TestEntities") and extend the constructor to take the connection string as argument
public partial class TestEntities: DbContext
{
public TestEntities(string connectionString)
: base(connectionString)
{
}
If you want then to interact with the database you need to retrieve the connection string from the app settings and then pass it over when initializing DbContext
string connectionString = Environment.GetEnvironmentVariable("connectionStringAppSettings");
using (var dbContext = new TestEntities(connectionString))
{
// Do Something
}
The problem with this approach is that every time you update the database you need to update the class "TestEntities" as it is overwritten
Approach 2
The goal here is to leave the class "TestEntities" as is to avoid the issue from Approach 1
Add the connection string to the App Settings (respectively local.settings.json) like in Approach 1
Leave TestEntities as is
public partial class TestEntities : DbContext
{
public TestEntities ()
: base("name=TestEntities")
{
}
As TestEntities is partial you can extend that class by creating another one that is also partial with the same name in the same namespace. The goal of this class is to provide the constructor that takes the connection string as argument
public partial class TestEntities
{
public TestEntities(string connectionString)
: base(connectionString)
{
}
}
Then you can go on like with Approach 1
I have an application using Entity Framework 6.
I have project named Data where I have my DbContext.
public partial class MyDbContext: DbContext
{
static MyDbContext()
{
Database.SetInitializer<MyDbContext>(null);
}
public MyDbContext()
: base("Name=" + Utility.Constants.DbConnectionName)
{
Database.SetInitializer<MyDbContext>(null);
}
}
Where Utility.Constants.DbConnectionName = "TestConnection"
In my web.config of course I have:
<add name="TestConnection" connectionString="Data Source=MYSERVERIPADDRESS;Initial Catalog=unitdb;User ID=sa;Password=12345;Connect Timeout=600;MultipleActiveResultSets=True" providerName="System.Data.SqlClient" />
Then I also have a project that is a Windows Service.
Inside that project I have a reference to my Data project so I have a access to my Entity Framework entities. Also in my Windows service project I have an App.config with the same connection string (above) because If not then it won't know where to get the data.
Is there I way I can set inside my Windows service project a connection string dynamically? Because my windows service may connect to a different database (of course that database has the same structure so EF can get the info).
I'm build my webapi project based on this article: A simple POC using ASP.NET Web API, Entity Framework, Autofac, Cross Domain Support
However, I need to pass a connection string to the DbContext because a user that connects to the webapi can select a different database to work with.
Where and what is that best way to implement this? In the controller, having a separate 'service' or a singleton?
I'm currently building a website that requires multiple databases also. This is the basics (for now). It may not be the best/efficient way, but it does the job so that I can continue working on the site.
Let's say you have this connection and everything there is correct, except the database name can change.
<add name="MyDbContext" connectionString="Server=SomeConnection;Database=SomeDatabase;"providerName="System.Data.SqlClient" />
You can create a connection string on the fly with this
public MyDbContext CreateDbContext(string databaseName)
{
SqlConnectionStringBuilder sqlBuilder = new SqlConnectionStringBuilder(System.Configuration.ConfigurationManager.ConnectionStrings["MyDbContext"].ConnectionString); // Gets the default connection
sqlBuilder.InitialCatalog = databaseName; // Update the database name
return new MyDbContext(sqlBuilder.ToString());
}
Also, unless you want to automatically create a database if it doesn't exist, possibly from incorrect database name, you need to set the initializer. Simply pass the initializer null to prevent ef from automatically creating one. I set this in the constructor of MyDbContext
public class MyDbContext : DbContext
{
/* some other constructors */
public MyDbContext(string nameOrConnectionString, bool createDb)
: base(nameOrConnectionString)
{
if(!createdDb)
{
System.Data.Entity.Database.SetInitializer<MyDbContext>(null);
}
}
}
One way would be to have multiple ConnectionStrings in your web config. You could then select which ConnectionString to use depending on a parameter passed to your webapi:
In your web config:
<connectionStrings>
<add name="dataBase1" connectionString="/*db info here*/" providerName="System.Data.SqlClient" />
<add name="dataBase2" connectionString="/*db info here*/" providerName="System.Data.SqlClient" />
</connectionStrings>
Then in your code:
public someobject GetData(/*arguments*/, string dbType)
{
var connectionString = dbType == 'dataBase1'
? ConfigurationManager.ConnectionString['dataBase1'].toString()
: ConfigurationManager.ConnectionString['dataBase2'].toString()
/*then pass connectionString to dbContext*/
var dbContext = new DbContext(connectionString);
}