Многие приложения не обращаются к Entity Framework напрямую. Для этого используются различные сервисы, репозитории, команды, запросы и. т. д. При этом вся непосредственная работа с базой данных вынесена в отдельную сборку. Однако, Entity Framework все равно приходится добавлять в запускаемые приложения. Но является ли это обязательным или можно этого как-то избежать?
А зачем это надо?
На первый взгляд лишнее указание в References никому не мешает. Однако:
- Различные абстракции и шаблоны проектирования (например, CQRS) используются для сокрытия подробностей хранения данных от приложения. При этом тут же в web.config/app.config указываются connectionStrings и другие параметры, по сути раскрывающие их.
- Если в проекте более 1 приложения, то для каждого из них приходится задавать (подключать) и поддерживать в актуальном состоянии все требуемые настройки (например, connectionStrings).
- С выходом новой версии Entity Framework придется обновлять, компилировать и публиковать все что от неё зависит. Хотя на самом деле необходимость делать это есть только для *.DataAccess сборки.
Давайте рассмотрим как можно исключить зависимость от Entity Framework у приложения SomeProject.WebUI. Пусть также имеется сборка SomeProject.Infrastructure.DataAccess, которая и предназначена для доступа к данным.
Данное описание подойдет и для создания нового проекта. Просто не потребуется первый шаг.
Шаг 1 – Избавляемся от зависимости в приложении SomeProject.WebUI
Необходимо из существующего веб-приложения удалить Entity Framework:
PM> uninstall-package EntityFramework -ProjectName SomeProject.WebUI
Из файла конфигурации (web.config/app.config) необходимо убрать все связанные c Entity Framework секции. При этом данные connectionStrings можно где-то временно сохранить, т.к. они пригодятся в дальнейшем.
В результате приложение больше не зависит от Entity Framework и не содержит параметры её настройки.
Шаг 2 – Задаем строку соединения с БД в SomeProject.Infrastructure.DataAccess
Добавим в сборку SomeProject.Infrastructure.DataAccess конфигурацию SomeProject.Infrastructure.DataAccess.config, содержащую строку соединения:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<connectionStrings>
<add name="SomeProjectDatabase" providerName="System.Data.SqlClient"
connectionString="…" />
</connectionStrings>
</configuration>
В его свойствах этого файла нужно указать необходимость его копирования при сборке проекта (Copy to Output Directory: Copy always).
Данный подход работает в случаях, если нет необходимости в трансформациях файла конфигурации или они реализуются скриптом на build-сервере. При необходимости изменений для отладки или публикации с компьютера разработчика можно воспользоваться описанным тут советом.
Заданное выше значение строки соединения необходимо передать в класс, который реализует контекст. Вот пример кода для решения этой задачи:
internal class SomeProjectContext : DbContext
{
private const string _cfgFileName = "SomeProject.Infrastructure.DataAcceess.config";
private const string _connectionStringName = "SomeProjectDatabase";
private static readonly string _connectionString;
static SomeProjectContext()
{
// Получаем полное имя файла конфигурации
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
var uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
var fullCfgFileName = string.Format("{0}/{1}",
Path.GetDirectoryName(path),
SomeProjectContext._cfgFileName);
// Загружаем файл конфигурации
var configFileMap = new ExeConfigurationFileMap { ExeConfigFilename = fullCfgFileName };
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(
configFileMap, ConfigurationUserLevel.None);
// Получаем строку соединения
var connectionStrings = config.ConnectionStrings.ConnectionStrings;
for (int i = 0; i < connectionStrings.Count; ++i) {
if (string.Equals(connectionStrings[i].Name, SomeProjectContext._connectionStringName, StringComparison.OrdinalIgnoreCase)) {
SomeProjectContext._connectionString = connectionStrings[i].ConnectionString;
break;
}
}
if (string.IsNullOrEmpty(SomeProjectContext._connectionString))
throw new ConfigurationErrorsException("Connection string was not found.");
}
public SomeProjectContext()
: base(SomeProjectContext._connectionString)
{
}
...
}
Шаг 3 – Настройка Entity Framework (при необходимости)
Дополнительно можно настроить стратегию исполнения запросов (execution strategy) или фабрику соединений (connection factory). Раньше для этого использовался web.config/app.config. Теперь тоже самое сделаем в коде приложения. Необходимо создать наследника DbConfiguration и задать в нем требуемые параметры настройки. Например, для поддержки SQL Azure это может выглядеть так:
internal class SomeProjectDatabaseConfiguration : DbConfiguration
{
public SomeProjectDatabaseConfiguration()
{
this.SetDefaultConnectionFactory(new SqlConnectionFactory());
this.SetProviderServices(SqlProviderServices.ProviderInvariantName, SqlProviderServices.Instance);
this.SetExecutionStrategy(SqlProviderServices.ProviderInvariantName,
() => new SqlAzureExecutionStrategy());
}
}
Созданная конфигурация связываться с контекстом при помощи атрибута:
[DbConfigurationType(typeof(SomeProjectDatabaseConfiguration))]
internal class SomeProjectContext : DbContext { … }
Шаг 4 – Исправляем развертывание проекта на сервере
В текущем состоянии проект может быть запущен в отладке. Однако при развертывании на сервере он выдаст ошибку. Если начать искать причину, то окажется что на сервер не была скопирована сборка EntityFramework.SqlServer.dll. Чтобы исправить ситуацию, необходимо обратиться к ней в любом месте кода. Например вот так:
bool instanceExists = System.Data.Entity.SqlServer.SqlProviderServices.Instance != null;
Данная строка, конечно, не имеет смысловой нагрузки. Однако это заставит включить требуемую dll в список для копирования на сервер.
Теперь проект избавлен от ненужной зависимости и полностью работоспособен.