Отсутствует имя поставщика при отладке AzureFunction, а также развертывание функции azure

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

Context:

  • Это проект функции 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: та же ошибка ocurrs
  • Установка атрибута 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, который, по-видимому, используется для генерации этого значения, поэтому я застрял.

введите описание изображения здесь

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

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

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

Ответ 1

Таким образом, решение оказалось тривиальным. Атрибут 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 верблюжий
  • Убедитесь, что имя поставщика - 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, а не фактической строки подключения. Пожалуйста, используйте полную строку подключения, включая метаданные, но без имени провайдера!

РЕДАКТИРОВАТЬ

Вам нужно будет отредактировать ваш автоматически сгенерированный шаблон .tt файла 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

Ответ 2

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

  • Самое главное, что мы понимаем файл local.settings.json НЕ ДЛЯ ЖИЗНИ. это запустить ваше приложение в локальном, так как имя ясно говоря. Поэтому решение не имеет ничего общего с этим файлом.

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

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

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

  1. После того, как вы установите это, вам нужно прочитать URL-адрес в своем приложении и предоставить DBC-текст. 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 Function с кодом ниже и предоставить вашему DbContext myDBEntities - это имя, которое вы указали на лазурном портале для вашей строки подключения.
var connString = ConfigurationManager.ConnectionStrings["myDBEntities"].ConnectionString;


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

Ответ 3

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

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.

Ответ 4

Вот два подхода, которые работают для меня:

Подход 1

  • Добавьте строку подключения в настройки приложения (соответственно local.settings.json) в следующем формате:

metadata = res:///xxx.csdl | res:///xxx.ssdl | res://*/xxx.msl; provider = System.Data.SqlClient; строка подключения поставщика = 'источник данных = xxx.database. windows.net; начальный каталог = xxx; идентификатор пользователя = xxx; пароль = xxx; MultipleActiveResultSets = True; App = EntityFramework ''

  • Перейти к классу, который расширяет DbContext ("TestEntities") и расширить конструктор, чтобы принять строку подключения в качестве аргумента
public partial class TestEntities: DbContext
{
    public TestEntities(string connectionString)
        : base(connectionString)
    {
    }
  • Если вы хотите затем взаимодействовать с базой данных, вам нужно извлечь строку подключения из настроек приложения и затем передать ее при инициализации DbContext.
string connectionString = Environment.GetEnvironmentVariable("connectionStringAppSettings");

using (var dbContext = new TestEntities(connectionString))
{
// Do Something
}
  • Проблема с этим подходом состоит в том, что каждый раз, когда вы обновляете базу данных, вам нужно обновлять класс "TestEntities", так как он перезаписывается

Подход 2

  • Цель здесь состоит в том, чтобы оставить класс TestEntities, чтобы избежать проблемы из подхода 1

  • Добавьте строку подключения в настройки приложения (соответственно local.settings.json), как в подходе 1

  • Оставьте TestEntities как есть

public partial class TestEntities : DbContext
    {
        public TestEntities ()
            : base("name=TestEntities")
        {
        }
  • Поскольку TestEntities является частичным, вы можете расширить этот класс, создав еще один, который также является частичным с тем же именем в том же пространстве имен. Цель этого класса - предоставить конструктор, который принимает строку подключения в качестве аргумента.

public partial class TestEntities
{
    public TestEntities(string connectionString)
        : base(connectionString)
    {
    }
}
  • Тогда вы можете продолжать как с подходом 1