При разработке программного обеспечения всегда руководствовался простым правилом, чем меньше в развивающемся проекте используется сторонних компонентов, платформ, технологий, тем лучше. Почти все гениальное - просто. К сожалению, у разработчика не всегда есть возможность свободного выбора средств и систем, с которыми он работает. Вот и мне достался проект Windows Forms + ODAC + Oracle DB Server.
С большой радостью узнал о выходе Oracle Data Access Components (ODAC) for Microsoft Entity Framework and LINQ to Entities (Beta2). Очень ждал этого момента и вот свершилось! Можно не использовать сторонние ORM - все под ключ.
Как научить приложение использовать Entity Framework через установленный на конечных машинах клиент ODAC - описано в пошаговой инструкции от Oracle. Кому интересно, как избавиться от необходимости установки клиента Oracle на машинах пользователей, прошу под кат.
Общие требования
К системе пользователя:
ОС не ниже MS Windows XP SP3
Microsoft .NET Framework 4
Дополнительно ~135 МБ свободного дискового пространства
К системе разработчика:
Visual Studio 2010 Service Pack 1
Oracle Database server 9.2 или выше
ODAC for Microsoft Entity Framework and LINQ to Entities
Подключаемые библиотеки
Для организации доступа к базе данных Oracle необходимо к проекту добавить следующие библиотеки из пакета ODAC.NET: Библиотеку "путь к клиенту"\client_1\odp.net\bin\4\Oracle.DataAccess.dll (~1.4 MB)) подключить как Reference и присвоить свойству "Copy local" значение "true".
Следующие файлы необходимо просто добавить к проекту и присвоить свойству "Copy to Output Directory" значение "Copy if newer":
"путь к клиенту"\client_1\oci.dll (~ 1 MB)
"путь к клиенту"\client_1\orannzsbb11.dll (~1.2 MB)
"путь к клиенту"\client_1\bin\OraOps11w.dll (~ 0.3 MB)
"путь к клиенту"\client_1\oraociei11.dll (~ 130 MB)
Строка подключения должна иметь такой же вид как в файле "tnsnames.ora":
Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=ServerNameOrIP)(PORT=PortNumber)))(CONNECT_DATA=(SERVICE_NAME=DBName)));User Id=UserName;Password=UserPa$$w0rd;
Этого достаточно для обеспечения доступа к БД без установки клиента на машине пользователя:
private bool TestConnect()
{
try
{
var oracleConnection = new OracleConnection
{
ConnectionString =
"Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=ServerNameOrIP)(PORT=PortNumber)))(CONNECT_DATA=(SERVICE_NAME=DBName)));User Id=UserName;Password=UserPa$$w0rd;"
};
oracleConnection.Open();
var oracleCommand = new OracleCommand
{
CommandText = "select sysdate from dual",
Connection = oracleConnection,
Transaction = null
};
var oracleDataAdapter = new OracleDataAdapter {SelectCommand = oracleCommand};
var sysDateDataSet = new DataSet("SomeName");
oracleDataAdapter.Fill(sysDateDataSet, "dateTimeTable");
return sysDateDataSet.Tables[0].Rows.Count > 0;
}
catch (Exception exx)
{
MessageBox.Show(string.Format("Could not connect directly to an Oracle database: \n {0}", exx.Message));
return false;
}
* This source code was highlighted with Source Code Highlighter.
Что необходимо сделать для использования Entity Framework Model без установки клиента Oracle
Создать, либо свою модель ADO.NET Entity Data Model, либо как в примере - "HRModel" с контекстом "HREntities". Для этого необходимо воспользоваться мастером добавления, как показано в руководстве от Oracle, упомянутом выше.
В файл конфигурации проекта (App.Config/Web.Config) будет автоматически (если вы выберете данный соответствующий пункт в мастере) добавлена строка подключения к БД Oracle:
<add name="HREntities" connectionString="metadata=res://*/HRModel.csdl/res://*/HRModel.ssdl/res://*/HRModel.msl;provider=Oracle.DataAccess.Client;provider connection string="Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=ServerNameOrIP)(PORT=PortNumber)))(CONNECT_DATA=(SERVICE_NAME=DBName)));User Id=UserName;Password=UserPa$$w0rd;"" providerName="System.Data.EntityClient"/>
Тут стоит обратить особое внимание на то, что значение, присвоенное атрибуту "provider connection string" обязательно должно быть в двойных кавычках (для их обозначения необходимо использовать код &_q_u_o_t). Это также стоит учесть при динамическом формировании строки подключения.
Затем в файле конфигурации проекта необходимо создать раздел, в котором мы добавляем свой поставщик данных к уже зарегистрированным в системе, список которых находится в файле "%windir%\Microsoft.NET\Framework\v4.0.30319\Config\machine.config". Если на машине установлен ODAC, то провайдер "Oracle.DataAccess.Client" уже будет прописан в файле конфигурации системы и попытка добавить наш поставщик вызовет ошибку при старте приложения: "An error occurred creating the configuration section handler for system.data: Column 'InvariantName' is constrained to be unique. Value 'Oracle.DataAccess.Client' is already present".
Для предотвращения подобной ситуации, при помощи тега
<remove invariant="Oracle.DataAccess.Client" />
провайдер "Oracle.DataAccess.Client" будет удален из списка поставщиков данных, если он присутствует. Затем добавляем наш поставщик. Он будет добавлен к списку провайдеров EntityClient, зарегистрированных в системе:
<system.data>
<DbProviderFactories>
<remove invariant="Oracle.DataAccess.Client" />
<add name="Oracle.DataAccess.Client" invariant="Oracle.DataAccess.Client" description="Oracle Data Provider for .NET" type="Oracle.DataAccess.Client.OracleClientFactory, Oracle.DataAccess, Version=4.112.2.40, Culture=neutral, PublicKeyToken=89b483f429c47342"/>
</DbProviderFactories>
</system.data>
Тонкости
При динамическом подключении модели к БД "правильным" методом от MS, следует обратить внимание на не совсем корректное формирование строки подключения через объект EntityConnectionStringBuilder
string providerName = "Oracle.DataAccess.Client";
string dataSourse = (DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=ServerNameOrIP)(PORT=PortNumber)))(CONNECT_DATA=(SERVICE_NAME=DBName)));User Id=UserName;Password=UserPa$$w0rd;";
var sqlBuilder =
new SqlConnectionStringBuilder
{
DataSource = dataSourse
};
string providerString = sqlBuilder.ToString();
var entityBuilder =
new EntityConnectionStringBuilder
{
Provider = providerName,
ProviderConnectionString = providerString,
Metadata =
@"res://*/HRModel.csdl/res://*/HRModel.ssdl/res://*/HRModel.msl"
};
using (var conn =
new EntityConnection(entityBuilder.ToString()))
{
conn.Open();
Console.WriteLine("Just testing the connection.");
conn.Close();
}
* This source code was highlighted with Source Code Highlighter.
В результате entityBuilder.ToString() вернет строку подключения с лишними одинарными кавычками, в которые заключено полностью значение атрибута provider connection string
metadata=…;provider=Oracle.DataAccess.Client;provider connection string='Data Source=\"(DESCRIPTION=(… ); User Id=UserName;Password=UserPa$$w0rd;\"'
В то время как провайдер Oracle требует строку подключения следующего формата:
metadata=…;provider=Oracle.DataAccess.Client;provider connection string=\"Data Source=(DESCRIPTION(…); User Id=UserName;Password=UserPa$$w0rd;\"
- иначе выдает ошибку.
Пока можно формировать строку подключения некрасиво:
private bool DynamicConnect()
{
const string providerName = "Oracle.DataAccess.Client";
const string serverName = "(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=ServerNameOrIP)(PORT=PortNumber)))(CONNECT_DATA=(SERVICE_NAME=DBName)));User Id=UserName;Password=UserPa$$w0rd;";
const string metadata = "metadata=res://*/HRModel.csdl/res://*/HRModel.ssdl/res://*/HRModel.msl";
var entBild = string.Format("metadata={0};provider={1};provider connection string=\"Data Source={2}\";", metadata, providerName, serverName);
try
{
var conn = new EntityConnection(entBild);
conn.Open();
var hrEntities = new HREntities(conn);
var tmpResult = hrEntities.BRIDGE.Count();
conn.Close();
return true;
}
catch (Exception exx)
{
MessageBox.Show(string.Format("Could not connect directly to an Oracle database: \n {0}", exx.Message));
return false;
}
}
* This source code was highlighted with Source Code Highlighter.
Быть может это отголоски бета версии - посмотрим как будет в релизе, который, как обещает Oracle, состоится в четвертом квартале 2011 года.
В итоге можно записать в актив:
Прямая связка .NET Entity Framework с СУБД Oracle DB Server, без использования сторонних разработок
Избавление от установки клиента к Oracle DB Server на рабочих станциях пользователей
Развертывание .NET приложений, использующих СУБД Oracle, средствами технологии ClickOnce без предоставления прав администратора конечному пользователю (это была основная моя задача).
В пассив можно занести:
Увеличение объема приложения на ~130 МБ - в моем случае не очень критично, так как ClickOnce загружает только измененные или новые файлы.
В ближайшем будущем есть желание сравнить скорость работы через ODAC for Microsoft Entity Framework+Oracle vs System.Data.SqlClient + MS SQL Server.
Спасибо за ваше время.