DataContext и [Your]DataContext
35LINQ --- LINQ to DataSet и SQL --- DataContext и [Your]DataContext
»» В ДАННОЙ СТАТЬЕ ИСПОЛЬЗУЕТСЯ ИСХОДНЫЙ КОД ДЛЯ ПРИМЕРОВ
Обычно от класса DataContext производится наследование для создания класса [Your]DataContext. Он существует для подключения к базе данных и обработки всего взаимодействия с базой данных. Для создания объекта DataContext или [Your]DataContext будет использоваться один из представленных далее конструкторов.
Конструктор DataContext имеет четыре прототипа, которые описаны ниже:
- Первый прототип конструктора DataContext
-
DataContext(string fileOrServerOrConnection);
Этот прототип конструктора принимает строку соединения ADO.NET и, возможно, он будет использоваться большую часть времени. Этот прототип применяется также в большинстве примеров LINQ to SQL, приведенных ранее.
- Второй прототип конструктора DataContext
-
DataContext(System.Data.IDbConnection connection);
Поскольку System.Data.SqlClient.SqlConnection наследуется от System.Data.Common.DbConnection, который реализует System.Data.IDbConnection, можно создать экземпляр DataContext или [Your]DataContext с только что созданным объектом SqlConnection. Этот прототип конструктора удобен, когда приходится смешивать код LINQ to SQL с существующим кодом ADO.NET.
- Третий прототип конструктора DataContext
-
DataContext(string fileOrServerOrConnection, System.Data.Linq.MappingSource mapping);
Этот прототип конструктора удобен, когда нет класса [Your]DataContext, а вместо него применяется XML-файл отображения. Иногда уже может быть существующий бизнес-класс, к которому нельзя добавить соответствующие атрибуты LINQ to SQL. Возможно, даже исходный код его отсутствует. Файл отображения можно сгенерировать с помощью утилиты SQLMetal либо написать вручную, чтобы он работал с существующим бизнес-классом или любым другим классом того же рода. Для установки соединения предоставляется нормальная строка соединения ADO.NET.
- Четвертый прототип конструктора DataContext
-
DataContext(System.Data.IDbCohnection connection, System.Data.Linq.MappingSource mapping);
Этот прототип позволит создать соединение LINQ to SQL из существующего соединения ADO.NET и предоставить XML-файл отображения. Эта версия прототипа удобна в случаях комбинирования кода LINQ to SQL с существующим кодом ADO.NET при отсутствии сущностных классов, оснащенных атрибутами.
Для примера первого прототипа конструктора DataContext в следующем коде производится подключение к физическому файлу .mdf, используя строку соединений типа ADO.NET:
// Используйте свое подключение
DataContext dc = new DataContext(@"Data Source=MICROSOF-1EA29E\SQLEXPRESS;
Initial Catalog=C:\NORTHWIND.MDF;
Integrated Security=True");
IQueryable<Customer> query = from c in dc.GetTable<Customer>()
where c.Country == "UK"
select c;
foreach (Customer cust in query)
{
Console.WriteLine(cust.CompanyName);
}
Чтобы создать экземпляр объекта DataContext, просто предоставляется путь к файлу .mdf. Поскольку создается объект DataContext, а не [Your]DataContext, для доступа к заказчикам в базе данных должен быть вызван метод GetTable<T>. Вот результат:
Ниже демонстрируется тот же базовый код, но на этот раз используется класс [Your]DataContext, которым в данном случае является Northwind:
// Используйте свое подключение
Northwind db = new Northwind(
@"Data Source=MICROSOF-1EA29E\SQLEXPRESS;
Initial Catalog=C:\NORTHWIND.MDF;
Integrated Security=True");
IQueryable<Customer> query = from c in db.Customers
where c.Country == "UK"
select c;
foreach (Customer cust in query)
{
Console.WriteLine(cust.CompanyName);
}
Обратите внимание, что вместо вызова метода GetTable<T> для доступа к заказчикам в базе данных просто производится ссылка на свойство Customers. Неудивительно, что код выдает те же самые результаты.
Поскольку второй прототип для класса DataContext удобен при комбинировании кода LINQ to SQL с кодом ADO.NET, этому будет посвящен следующий пример.
Первым делом, создается объект SqlConnection, через который вставляется запись в таблицу Customers. Затем с помощью SqlConnection создается экземпляр класса [Your]DataContext. И, наконец, посредством ADO.NET вставленная ранее запись удаляется из таблицы Customers, таблица вновь запрашивается, на этот раз с применением LINQ to SQL, и результаты выводятся на консоль:
System.Data.SqlClient.SqlConnection sqlConn =
new System.Data.SqlClient.SqlConnection(
@"Data Source=MICROSOF-1EA29E\SQLEXPRESS;Initial Catalog=C:\NORTHWIND.MDF;Integrated Security=True");
string cmd = @"insert into Customers values ('LAWN', 'Lawn Wranglers',
'Mr. Abe Henry', 'Owner', '1017 Maple Leaf Way', 'Ft. Worth', 'TX',
'76104', 'UK', '(800) MOW-LAWN', '(800) MOW-LAWO')";
System.Data.SqlClient.SqlCommand sqlComm =
new System.Data.SqlClient.SqlCommand(cmd);
sqlComm.Connection = sqlConn;
try
{
sqlConn.Open();
// Вставить запись
sqlComm.ExecuteNonQuery();
Northwind db = new Northwind(sqlConn);
IQueryable<Customer> query = from cust in db.Customers
where cust.Country == "UK"
select cust;
Console.WriteLine("Заказчики после вставки, но перед удалением:\n");
foreach (Customer c in query)
{
Console.WriteLine("{0}", c.CompanyName);
}
sqlComm.CommandText = "delete from Customers where CustomerID = 'LAWN'";
// Удалить запись
sqlComm.ExecuteNonQuery();
Console.WriteLine("\n\nЗаказчики после удаления записи:\n");
foreach (Customer c in query)
{
Console.WriteLine(c.CompanyName);
}
}
finally
{
// Закрыть соединение
sqlComm.Connection.Close();
}
Обратите внимание, что запрос LINQ определен лишь однажды, но выполняется дважды — за счет двукратного перечисления возвращенной последовательности. Напомню, что из-за отложенного выполнения запроса определение запроса LINQ в действительности не запускает его выполнения. Запрос выполняется только при перечислении результатов. Это доказывает тот факт, что результаты отличаются между двумя перечислениями. Приведенный код также демонстрирует замечательную интеграцию ADO.NET и LINQ to SQL и то, насколько хорошо они работают вместе. Вот результат:
В примере применения третьего прототипа даже не будет использоваться сущностный класс Northwind. Предположим, что он отсутствует. Вместо этого применяется написанный вручную класс Customer и сокращенный файл отображения. По правде говоря, вручную написанный класс Customer — это сгенерированный SQLMetal класс Customer, который был несколько сокращен за счет удаления из него атрибутов LINQ to SQL. Взглянем на вручную написанный класс Customer:
namespace Linqtest
{
public partial class Customer
{
private string _CustomerID;
private string _CompanyName;
private string _ContactName;
private string _ContactTitle;
private string _Address;
private string _City;
private string _Region;
private string _PostalCode;
private string _Country;
private string _Phone;
private string _Fax;
public Customer()
{
}
public string CustomerID
{
get
{
return this._CustomerID;
}
set
{
if ((this._CustomerID != value))
{
this._CustomerID = value;
}
}
}
public string CompanyName
{
get
{
return this._CompanyName;
}
set
{
if ((this._CompanyName != value))
{
this._CompanyName = value;
}
}
}
public string ContactName
{
get
{
return this._ContactName;
}
set
{
if ((this._ContactName != value))
{
this._ContactName = value;
}
}
}
public string ContactTitle
{
get
{
return this._ContactTitle;
}
set
{
if ((this._ContactTitle != value))
{
this._ContactTitle = value;
}
}
}
public string Address
{
get
{
return this._Address;
}
set
{
if ((this._Address != value))
{
this._Address = value;
}
}
}
public string City
{
get
{
return this._City;
}
set
{
if ((this._City != value))
{
this._City = value;
}
}
}
public string Region
{
get
{
return this._Region;
}
set
{
if ((this._Region != value))
{
this._Region = value;
}
}
}
public string PostalCode
{
get
{
return this._PostalCode;
}
set
{
if ((this._PostalCode != value))
{
this._PostalCode = value;
}
}
}
public string Country
{
get
{
return this._Country;
}
set
{
if ((this._Country != value))
{
this._Country = value;
}
}
}
public string Phone
{
get
{
return this._Phone;
}
set
{
if ((this._Phone != value))
{
this._Phone = value;
}
}
}
public string Fax
{
get
{
return this._Fax;
}
set
{
if ((this._Fax != value))
{
this._Fax = value;
}
}
}
}
}
Наверное, это наихудший написанный вручную сущностный класс за все времена :) Здесь не обрабатываются уведомления об изменениях, и удалена значительная часть кода, которая делала этот сущностный класс полноценным.
В коде указано, что этот класс находится в пространстве имен Linqtest. Это важно, потому что понадобится не только указать это в коде примера, чтобы отличать данный класс Customer от того, который имеется в пространстве nwind, но это же пространство имен должно быть задано во внешнем файле отображения.
Однако что важнее всего в этом примере, так это наличие свойства для каждого поля базы данных, отображенного во внешнем файле. Теперь давайте взглянем на этот внешний файл отображения, который будет использоваться для этого примера:
<?xml version="1.0" encoding="utf-8"?>
<Database Name="Northwind" xmlns="http://schemas.microsoft.com/linqtosql/mapping/2007">
<Table Name="dbo.Customers" Member="Customers">
<Type Name="Linqtest.Customer">
<Column Name="CustomerID" Member="CustomerID" Storage="_CustomerID"
DbType="NChar(5) NOT NULL" CanBeNull="false" IsPrimaryKey="true" />
<Column Name="CompanyName" Member="CompanyName" Storage="_CompanyName"
DbType="NVarChar(40) NOT NULL" CanBeNull="false" />
<Column Name="ContactName" Member="ContactName" Storage="_ContactName"
DbType="NVarChar(30)" />
<Column Name="ContactTitle" Member="ContactTitle" Storage="_ContactTitle"
DbType="NVarChar(30)" />
<Column Name="Address" Member="Address" Storage="_Address" DbType="NVarChar(60)" />
<Column Name="City" Member="City" Storage="_City" DbType="NVarChar(15)" />
<Column Name="Region" Member="Region" Storage="_Region" DbType="NVarChar(15)" />
<Column Name="PostalCode" Member="PostalCode" Storage="_PostalCode"
DbType="NVarChar(10)" />
<Column Name="Country" Member="Country" Storage="_Country" DbType="NVarChar(15)" />
<Column Name="Phone" Member="Phone" Storage="_Phone" DbType="NVarChar(24)" />
<Column Name="Fax" Member="Fax" Storage="_Fax" DbType="NVarChar(24)" />
</Type>
</Table>
</Database>
Обратите внимание на указание, что класс Customer, на который выполняются отображения, находится в пространстве имен Linqtest.
Этот код XML помещен в файл по имени abbrNorthwind.xml, а этот файл — в каталог bin\Debug.
В следующем коде, вручную написанный класс Customer используется вместе с внешним файлом отображения для выполнения запроса LINQ to SQL без использования атрибутов:
string mapPath = "abbrNorthwind.xml";
System.Data.Linq.Mapping.XmlMappingSource nwindMap =
System.Data.Linq.Mapping.XmlMappingSource.FromXml(System.IO.File.ReadAllText(mapPath));
DataContext db = new DataContext(
@"Data Source=MICROSOF-1EA29E\SQLEXPRESS;Initial Catalog=C:\NORTHWIND.MDF;
Integrated Security=True", nwindMap);
IQueryable<Linqtest.Customer> query =
from cust in db.GetTable<Linqtest.Customer>()
where cust.Country == "USA"
select cust;
foreach (Linqtest.Customer c in query)
{
Console.WriteLine("{0}", c.CompanyName);
}
Как видите, из файла отображения создается экземпляр объекта XmlMappingSource, который затем передается XmlMappingSource конструктору DataContext. Также обратите внимание, что нельзя просто обратиться к свойству Customers Table<Customer> в объекте DataContext для запроса LINQ to SQL, потому что вместо [Your]DataContext используется базовый класс DataContext, который здесь не существует.
Кроме того, везде, где производится ссылка на класс Customer, также явно указывается пространство Linqtest — просто чтобы иметь уверенность, что случайно не будет применен сгенерированный SQLMetal класс Customer, используемый большинством прочих примеров.
Вот результат запуска этого примера:
Хотя в этом примере используется низкоуровневый класс Customer, которому недостает большей части кода, необходимого полноценному сущностному классу, нужно было продемонстрировать пример использования файла отображения и класса, лишенного атрибутов LINQ to SQL.
Четвертый прототип является комбинацией второго и третьего:
System.Data.SqlClient.SqlConnection sqlConn =
new System.Data.SqlClient.SqlConnection(
@"Data Source=MICROSOF-1EA29E\SQLEXPRESS;Initial Catalog=C:\NORTHWIND.MDF;Integrated Security=True");
string cmd = @"insert into Customers values ('LAWN', 'Lawn Wranglers',
'Mr. Abe Henry', 'Owner', '1017 Maple Leaf Way', 'Ft. Worth', 'TX',
'76104', 'UK', '(800) MOW-LAWN', '(800) MOW-LAWO')";
System.Data.SqlClient.SqlCommand sqlComm =
new System.Data.SqlClient.SqlCommand(cmd);
sqlComm.Connection = sqlConn;
try
{
sqlConn.Open();
// Вставить запись
sqlComm.ExecuteNonQuery();
string mapPath = "abbrNorthwind.xml";
System.Data.Linq.Mapping.XmlMappingSource nwindMap =
System.Data.Linq.Mapping.XmlMappingSource.FromXml(System.IO.File.ReadAllText(mapPath));
Northwind db = new Northwind(sqlConn, nwindMap);
IQueryable<Linqtest.Customer> query =
from cust in db.GetTable<Linqtest.Customer>()
where cust.Country == "UK"
select cust;
Console.WriteLine("Заказчики после вставки, но перед удалением:\n");
foreach (Linqtest.Customer c in query)
{
Console.WriteLine("{0}", c.CompanyName);
}
sqlComm.CommandText = "delete from Customers where CustomerID = 'LAWN'";
// Удалить запись
sqlComm.ExecuteNonQuery();
Console.WriteLine("\n\nЗаказчики после удаления записи:\n");
foreach (Linqtest.Customer c in query)
{
Console.WriteLine("{0}", c.CompanyName);
}
}
finally
{
// Закрыть соединение
sqlComm.Connection.Close();
}
Приведенный код зависит от класса Linqtest.Customer и внешнего файла abbrNorthwind.xml.
Это замечательный пример использования LINQ to SQL для запроса базы данных без оснащенного атрибутами кода сущностного класса и интеграции с кодом ADO.NET. Результат именно такой, какой следовало ожидать, и он аналогичен примеру с использованием второго прототипа конструктора DataContext.