I have an issue getting a DbContext
to correctly pull my connection string from my local.settings.json
Context:
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
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());
}
}
}
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:
ProviderName
attribute is camel caseSystem.Data.EntityClient
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!
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://docs.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
Here are two approaches that work for me:
Approach 1
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'`
public partial class TestEntities: DbContext
{
public TestEntities(string connectionString)
: base(connectionString)
{
}
string connectionString = Environment.GetEnvironmentVariable("connectionStringAppSettings");
using (var dbContext = new TestEntities(connectionString))
{
// Do Something
}
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")
{
}
public partial class TestEntities
{
public TestEntities(string connectionString)
: base(connectionString)
{
}
}
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.