Использование клиентской библиотеки эластичных баз данных с Entity Framework

Применимо к:База данных SQL Azure

В этом документе показаны изменения, которые следует внести в приложение Entity Framework для интеграции со средствами эластичных баз данных. Основное внимание уделяется совмещению методов управления картой сегментов и маршрутизации, зависящей от данных, с помощью подхода Entity Framework Code First. Руководство Code First — создание базы данных для Entity Framework используется в этом документе как пример. Примером кода для этого документа является часть набора примеров для эластичной базы данных в Visual Studio.

Примечание.

Эта статья неприменима к Entity Framework Core (EF Core).

Загрузка и выполнение примера кода

Как загрузить код для этой статьи

  • Требуется Visual Studio 2012 или более поздней версии.
  • Скачайте пример средств эластичной базы данных для SQL Azure (интеграция с Entity Framework). Распакуйте образец в выбранную папку.
  • Запустите среду Visual Studio.
  • Откройте Visual Studio, выберите "Файл" -> "Открыть проект или решение".
  • В диалоговом окне Открытие проекта перейдите к скачанному образцу и выберите EntityFrameworkCodeFirst.sln, чтобы его открыть.

Чтобы запустить пример, необходимо создать три пустые базы данных в Базе данных SQL Azure:

  • База данных диспетчера сопоставления сегментов
  • База данных сегмента 1
  • База данных сегмента 2

Создав эти базы данных, замените заполнители в файле Program.cs именем своего сервера, именами баз данных и учетными данными для подключения к этим базам данных. Постройте решение в Visual Studio. В процессе построения Visual Studio скачает необходимые пакеты NuGet для клиентской библиотеки эластичной базы данных, платформы Entity Framework и обработки временных сбоев. Убедитесь, что для вашего решения включена функция восстановления пакетов NuGet. Этот параметр можно включить, щелкнув файл решения правой кнопкой мыши в обозревателе решений Visual Studio.

Рабочие процессы Entity Framework

Разработчики Entity Framework используют один из следующих четырех рабочих процессов для построения приложений и обеспечения сохранения объектов приложения.

  • Code First (создание базы данных). Разработчик EF в коде приложения создает модель, и затем на ее основе платформа EF создает базу данных.
  • Code First (имеющаяся база данных). Разработчик позволяет EF сформировать для модели код приложения на основе имеющейся базы данных.
  • Model First. Разработчик создает модель в конструкторе EF, после чего EF создает базу данных на основе модели.
  • Database First. Разработчик использует инструментарий EF для получения модели из имеющейся базы данных.

Во всех подходах класс DbContext используются для прозрачного управления подключениями к базе данных и схемой базы данных приложения. Различные конструкторы базового класса DbContext обеспечивают разный уровень управления при создании подключений и схем, а также при начальной загрузке базы данных. В основном сложности связаны с тем, что управление подключениями к базе данных, обеспечиваемое EF, пересекается с функциями управления подключением интерфейсов маршрутизации, зависящей от данных, предоставляемых клиентской библиотекой эластичной базы данных.

Допущения в отношении средств эластичной базы данных

Определения терминов см. в статье Глоссарий по средствам работы с эластичными базами данных.

При использовании клиентской библиотеки эластичной базы данных данные приложения разделяются на сегменты, называемые шардлетами. Шардлеты идентифицируются по ключу сегментирования и сопоставляются с определенными базами данных. Приложение может иметь любое количество баз данных и распределять шардлеты по мере необходимости для предоставления достаточной емкости и производительности, исходя из текущих бизнес-требований. Сопоставление значений ключей сегментирования с базами данных хранится в сопоставлении сегментов, которое предоставляют клиентские API-интерфейсы эластичной базы данных. Эта функция называется управлением размещением сегментов (или сокращенно SMM). Сопоставление сегментов также выступает в качестве посредника подключений к базе данных для запросов с ключом сегментирования. Эта функция называется маршрутизацией, зависящей от данных.

Диспетчер сопоставления сегментов защищает пользователей от получения несогласованных представлений данных шардлета, которые могут иметь место при выполнении параллельных операций управления шардлетом (например, при перемещении данных из одного сегмента в другой). Для предотвращения таких случаев сопоставления сегментов, управляемые клиентской библиотекой, устанавливают подключения к базе данных для приложения. Это позволяет сопоставлению сегментов автоматически завершить соединение с базой данных, если операции управления сегментами могут повлиять на шардлет, для которого создано подключение. Этот подход должен интегрироваться с некоторыми функциями EF, такими как создание новых подключений из существующего к проверка для существования базы данных. В общем случае наши наблюдения показали, что стандартные конструкторы DbContext работают надежно только для закрытых подключений к базе данных, которые можно безопасно клонировать для работы в EF. В противоположность этому принцип эластичной базы данных заключается в использовании открытых подключений только через посредника. Можно прийти к выводу, что закрытие подключения через клиентскую библиотеку перед его передачей в EF DbContext может решить проблему. Однако, закрыв подключение и полагаясь на то, что EF откроет его снова, разработчик отказывается от проверок согласованности, выполняемых библиотекой. Однако, функции миграции в EF используют эти подключения для управления схемой базы данных таким образом, что это эти действия видны и в приложении. В идеальном случае необходимо сохранить и объединить возможности клиентской библиотеки эластичной базы данных и платформы EF в одном приложении. В следующем разделе эти свойства и требования описываются более подробно.

Requirements

Во время работы одновременно с клиентской библиотекой эластичной базы данных и интерфейсами API Entity Framework цель заключается в том, чтобы сохранить следующие свойства.

  • Горизонтальное масштабирование.Необходимо добавлять и удалять базы данных на уровне данных сегментированного приложения в соответствии с потребностями приложения в ресурсах. Это означает контроль создания баз данных и их удаления, а также использование интерфейсов API диспетчера сопоставления сегментов эластичной базы данных для управления базами данных и сопоставлениями шардлетов.
  • Согласованность. В приложении применяется сегментирование и используются возможности клиентской библиотеки по маршрутизации, зависящей от данных. Чтобы избежать повреждения данных и получения неправильных результатов запросов, соединения проходят через посредника в виде диспетчера сопоставления сегментов. Это также позволяет использовать проверки и поддерживать согласованность данных.
  • Код первое. Чтобы сохранить удобство кода EF в первой парадигме. В парадигме Code First классы приложения прозрачно сопоставляются со структурами базы данных. Код приложения взаимодействует с наборами DbSet, в которых упрятаны большинство используемых аспектов обработки базы данных.
  • Схема.Entity Framework берет на себя создание начальной схемы базы данных и последующего развития схемы в ходе миграций. Если сохранить эти возможности, то адаптация приложения будет таким же простым как развитие данных.

В следующем руководстве показано, как с помощью средств эластичной базы данных достичь соответствия этим требованиям для приложений с приоритетом кода.

Маршрутизация, зависящая от данных, с использованием EF DbContext

Подключения к базам данных в платформе Entity Framework обычно управляются через подклассы DbContext. Создайте эти подклассы, сделав их производными от DbContext. Здесь определяются наборы DbSet , реализующие коллекции CLR-объектов для приложения на основе базы данных. В контексте маршрутизации, зависящей от данных, можно выделить несколько полезных свойств, которые не обязательно будут применимы в других сценариях использования приложений EF Сode First.

  • База данных уже существует и зарегистрирована в сопоставлении сегментов эластичной базы данных.
  • Схема приложения уже развернута в базу данных (как описано ниже).
  • Подключение маршрутизации на основе данных к базе данных устанавливается через сопоставление сегментов.

Для интеграции DbContext с маршрутизацией на основе данных в случае горизонтального масштабирования нужно сделать следующее.

  1. Создать подключения к физическим базам данных через клиентские интерфейсы эластичной базы данных диспетчера сопоставления сегментов.
  2. Зашифровать подключение с помощью подкласса DbContext .
  3. Передать подключение в базовые классы DbContext , чтобы вся необходимая обработка также была выполнена и на стороне EF.

Этот подход показан в следующем примере кода. (Этот код также есть в соответствующем проекте Visual Studio)

public class ElasticScaleContext<T> : DbContext
{
public DbSet<Blog> Blogs { get; set; }
...

    // C'tor for data-dependent routing. This call opens a validated connection
    // routed to the proper shard by the shard map manager.
    // Note that the base class c'tor call fails for an open connection
    // if migrations need to be done and SQL credentials are used. This is the reason for the
    // separation of c'tors into the data-dependent routing case (this c'tor) and the internal c'tor for new shards.
    public ElasticScaleContext(ShardMap shardMap, T shardingKey, string connectionStr)
        : base(CreateDDRConnection(shardMap, shardingKey, connectionStr),
        true /* contextOwnsConnection */)
    {
    }

    // Only static methods are allowed in calls into base class c'tors.
    private static DbConnection CreateDDRConnection(
    ShardMap shardMap,
    T shardingKey,
    string connectionStr)
    {
        // No initialization
        Database.SetInitializer<ElasticScaleContext<T>>(null);

        // Ask shard map to broker a validated connection for the given key
        SqlConnection conn = shardMap.OpenConnectionForKey<T>
                            (shardingKey, connectionStr, ConnectionOptions.Validate);
        return conn;
    }

Основные положения

  • Новый конструктор заменяет конструктор по умолчанию подкласса DbContext

  • Новый конструктор принимает аргументы, которые требуются для маршрутизации, зависящей от данных, через клиентскую библиотеку эластичной базы данных.

    • сопоставление сегментов для доступа к интерфейсам маршрутизации на основе данных,
    • ключ сегментирования для определения шардлета,
    • Строка подключения с учетными данными для подключения к сегменту через маршрутизацию на основе данных.
  • Вызов конструктора базового класса приводит к выполнению статического метода, реализующего все действия, необходимые для маршрутизации на основе данных.

    • Конструктор использует вызов OpenConnectionForKey клиентских интерфейсов эластичной базы данных в сопоставлении сегментов для установления открытого подключения.
    • Сопоставление создает открытое подключение к сегменту, в котором содержится шардлет, соответствующий заданному ключу сегментирования.
    • Это открытое подключение передается обратно в конструктор базового класса DbContext, чтобы EF использовал его вместо автоматического создания нового подключения. Так подключение помечается клиентским API-интерфейсом эластичной базы данных, что гарантирует согласованность данных при выполнении операций по управлению сопоставлением сегментов.

Используйте в коде новый конструктор для подкласса DbContext вместо конструктора по умолчанию. Рассмотрим пример:

// Create and save a new blog.

Console.Write("Enter a name for a new blog: ");
var name = Console.ReadLine();

using (var db = new ElasticScaleContext<int>(
                        sharding.ShardMap,  
                        tenantId1,  
                        connStrBldr.ConnectionString))
{
    var blog = new Blog { Name = name };
    db.Blogs.Add(blog);
    db.SaveChanges();

    // Display all Blogs for tenant 1
    var query = from b in db.Blogs
                orderby b.Name
                select b;
    …
}

Новый конструктор открывает подключение к сегменту, который содержит данные для шардлета, определяемого значением tenantid1. Код в блоке using не изменяется, чтобы с помощью EF дать доступ к DbSet блогов в сегменте для tenantid1. Это меняет семантику кода в блоке using таким образом, что все операции с базой данных теперь относятся к одному сегменту, где хранится tenantid1 . Например, LINQ-запрос к DbSet блогов будет возвращать только блоги, хранящиеся в текущем сегменте.

Обработка временных сбоев

Команда Microsoft Patterns & Practices опубликовала статью 4 - Perseverance, Secret of All Triumphs: Using the Transient Fault Handling Application Block (4. Настойчивость — секрет всех побед. Использование блока приложения для обработки временных ошибок). Библиотека используется с клиентской библиотекой эластичного масштабирования совместно с EF. Тем не менее обеспечьте возврат всех временных исключений в место, где после временного сбоя будет использоваться новый конструктор, чтобы все попытки установки нового подключения выполнялись с помощью измененных нами конструкторов. В противном случае не гарантируется установка подключения с правильным сегментом и нет гарантий, что подключение будет поддерживаться в случае изменения в сопоставлении сегментов.

В следующем примере кода показано, как можно использовать политику повторных попыток SQL в новых конструкторах подкласса DbContext .

SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() =>
{
    using (var db = new ElasticScaleContext<int>(
                            sharding.ShardMap,  
                            tenantId1,  
                            connStrBldr.ConnectionString))
        {
                var blog = new Blog { Name = name };
                db.Blogs.Add(blog);
                db.SaveChanges();
        …
        }
    });

Политика SqlDatabaseUtils.SqlRetryPolicy в приведенном выше коде определена как SqlDatabaseTransientErrorDetectionStrategy с числом попыток, равным 10, и 5-секундным временем ожидания между повторами попытки. Такой подход согласуется с рекомендациями для EF и транзакций, инициированных пользователями. См. статью Entity Framework (Ограничения Entity Framework в стратегиях с повторами попыток (далее EF6)). В обоих случаях требуется, чтобы приложение определяло область действия, куда возвращается временное исключение: чтобы снова открыть транзакцию или (как показано) повторно создать контекст из подходящего конструктора, использующего клиентскую библиотеку эластичной базы данных.

Необходимость определять место в области действия, куда возвращаются временные исключения, также исключает использование встроенного класса SqlAzureExecutionStrategy, поставляемого с EF. SqlAzureExecutionStrategy будет снова открывать подключение, но не будет использовать OpenConnectionForKey, поэтому обходить все проверки, выполняемые при вызове OpenConnectionForKey. Вместо этого в примере кода используется встроенный класс DefaultExecutionStrategy, также поставляемый с EF. В отличие от SqlAzureExecutionStrategy он работает должным образом в сочетании с политикой повторных попыток из библиотеки обработки временных сбоев. Политика выполнения задана в классе ElasticScaleDbConfiguration. Обратите внимание на то, что мы решили не использовать DefaultSqlExecutionStrategy, так как он предполагает использование SqlAzureExecutionStrategy при появлении временных исключений, что может привести к неправильной реакции на событие, как описано выше. Дополнительные сведения о различных политиках повтора и EF см. в статье Entity Framework Connection Resiliency and Retry Logic (EF6 onwards) (Устойчивость подключения и логика повторных попыток Entity Framework (далее EF6)).

Переделка конструктора

Вышеприведенные примеры кода показывают переделку конструктора по умолчанию, необходимую для приложения, использующего маршрутизацию, зависящую от данных, с платформой Entity Framework. Следующая таблица обобщает этот подход для других конструкторов.

Текущий конструктор Переделанный конструктор для данных Базовый конструктор Примечания.
MyContext() ElasticScaleContext(ShardMap, TKey) DbContext(DbConnection, bool) Подключение должно быть функцией сопоставления карты сегментов и ключа маршрутизации на основе данных. Нужно обойти автоматическое создание подключения в EF и вместо этого использовать сопоставление сегментов в качестве посредника подключения.
MyContext(string) ElasticScaleContext(ShardMap, TKey) DbContext(DbConnection, bool) Подключение является функцией сопоставления карты сегментов и ключа маршрутизации на основе данных. Фиксированное имя базы данных или строка подключения не сработает, так как они обходят проверку в сопоставлении сегментов.
MyContext(DbCompiledModel) ElasticScaleContext(ShardMap, TKey, DbCompiledModel) DbContext(DbConnection, DbCompiledModel, bool) Подключение будет создано для заданного сопоставления сегментов и ключа сегментирования по предоставленной модели. Скомпилированная модель передается базовому конструктору.
MyContext(DbConnection, bool) ElasticScaleContext(ShardMap, TKey, bool) DbContext(DbConnection, bool) Подключение должно быть зависимым от карты сегментов и ключа. Оно не может поступать в качестве входного аргумента (если этот аргумент ранее не был получен с помощью сопоставления сегментов и ключа). Передается логическое значение.
MyContext(string, DbCompiledModel) ElasticScaleContext(ShardMap, TKey, DbCompiledModel) DbContext(DbConnection, DbCompiledModel, bool) Подключение должно быть зависимым от карты сегментов и ключа. Оно не может поступать в качестве входного аргумента (если этот аргумент не был получен с помощью сопоставления сегментов и ключа). Передается скомпилированная модель.
MyContext(ObjectContext, bool) ElasticScaleContext(ShardMap, TKey, ObjectContext, bool) DbContext(ObjectContext, bool) Новый конструктор должен обеспечить перенаправление подключений в ObjectContext, переданного в качестве входных данных, на подключение, которым управляет Elastic Scale. Подробное рассмотрение ObjectContext выходит за рамки данного документа.
MyContext(DbConnection, DbCompiledModel, bool) ElasticScaleContext(ShardMap, TKey, DbCompiledModel, bool) DbContext(DbConnection, DbCompiledModel, bool); Подключение должно быть зависимым от карты сегментов и ключа. Подключение не может поступать в виде входного аргумента (если этот аргумент ранее не был получен с помощью сопоставления сегментов и ключа). Модель и логическое значение передаются конструктор базового класса.

Развертывание схемы сегментирования через миграции EF

Автоматическое управление схемой – одно из удобств, предоставляемых платформой Entity Framework. В контексте приложения с использованием инструментов эластичной базы данных желательно сохранить эту возможность автоматической подготовки схемы для вновь созданных сегментов при добавлении баз данных в сегментированное приложение. Основной случай использования – увеличение емкости уровня данных для сегментированных приложений, использующих EF. Использование возможностей EF для управления схемами сокращает усилия администрирования базы данных с сегментированных приложений, созданных на основе EF.

Развертывание схемы с помощью миграций EF лучше всего работает для неоткрытых подключений. Это отличается от сценария с маршрутизацией, зависящей от данных, основой которого выступает открытое подключение, предоставляемое клиентским API эластичной базы данных. Другим отличием является требование к непрерывности. Хотя желательно обеспечить согласованность всех подключений с маршрутизацией на основе данных для защиты от параллельно выполняемых операций с сопоставлениями сегментов, это не является проблемой при начальном развертывании схемы в новой базе данных, которая еще не зарегистрирована в сопоставлении сегментов и не выделена для размещения шардлетов. Таким образом для этих сценариев можно полагаться на обычные подключения к базе данных в противовес маршрутизации, зависящей от данных.

Это приводит к тому, что развертывание схемы с помощью миграций EF тесно связано с регистрацией новой базы данных в виде сегмента в карте сегментов приложения. При этом подразумевается выполнение следующих условий:

  • База данных уже создана.
  • База данных пуста — в ней нет пользовательской схемы и данных.
  • База данных еще недоступна для маршрутизации на основе данных через клиентский API-интерфейс эластичной базы данных.

При выполнении этих условий можно создать обычное закрытое подключение SqlConnection, чтобы запустить перенос EF для развертывания схемы. Этот подход показан в следующем примере кода.

// Enter a new shard - i.e. an empty database - to the shard map, allocate a first tenant to it  
// and kick off EF initialization of the database to deploy schema

public void RegisterNewShard(string server, string database, string connStr, int key)
{

    Shard shard = this.ShardMap.CreateShard(new ShardLocation(server, database));

    SqlConnectionStringBuilder connStrBldr = new SqlConnectionStringBuilder(connStr);
    connStrBldr.DataSource = server;
    connStrBldr.InitialCatalog = database;

    // Go into a DbContext to trigger migrations and schema deployment for the new shard.
    // This requires an un-opened connection.
    using (var db = new ElasticScaleContext<int>(connStrBldr.ConnectionString))
    {
        // Run a query to engage EF migrations
        (from b in db.Blogs
            select b).Count();
    }

    // Register the mapping of the tenant to the shard in the shard map.
    // After this step, data-dependent routing on the shard map can be used

    this.ShardMap.CreatePointMapping(key, shard);
}

В этом примере показан метод RegisterNewShard , который регистрирует сегменты в сопоставлении сегментов, развертывает схему через миграции EF и сохраняет сопоставление ключа сегментирования с сегментом. Он использует конструктор подкласса DbContext (в примере ElasticScaleContext), который принимает в качестве входных данных строку подключения SQL. Код этого конструктора довольно прост, как показывает следующий пример.

// C'tor to deploy schema and migrations to a new shard
protected internal ElasticScaleContext(string connectionString)
    : base(SetInitializerForConnection(connectionString))
{
}

// Only static methods are allowed in calls into base class c'tors
private static string SetInitializerForConnection(string connectionString)
{
    // You want existence checks so that the schema can get deployed
    Database.SetInitializer<ElasticScaleContext<T>>(
new CreateDatabaseIfNotExists<ElasticScaleContext<T>>());

    return connectionString;
}

Можно использовать версию конструктора, унаследованную от базового класса. Однако при подключении код должен обеспечить использование для EF инициализатора по умолчанию. Поэтому присутствует вызов статического метода перед вызовом конструктора базового класса со строкой подключения. Обратите внимание, что регистрация сегментов должна выполняться в другом домене приложения или процесса, чтобы параметры инициализатора EF не конфликтовали.

Ограничения

Подходы, описанные в данном документе, имеют несколько ограничений:

  • Приложения EF, которые используют LocalDb , сначала должны перейти на обычную базу данных SQL Server, прежде чем использовать клиентскую библиотеку эластичной базы данных. Горизонтальное масштабирование приложения через сегментирование с помощью эластичного масштабирования невозможно с LocalDb. Обратите внимание, что во время разработки LocalDbпо-прежнему можно использовать.
  • Все изменения в приложении, которые подразумевают изменение схемы базы данных, должны проходить через миграции EF на всех сегментах. В примере кода для этого документа не показано как делается. Рассмотрите возможность использования операции Update-Database с параметром ConnectionString для перебора всех сегментов. Или извлеките сценарий T-SQL для ожидающейся миграции с помощью Update-Database с параметром -Script и примените этот сценарий к сегментам.
  • Предполагается, что вся обработка в базе данных для выполнения запроса ведется в пределах одного сегмента, который идентифицируется ключом сегментирования, предоставленным в запросе. Однако это предположение не всегда является верным. Например, в случае когда невозможно предоставить ключ сегментирования. Для решения этой проблемы клиентская библиотека предоставляет класс MultiShardQuery , который реализует абстракцию подключения для отправки запросов в несколько сегментов. Описание использования MultiShardQuery вместе с EF выходит за рамки данного документа.

Заключение

С помощью рекомендаций, описанных в этом документе, приложения EF могут использовать возможности клиентской библиотеки эластичной базы данных для маршрутизации, зависящей от данных, если разработчик перепишет конструкторы подклассов DbContext, используемых в приложении EF. Это ограничивает внесение изменений местами применения классов DbContext. Кроме того, приложения EF могут продолжать использовать преимущества автоматического развертывания схемы, объединив действия, вызывающие необходимые миграции EF, с регистрацией новых сегментов и сопоставлений в сопоставлении сегментов.

Дополнительные ресурсы

Еще не используете средства эластичных баз данных? Ознакомьтесь с нашим руководством по началу работы. Возникшие вопросы вы можете задать нам на странице вопросов Microsoft Q&A по Базе данных SQL. Что касается запросов новых функций, вы можете поделиться новыми идеями или проголосовать за существующие на форуме отзывов по Базе данных SQL.