web-dev-qa-db-fra.com

Nom de fournisseur manquant lors du débogage d'AzureFunction et du déploiement de la fonction Azure

J'ai un problème à obtenir une DbContext pour extraire correctement ma chaîne de connexion de mon local.settings.json

Le contexte:

  • Ceci est un projet de fonction Azure
  • Le code du problème principal est dans System.Data.Entity.Internal.AppConfig
  • Bien que j’ai un fichier local.settings.json, ce n’est pas du noyau dotnet. C'est .net 4.6.1

Message d'erreur: 

'La chaîne de connexion' ShipBob_DevEntities 'dans le fichier de configuration de l'application ne contient pas l'attribut providerName requis. "' 

Configuration Json:

{
  "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"
    }
  }
}  

Versions de configuration testées:

  • Déplacement du nom du fournisseur dans la valeur du jeton ConnectionString réelle: la même erreur se produit
  • Définition de l'attribut provider à l'intérieur de l'attribut ConnectionString sur EntityClient: cela n'a rien fait
  • Faire de ShipBob_DevEntities une valeur de chaîne = à la valeur de ConnectionString: cela génère de nouvelles erreurs similaires 

    les métadonnées de mots clés ne sont pas prises en charge

  • J'ai essayé d'utiliser une chaîne de connexion ADO qui lève une exception code first qui semble se produire lorsque votre chaîne de connexion est incorrecte dans une approche database first

J'ai pris la liberté de décompiler EntityFramework.dll en utilisant dotPeek et j'ai retracé le problème jusqu'à System.Data.Entity.Internal.LazyInternalConnection.TryInitializeFromAppConfig. Dans cette méthode, il existe un appel à LazyInternalConnection.FindConnectionInConfig qui crache un objet ConnectionStringSettings dont la valeur ProviderName est définie sur null. Malheureusement, je suis incapable de déboguer la classe AppConfig.cs qu'il semble utiliser pour générer cette valeur, donc je suis bloqué. 

 enter image description here

Jusqu'à présent, j'ai consulté ces deux articles. L'une d'elles indique de mettre le nom du fournisseur comme son propre jeton; Cependant, cela ne fonctionne pas.

https://github.com/Azure/azure-functions-cli/issues/193
https://github.com/Azure/azure-functions-cli/issues/46

Est-ce que quelqu'un connaît le format correct à utiliser dans local.settings.json pour une connexion Entity Framework?

11
Adrian

Donc, la solution a fini par être triviale. L'attribut ProviderName spécifié dans local.settings.jsonDOIT être un cas de chameau. 

D'après les discussions sur le hub git d'origine:
https://github.com/Azure/azure-functions-cli/issues/46
Affiche le nom du fournisseur comme étant la casse Pascal 

https://github.com/Azure/azure-functions-cli/issues/193
indique le pseudo-code du nom du fournisseur en tant qu'affaire camel Il était très facile de rater mais votre section de configuration doit être exactement comme suit 

"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"
  }
}  

Ces points sont importants: 

  • Assurez-vous que votre chaîne de connexion a des métadonnées
  • Si vous copiez votre chaîne depuis une configuration xml, assurez-vous de libérer les apostrophes
  • Assurez-vous que l'attribut ProviderName est camel case
  • Assurez-vous que le nom du fournisseur est System.Data.EntityClient 

Correction du nom de Provid manquant dans le déploiement

Remarque, cette réponse suppose que vous essayez d'utiliser le constructeur sans paramètre d'un DbContext. Si vous créez un nouveau code, vous pouvez facilement suivre la deuxième réponse votée.

J'ai trouvé un moyen de contourner le problème du nom du fournisseur tout en conservant l'utilisation de la configuration du portail et donc des emplacements de déploiement. Cela implique de définir la chaîne de connexion par défaut du contexte de base de données à l'aide de propriétés statiques 

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;
    }  

Cela implique que le développeur crée un paramètre d'application dans le portail Azure en tant qu'indicateur. Dans mon cas, il s'agit de AzureFunction. Cela garantit que notre code est uniquement exécuté dans une fonction Azure et que tous les autres clients de ce DbContext, qu'il s'agisse d'applications Web, d'applications Windows, etc., peuvent continuer à se comporter comme prévu. Cela implique également l'ajout de votre chaîne de connexion au portail Azure en tant que AppSetting et non en tant que chaîne de connexion réelle. Veuillez utiliser la chaîne de connexion complète, y compris les informations metadata, mais sans le nom du fournisseur!

MODIFIER

Vous devrez modifier votre modèle t4 de fichier .tt généré automatiquement pour vous assurer que ce code ne sera pas remplacé si vous utilisez db en premier. 

Voici un lien sur la syntaxe T4: https://docs.Microsoft.com/en-us/visualstudio/modeling/writing-a-t4-text-template

Et voici une explication sur les modèles EF T4: https://msdn.Microsoft.com/en-us/library/jj613116(v=vs.113).aspx#1159a805-1bcf-4700-9e99-86d182f143fe

10
Adrian

J'ai parcouru plusieurs questions et réponses similaires ici. Nombre d'entre eux sont trompeurs ou supposent que tout le monde est au même niveau et comprend le fonctionnement des fonctions Azure. il n'y a pas de réponse pour les débutants comme moi. Je voudrais résumer ici ma solution étape par étape. Je ne pense pas que la réponse fournie soit la meilleure option car elle vous oblige à modifier les fichiers edmx générés automatiquement qui peuvent être écrasés par erreur ou lors de la prochaine mise à jour de votre edmx à partir de la base de données. La meilleure option consiste également à utiliser des chaînes de connexion au lieu des paramètres de l'application, à mon avis.

  1. le plus important, c’est que nous comprenions le fichier local.settings.jsonIS PAS POUR Azure. il s'agit d'exécuter votre application dans la section locale comme son nom l'indique clairement. La solution n’a donc rien à voir avec ce fichier.

  2. App.Config ou Web.Config ne fonctionne pas pour les chaînes de connexion de fonction Azure. Si vous disposez d'une bibliothèque de couches de base de données, vous ne pouvez pas écraser la chaîne de connexion à l'aide de l'une de ces méthodes, comme vous le feriez dans les applications Asp.Net.

  3. Pour pouvoir travailler avec, vous devez définir votre chaîne de connexion sur le portail Azure sous le Application Settings dans votre fonction Azure. Il y a des chaînes de connexion Là, vous devriez copier votre chaîne de connexion de votre DBContext. si c'est edmx, cela ressemblera à celui ci-dessous. Il y a un type de connexion, je l'utilise SQlAzure mais j'ai testé avec Custom (quelqu'un a prétendu ne fonctionne qu'avec custom) fonctionne avec les deux. 

metadata = res: // /Models.myDB.csdl|res:// / Models.myDB.ssdl | res: //*/Models.myDB.msl; provider = System.Data.SqlClient; provider chaîne de connexion = 'source de données = [yourdbURL]; initiale catalog = myDB; informations de sécurité persistantes = True; utilisateur id = xxxx; mot de passe = xxx; MultipleActiveResultSets = True; App = EntityFramework

  1. Après avoir configuré cela, vous devez lire l'URL dans votre application et fournir le DBContext. DbContext implémente un constructeur avec un paramètre de chaîne de connexion. Par défaut, le constructeur est sans paramètre mais vous pouvez l'étendre. Si vous utilisez la classe POCO, vous pouvez modifier simplement la classe DbContext. Si vous utilisez des classes Edmx générées par une base de données comme moi, vous ne souhaitez pas toucher à la classe edmx générée automatiquement au lieu de créer une classe partielle dans le même espace de nom et étendre cette classe comme ci-dessous.

Ceci est généré automatiquement DbContext

namespace myApp.Data.Models
{   

    public partial class myDBEntities : DbContext
    {
        public myDBEntities()
           : base("name=myDBEntities")
        {
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            throw new UnintentionalCodeFirstException();
        }

}

c'est la nouvelle classe partielle, vous créez

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. Après tout, vous pouvez obtenir la chaîne de connexion à partir des paramètres Azure, dans votre projet Azure Function avec le code ci-dessous et fournir à votre DbContext MyDBEntities est le nom que vous avez donné dans le portail Azure pour votre chaîne de connexion.
var connString = ConfigurationManager.ConnectionStrings["myDBEntities"].ConnectionString;


 using (var dbContext = new myDBEntities(connString))
{
        //TODO:
}
9
batmaci

J'ai déjà rencontré un problème similaire auparavant, j'utiliserais l'approche suivante pour atteindre mon objectif, vous pouvez vous y référer:

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"
  }
} 

Pour récupérer la chaîne de connexion:

var connString = ConfigurationManager.AppSettings["sqldb-connectionstring"];
//or var connString = ConfigurationManager.ConnectionStrings["Bruce_SQLConnectionString"].ConnectionString;
using (var dbContext = new BruceDbContext(connString))
{
    //TODO:
}

Ou vous pouvez initier votre constructeur sans argument pour votre DbContext comme suit:

public class BruceDbContext:DbContext
{
    public BruceDbContext()
        : base("Bruce_SQLConnectionString")
    {
    }

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

Ensuite, vous pouvez créer l'instance pour votre DbContext comme suit:

using (var dbContext = new BruceDbContext(connString))
{
    //TODO:
}

De plus, vous pouvez vous référer à Fichier de paramètres locaux pour Azure Functions.

0
Bruce Chen