it-roy-ru.com

Отсутствует ProviderName при отладке AzureFunction, а также при развертывании функции Azure.

У меня проблема с получением DbContext для правильного извлечения строки подключения из моего local.settings.json

Контекст:

  • Это функциональный проект Azure
  • Основной код проблемы находится в System.Data.Entity.Internal.AppConfig
  • Хотя у меня есть файл local.settings.json, это не ядро ​​dotnet. Это .net 4.6.1

Сообщение об ошибке: 

«Строка подключения« ShipBob_DevEntities »в файле конфигурации приложения не содержит обязательный атрибут providerName.» 

Конфигурация 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"
    }
  }
}  

Проверенные версии конфигурации:

  • Перемещение имени провайдера в фактическое значение токена ConnectionString: такие же ошибки
  • Установка атрибута provider внутри атрибута ConnectionString в EntityClient: это ничего не дало
  • Присвоение ShipBob_DevEntities строкового значения = значению ConnectionString: это приводит к появлению новых ошибок, подобные которым 

    метаданные ключевых слов не поддерживаются

  • Я попытался использовать строку подключения ADO, которая выдает исключение code first, которое, по-видимому, возникает, когда строка подключения неверна в подходе database first

Я взял на себя смелость декомпилировать EntityFramework.dll с помощью dotPeek и отследил проблему до System.Data.Entity.Internal.LazyInternalConnection.TryInitializeFromAppConfig. Внутри этого метода есть вызов LazyInternalConnection.FindConnectionInConfig, который выплевывает объект ConnectionStringSettings, для которого значение ProviderName имеет значение null. К сожалению, я не могу отладить класс AppConfig.cs, который он использует для генерации этого значения, поэтому я застрял. 

 enter image description here

До сих пор я ознакомился с этими двумя статьями. Одна из них гласит, что имя провайдера следует указывать как собственный токен; Однако это не работает.

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

Кто-нибудь знает правильный формат для использования в local.settings.json для подключения Entity Framework?

11
Adrian

Таким образом, решение оказалось тривиальным. Атрибут ProviderName, указанный в local.settings.json, ДОЛЖЕН быть верблюжьим. 

Из оригинальных обсуждений git hub:
https://github.com/Azure/azure-functions-cli/issues/46
Показывает имя провайдера как регистр Паскаля 

https://github.com/Azure/azure-functions-cli/issues/193
Показывает имя провайдера, являющееся регистром верблюда в псевдокоде Это было очень легко пропустить, но ваш раздел конфигурации должен быть точно таким, как указано ниже. 

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

Эти пункты важны: 

  • Убедитесь, что в строке подключения есть метаданные
  • Если вы копируете строку из конфигурации xml, убедитесь, что вы удалили апострофы
  • Убедитесь, что атрибут ProviderName равен camel case
  • Убедитесь, что имя провайдера System.Data.EntityClient 

Исправлено пропущенное имя провайдера при развертывании

Обратите внимание, этот ответ предполагает, что вы пытаетесь использовать конструктор без параметров DbContext. Если вы создаете новый код, вы можете легко следовать за вторым ответом с голосованием

Я нашел способ обойти проблему имени провайдера, сохранив при этом использование конфигурации портала и, следовательно, слотов развертывания. Он включает установку строки соединения по умолчанию для контекста БД с использованием статических свойств. 

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

Это требует от разработчика создания настройки приложения на портале Azure в качестве флага. В моем случае это AzureFunction. Это гарантирует, что наш код запускается только в функции Azure, и все другие клиенты этого DbContext, будь то веб-приложения, приложения Windows и т.д., Могут продолжать вести себя как положено. Это также включает добавление строки подключения к порталу Azure в виде AppSetting, а не фактической строки подключения. Пожалуйста, используйте полную строку подключения, включая информацию metadata, но без имени провайдера!

Правка

Вам нужно будет отредактировать автоматически сгенерированный шаблон ttt-файла t4, чтобы убедиться, что этот код не будет переопределен, если вы сначала используете db. 

Вот ссылка на синтаксис T4: https://docs.Microsoft.com/en-us/visualstudio/modeling/writing-a-t4-text-template

А вот объяснение шаблонов EF T4: https://msdn.Microsoft.com/en-us/library/jj613116(v=vs.113).aspx#1159a805-1bcf-4700-9e99-86d182f143fe

10
Adrian

Я прошел несколько похожих вопросов и ответов здесь. Многие из них либо вводят в заблуждение, либо предполагают, что все находятся на одном уровне и понимают, как работают функции Azure. нет ответа для новичков, как я. Я хотел бы суммировать здесь мое решение шаг за шагом. Я не думаю, что предоставленный ответ - лучший вариант, потому что он заставляет вас изменять автоматически созданные файлы edmx, которые могут быть перезаписаны по ошибке или при следующем обновлении вашего edmx из базы данных. Также, на мой взгляд, лучше всего использовать строки подключения вместо настроек приложения.

  1. самое главное, что мы понимаем файл local.settings.json НЕ ДЛЯ Azure. оно должно запускать ваше приложение на локальном компьютере, поскольку имя ясно говорит. Таким образом, решение не имеет ничего общего с этим файлом.

  2. App.Config или Web.Config не работают со строками подключения функций Azure. Если у вас есть библиотека уровня базы данных, вы не можете перезаписать строку подключения, используя любую из них, как в приложениях Asp.Net.

  3. Чтобы работать с ним, вам нужно определить строку подключения на портале Azure в Application Settings в вашей функции Azure. Есть Строки подключения. там вы должны скопировать строку подключения вашего DBContext. если это edmx, это будет выглядеть так, как показано ниже. Существует тип подключения, я использую его SQlAzure, но я тестировал с Custom (кто-то утверждал, что работает только с Custom) работает с обоими. 

metadata = res: // /Models.myDB.csdl|res:// / Models.myDB.ssdl | res: //*/Models.myDB.msl; provider = System.Data.SqlClient; поставщик Строка подключения = 'источник данных = [yourdbURL]; исходный catalog = myDB; постоянная информация о безопасности = True; пользователь ID = хххх, пароль = ххх, MultipleActiveResultSets = True; App = EntityFramework

  1. После того, как вы это настроите, вам нужно прочитать URL в вашем приложении и предоставить DBContext. DbContext реализует конструктор с параметром строки подключения. По умолчанию конструктор не имеет параметров, но вы можете расширить его. если вы используете класс POCO, вы можете просто изменить класс DbContext. Если вы используете сгенерированные базой данных классы Edmx, как я, вы не хотите трогать автоматически сгенерированный класс edmx вместо того, чтобы создавать частичный класс в том же пространстве имен и расширять этот класс, как показано ниже.

Это автоматически сгенерированный DbContext

namespace myApp.Data.Models
{   

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

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

}

это новый частичный класс, вы создаете

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. В конце концов, вы можете получить строку подключения в настройках Azure в своем проекте функции Azure с приведенным ниже кодом и указать свой DbContext MyDBEntities - это имя, которое вы указали на портале Azure для своей строки подключения.
var connString = ConfigurationManager.ConnectionStrings["myDBEntities"].ConnectionString;


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

Я сталкивался с подобной проблемой раньше, я бы использовал следующий подход для достижения своей цели, вы могли бы обратиться к нему:

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

Для получения строки подключения:

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

Или вы можете запустить свой конструктор без аргументов для DbContext следующим образом:

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

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

Затем вы можете создать экземпляр для вашей DbContext следующим образом:

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

Кроме того, вы можете обратиться к файлу локальных настроек для функций Azure.

0
Bruce Chen