How to specify EntityFramework ProviderName in an Azure Function - entity-framework

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

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

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.

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.
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:
}

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

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() {}
}

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.

Related

Using interfaces in dependency injection in .Net MAUI

I am trying to implement dependency injection for the first time in my new project in .Net MAUI. For testing purposes, I want to fetch data from a local source in my ViewModel. The production scenario will fetch data from the remote data source using HttpClient.
Below is my code base structure:
I have an Interface:
public interface IApiService
{
Task<bool> GetSomething(string parameter);
Task<string> GetSomethingElse(string parameter);
}
I have two classes that derive from it.
public class LocalDataStore: IApiService
public class RemoteDataStore: IApiService
In my MauiProgram.cs, when I want to use Local Data Store:
builder.Services.AddSingleton<LocalDataStore>()
builder.Services.AddSingleton<IApiService>()
And for Remote Data Store
builder.Services.AddSingleton<RemoteDataStore>()
builder.Services.AddSingleton<IApiService>()
In my ViewModel:
public class Page1ViewModel
{
public Page1ViewModel(IApiService localDataStore)
{
var items = Task.Run(async () => await localDataStore.GetSomething(true));
}
}
While running the app, I get an error :
System.InvalidOperationException: 'Unable to resolve service for type '...IApiService' while attempting to activate 'ViewModels.Page1ViewModel'.'
What am I doing wrong or what else should I be doing?
Kindly help.
Thanks, and regards.
Edit*
Of course, it works if I use LocalDataStore or RemoteDataStore instead of IApiService, when I register the services with the builder. But then if I have to change from one data store to another, I will have to change that in all the ViewModel classes?
It was trivial.
I needed to register the service like so :
builder.Services.AddSingleton<IApiService,LocalDataStore>();
Thanks to https://youtu.be/paZNvvUNFi0, I realised that.
Hope this helps someone.

Read DbContextOptions value in Entity Framework core constructor

In my asp.net core service, I have this in the startup
services.AddDbContext<Mydb_Context>(
options => options.UseSqlServer(Configuration["Settings:ConnString"]));
and to make it works, I created also this snippet:
public Mydb_Context(DbContextOptions<Mydb_Context> options) : base(options)
{
//other stuff here
}
My problem now is: in "other stuff here" section, how can I read the options value and retrieve my connection string? Since there another dll and not the same project, I can't just read it again from the appsettings.json.
If I break my code during a debug session, I can see the connection string in the value inside this object, but I don't know how to get it properly. I see this value inside the "Extension" attribute of the options object.
So, how can I read the DbContextOptions value?
So, how can I read the DbContextOptions value?
You can (not publicly but using some EF Core internal infrastructure objects, in particular RelationalOptionsExtension class), but you don't need to.
To get the connection information, simply use context Database property and GetDbConnextion method:
public Mydb_Context(DbContextOptions<Mydb_Context> options) : base(options)
{
//other stuff here
var connectionString = this.Database.GetDbConnection().ConnectionString;
}

WCF - Entity Framework - ERR_CONNECTION_RESET

I got a problem with my WCF service. Here is the
[OperationContract]
[WebGet(UriTemplate = "/needs", ResponseFormat = WebMessageFormat.Json)]
List<CustomerNeed> getAllCustomerNeeds();
When I go on the page which call this service, I got this error
GET http://localhost:666/rest/Service1.svc/needs net::ERR_CONNECTION_RESET
When I'm trying to return a string instead of a List, it works.
CustomerNeed is a class generate from my database via EntityFramework.
In my service, I'm only calling an other method which is in an other class;
public List<CustomerNeed> getAllCustomerNeeds()
{
var needs = from cn in db.CustomerNeeds
select cn;
List<CustomerNeed> list = new List<CustomerNeed>();
foreach (CustomerNeed cusN in needs)
{
list.Add(cusN);
}
return list;
}
Maybe is it because I have a foreign key in my table CustomerNeed ?
When I do "LINQ to entities" to import my database, do I have to import tables that were created because of many to many relation ?
I will recommend you to create a simple custom class which will represent your CustomerNeeds database entity, initiate this object on the server side and pass to the client application. It can help you to avoid this problem and also it is recommended way to transfer data accross the WCF services.
In this case you need to do the next steps:
1) Create a public class CustomerNeeds and mark it with the DataContract attribute. For example:
[DataContract]
public class CustomerNeeds
{
[DataMember]
public SomeDataType PropertyName {get; set;}
}
2) Initiate this object on the service, change return datatype in getAllCustomerNeeds() method from the entity class to the newly created class CustomerNeed and pass this data to the clien
And that`s all.
You haven't shown where/what db is, but I'm assuming if you're using entity framework as your tag implies it's a entities context. You might be having some issues with the context already being disposed or not newed up correctly (though I would have expected you to receive a slightly different error if that's the case.)
It looks like you're going through some unnecessary steps in your function, I would think something like this would work:
public List<CustomerNeed> getAllCustomerNeeds()
{
using (var db = new YourContext()) // plug in your context object
{
return db.CustomerNeeds.ToList();
}
}
Additionally when you say it "works as a string" are you returning something small like "hello world"? you might need to take a look at your WCF configuration to make sure it can handle the amount of data you're trying to pass back and forth.
Hope this helps!

How to make Unity pass a constructor parameter based on Web API request?

I'm using Web API in a multi-tenant environnement. Each tenant has their own database with an identical structure. The tenant name is included in the URL as a sub-domain : http://{tenant}.mysite.com/api/doodad/action
I've written a message filter (DelegatingHandler) to pull out the tenant name and find the corresponding database. I'm currently stashing this value in the request parameters.
Pretty much every controller action needs access a DbContext pointing to this database. I'm not using a repository pattern. I've created a second constructor on my DbContext that accepts the database name.
I'm using Unity to inject an instance of the DbContext in the ApiController. How can I get Unity to call the second constructor on the DbContext passing in the value from the request parameter?
I haven't tried this yet, but I think it should work...
You could create an action filter that would add the HttpRequestMessage to the current hierarchical container and then register your custom DbContext type with an InjectionFactory to pull out the HttpRequestMessage and get the DB name.
Here's some untested code...
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class RegisterHttpRequestActionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var container = GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IUnityContainer)) as IUnityContainer;
container.RegisterInstance(actionContext.Request, new HierarchicalLifetimeManager());
}
}
and the configuration...
IUnityContainer rootContainer = new UnityContainer();
GlobalConfiguration.Configuration.DependencyResolver = new UnityHierarchicalDependencyResolver(rootContainer);
GlobalConfiguration.Configuration.Filters.Add(new RegisterHttpRequestActionAttribute());
rootContainer.RegisterType<MyDbContext>(new HierarchicalLifetimeManager(), new InjectionFactory(container =>
new MyDbContext(container.Resolve<HttpRequestMessage>().Properties["TenantDB"] as string)));
(FYI, I reference the Unity.AspNet.WebApi NuGet package to get the UnityHierarchicalDependencyResolver)

MVC4 + EntityFramework: metadata only found from within web.config - why is that?

I want to change our EF-driven database-first ASP.NET MVC4 web application in such a way that I can specify the database to connect to at runtime. For the beginning, I simply wanted to substitute the entry in the connectionStrings section of the web.config with a coded version. But first things first. Here's my web.config connection section:
<connectionStrings>
<add name="WEB_Entities" connectionString="metadata=~/bin/Models\WEB_Models.csdl|~/bin/Models\WEB_Models.ssdl|~/bin/Models\WEB_Models.msl;provider=System.Data.SqlClient;provider connection string="data source=testsvr;initial catalog=DEMO;persist security info=True;user id=sa;password=xxxxxxxx;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
</connectionStrings>
When using this setting, everything runs just fine. Now I tried to comment out this entry and hard-code it, thereby moving the setting from the web.config to the global.asax.cs.
I read about the EntityConnectionStringBuilder, but for the beginning I simply want to give the whole connection string as the constructor parameter of an EntityConnection:
string CS =
#"metadata=~/bin/Models\WEB_Models.csdl|
~/bin/Models\WEB_Models.ssdl|
~/bin/Models\WEB_Models.msl;
provider=System.Data.SqlClient;
provider connection string=""Data Source=testsvr\sqlexpress;
Initial Catalog=DEMO;
Integrated Security=True;MultipleActiveResultSets=True""";
conn = new EntityConnection(CS);
conn.Open();
The conn object is a static object that lives in my application class:
public static EntityConnection conn;
In order to use this connection object, I changed my DBContext code to use the aforementioned connection object as constructor parameter, rather than the Name of an entry in the web.config:
public partial class WEB_Entities : DbContext
{
public WEB_Entities()
: base(PAMVCTEST.MvcApplication.conn,true)
//: base("name=WEB_Entities")
{
}
Now when I compile an run the whole thing, the connection to the db server seems to be possible (because I get some network related errors when e.g. changing the datasource to something wrong), but the application does not find the given metadata files. This is the error:
The supplied connection string is not valid, because it contains insufficient mapping or metadata information. Parameter name: connection
I don't understand why the metadata files cannot be found, they are definitely present in the given location. As soon as I change everything back to using the web.config connection entry, everything works as expected.
I also tried changing the metadata files location to this:
res://*/Models.WEB_Models.csdl|res://*/Models.WEB_Models.ssdl|res://*/Models.WEB_Models.msl
I made sure that the resource names are correct with ILMerge. THe result is the same: when I use the web.config way, it works - when I set it by code, I get the same error as mentioned above.
What can I do to resolve this issue? Are there any workarounds? And why in the world do we have to cope with such awful and error-prone connection strings with nested escapings and stuff? It's 2013!!! :-]
Thanks for your help!
Call it from DbContext. Change your DbContext constructor to the following:
public class MyDbContext : DbContext
{
public MyDbContext()
: base("DefaultConnection")
{
}
public MyDbContext(string conStr)
: base(conStr)
{
}
// ...
}
Then add your desired ConStrs to the web config. Finally, when you want another ConStr than the DefaultConnection pass its name to the DbContext() constructor:
Models.MyDbContext db = new Models.MyDbContext("MyConStr");