10

I'm trying to port some webjob code to the new Azure Functions. So far I've managed to import my DLL's and reference them succesfully, but when I use the connection string in my code, I get an error saying I have to add the ProviderName:

The connection string 'ConnectionString' in the application's configuration file does not contain the required providerName attribute."

Which is normally not a problem because in a webjob (or web app), this will be in the App or Web.config, and the connectionstring will simply be overwritten with whatever I entered in Azure.

With Azure Functions, I don't have a web.config (Although I tried adding one to no avail), so naturally the providername is missing.

How do I specify that?

EDIT: After some playing around and some helpful tips by people below, I've almost managed to get it working.

What I do now is the following:

    var connString = **MY CONN STRING FROM CONFIG**; // Constring without metadata etc.
    EntityConnectionStringBuilder b = new EntityConnectionStringBuilder();
    b.Metadata = "res://*/Entities.MyDB.csdl|res://*/Entities.MyDB.ssdl|res://*/Entities.MyDB.msl";
    b.ProviderConnectionString = connString.ConnectionString;
    b.Provider = "System.Data.SqlClient";
    return new MyDB(b.ConnectionString);

Which gives me what I need for calling the database. I use a static method in a partial class to get an instance of the Database which runs the above code, and I decorate my MyDB Partial with [DbConfigurationType(typeof(MyDbConfiguration))]

I define that configuration as:

public class MyDBConfiguration: DbConfiguration
{
    public MyDBConfiguration()
    {
        SetProviderFactory("System.Data.EntityClient", EntityProviderFactory.Instance);
    }
}

My problem remains when I want to actually use the EF Entities. Here, it will try to initialize the database type using the original configuration, giving me the original error once again. As per this stack trace:

at Void Initialize()
at System.Data.Entity.Internal.EntitySetTypePair GetEntitySetAndBaseTypeForType(System.Type)
at Void InitializeContext()
at System.Data.Entity.Core.Objects.ObjectContext CreateObjectContextFromConnectionModel()
at Void Initialize()
at Boolean TryInitializeFromAppConfig(System.String, System.Data.Entity.Internal.AppConfig)
at Void InitializeFromConnectionStringSetting(System.Configuration.ConnectionStringSettings)

So how do I avoid this? I guess I need a way to hook into everything and run my custom setter..

bech
  • 627
  • 5
  • 22

6 Answers6

7

In the end, Stephen Reindel pushed me in the right direction; Code-based Configuration for Entity Framework.

[DbConfigurationType(typeof(MyDBConfiguration))]
public partial class MyDB
{
   public static MyDB GetDB()
   {
      var connString = **MY CONN STRING FROM SOMEWHERE**; // Constring without metadata etc.
      EntityConnectionStringBuilder b = new EntityConnectionStringBuilder();
      b.Metadata = "res://*/Entities.MyDB.csdl|res://*/Entities.MyDB.ssdl|res://*/Entities.MyDB.msl";
      b.ProviderConnectionString = connString.ConnectionString;
      b.Provider = "System.Data.SqlClient";
      return new MyDB(b.ConnectionString);
   }

   public MyDB(string connectionString) : base(connectionString)
   {
   }
}

With MyDbConfiguration like this:

public class MyDBConfiguration: DbConfiguration
{
    public MyDBConfiguration()
    {
        SetProviderServices("System.Data.SqlClient", SqlProviderServices.Instance);
        SetDefaultConnectionFactory(new SqlConnectionFactory());
    }
}

With the above code, EF never asks for AppConfig-related config files. But remember, if you have EF entries in your config file, it will attempt to use them, so make sure they're gone.

In terms of azure functions, this means I used the Azure Functions configuration panel in azure to punch in my ConnectionString without the Metadata and providername, and then loaded that in GetDB.

Edit: As per request, here is some explanatory text of the above: You can't add EF metadata about the connection in Azure Functions, as they do not use an app.config in which to specify it. This is not a part of the connection string, but is metadata about the connection besides the connection string that EF uses to map to a specific C# Class and SQL Provider etc. To avoid this, you hardcode it using the above example. You do that by creating a class inheriting from DBConfiguration, and you mark (with an attribute on a partial class) your EF database class with that.

This DBConfiguration contains a different kind of way to instantiate a new database object, in which this metadata is hardcoded, but the connectionstring is retrieved from your app settings in Azure. In this example I just used a static method, but I guess it could be a new constructor also.

Once you have this static method in play, you can use that to get a new database in your database code, like this:

using (var db = MyDB.GetDB()) {
   // db code here.
}

This allows you to use EntityFramework without an APP.Config, and you can still change the connectionstring using Azure Functions APP settings.

Hope that helps

bech
  • 627
  • 5
  • 22
  • Could you show with more detail, how to use that in Azure Function? I'm quite new to it and I don't fully understand your solution. – mnj Jul 24 '17 at 13:25
  • Hi. I will post a more beginner friendly guide at work tomorrow (20ish hours from now) – bech Jul 25 '17 at 14:13
  • 1
    I'm not quite sure what part of the above explanation you need more details with, but I've updated the response with some additional explanations. – bech Jul 26 '17 at 10:15
3

Using this question you can set your default factory before opening the connection by having your personal DbConfiguration class (see this link also for usage):

public class MyDbConfiguration : DbConfiguration
{
    public MyDbConfiguration()
    {
        SetDefaultConnectionFactory(new SqlConnectionFactory());
    }
}

Now you need to tell your DbContext to use the new configuration. As using web.config or app.config is no option, you may use an attribute to add the configuration:

[DbConfigurationType(typeof(MyDbConfiguration))] 
public class MyContextContext : DbContext 
{ 
}

Now using a connection string on your DbContext will use the SQL provider by default.

Community
  • 1
  • 1
Stephen Reindl
  • 5,659
  • 2
  • 34
  • 38
  • This seems like a suitable solution, but I can't quite get it to work.. How would you specify the ProviderName "System.Data.EntityClient", but leave everything else as it was? – bech Oct 07 '16 at 14:52
  • Do you use ObjectContext and not CodeFirst? – Stephen Reindl Oct 07 '16 at 14:53
  • (I pressed enter too fast, hence the update of the previous comment): I apparently can't figure out how to set the ProviderName, because it keeps telling me it's missing. – bech Oct 07 '16 at 14:54
  • I guess I'm using ObjectContext? I'm generating a context from an existing database, so I have a DbContext. It's calling the custom configuration just fine, but I just don't know what to write to set the ProviderName. I've tried: SetProviderFactory("System.Data.EntityClient", EntityProviderFactory.Instance) to no avail. – bech Oct 07 '16 at 14:56
  • Tried by myself, adding the lines `SetProviderFactory("System.Data.SqlClient", SqlClientFactory.Instance);` may help. I've created a completely empty app.config file and it created a SQL instance. – Stephen Reindl Oct 07 '16 at 15:09
  • BTW: instead of web.config, did you try to use app.config? – Stephen Reindl Oct 07 '16 at 15:13
  • Since the providername i use when I do it through the app config is System.Data.EntityClient, does this still apply? I figured SetProviderFactory("System.Data.EntityClient", EntityProviderFactory.Instance) would replace what you tried. Also; how are you supplying the actual connection string? I'm getting mine from the configuration file, but I've also tried just hardcoding it just to see if it worked. It did not :/ – bech Oct 07 '16 at 15:16
  • Yep, I've tried both in Azure Functions. It seems neither are supported? – bech Oct 07 '16 at 15:17
  • hmmm... I'l give it a try later on. – Stephen Reindl Oct 07 '16 at 15:19
  • I've tried hard to get my function app running but it seems I've messed up my azure account :( Your comment above with SetProviderFactory seems to be the correct approach, if this is not working I'm lost ... Maybe somebody from MSFT might help? – Stephen Reindl Oct 07 '16 at 16:28
  • Thanks for trying Stephen. Actually, just before I left work I managed to find a working configuration. When I get back monday I'll post the solution.. Or well, it worked locally without the providername in the config, so hopefully it'll work in a function ;) – bech Oct 08 '16 at 20:49
  • Okay @stephen-reindl, I've managed to halfway get this working using the code in my edit. But my problem still remains for some calls as per the edit.. It's more of an EF question now than Azure Functions, so maybe you can point me in the right direction? Thanks. – bech Oct 10 '16 at 14:54
3

Provided answer is perfect and it helped me a lot but it is not dynamic as I dont want to hardcode my connectionstring. if you are working the slots in azure functions. I was looking for a solution where I can use more than 1 connection strings. Here is my alternative approach step by step for anybody else struggling with this problem.

  1. 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.

  2. 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.

  3. 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

  1. 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());
            }
        }
    }
  1. 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:
}
Emil
  • 6,411
  • 7
  • 62
  • 112
  • An addendum that I would make to this would be that in order to properly generate the datacontext, you will need to make a second project that is a .net library. The entity framework auto generation won't work in an azure function project correctly. – Taugenichts Feb 13 '19 at 02:27
1

Adding an answer in the event you cannot simply change the way you instantiate you DbContext. This would occur if you are calling code that has DbContexts being instatiated with the parameter-less constructor.

It involves using a static constructor to read your connection string from the appsettings in the azure portal and passing it in to your DbContext base constructor. This allows you to circumvent the need for a providerName and also allows you to retain use of the portal configuration without needing to hardcode anything.

Please refer to my accepted answer here: Missing ProviderName when debugging AzureFunction as well as deploying azure function

Adrian
  • 3,332
  • 5
  • 34
  • 52
1

Stumbled upon this and solved it like this, inside of the Azure Function.

public static class MyFunction
{
     // Putting this in more than one place in your project will cause an exception,
     // if doing it after the DbConfiguration has been loaded.
     static MyFunction() =>
         DbConfiguration.Loaded += (_, d) =>
             d.AddDefaultResolver(new global::MySql.Data.Entity.MySqlDependencyResolver());


    // The rest of your function...
    //[FunctionName("MyFunction")]
    //public static async Task Run() {}
}
Mikael Dúi Bolinder
  • 2,080
  • 2
  • 19
  • 44
0

You can access the site's App Settings by going to the portal, clicking Function app settings and then Configure app settings. That will open up a blade that allows you to set all the app settings for your function app. Just use the same key and value that you'd use for your web.config.

brettsam
  • 2,702
  • 1
  • 15
  • 24
  • 2
    Yep, but not ProviderName for EntityFramework, so how do I specify that? – bech Oct 07 '16 at 13:39
  • Oh, good point. Specifically, you need `providerName="System.Data.EntityClient"`? I think even if you set it in the 'Connection Strings' section in the portal and choose 'Custom', it'll try to pull it from the web.config -- which, as you stated, you don't have. If the other answers here don't help you, I'll try to figure out if there's another way to do this via settings. – brettsam Oct 07 '16 at 14:26
  • (Read it wrong the first time, hence the deletion of my previous comment): Yep, that's the one I need. I'm trying to do it via code atm as suggested, but it seems like something that should be supported in the portal. – bech Oct 07 '16 at 14:45
  • I asked around and unfortunately this appears to be a limitation you'll hit in Functions. As you've seen, because we don't allow you to supply your own web.config, the Connection String settings don't work as you'd expect when you need to use 'Custom'. The code approach that I see in another comment may be your best bet. – brettsam Oct 10 '16 at 15:12
  • Hi Brett. Thanks for the follow up. I'm halfway with a solution on EF so it appears viable. PS: I hope you're aware of the very tedious process of updating/deleting DLL's in the bin folder in Kudu, as the damn thing is being used by the function and you have to go kill the w3wp process before you're allowed. – bech Oct 11 '16 at 07:18