DataContext и [Your]DataContext

35

»» В ДАННОЙ СТАТЬЕ ИСПОЛЬЗУЕТСЯ ИСХОДНЫЙ КОД ДЛЯ ПРИМЕРОВ

Обычно от класса 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>. Вот результат:

Первый прототип конструктора DataContext, подключаемого к файлу базы данных

Ниже демонстрируется тот же базовый код, но на этот раз используется класс [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, используемый большинством прочих примеров.

Вот результат запуска этого примера:

Третий прототип конструктора DataContext, подключаемого к базе данных и использующего файл отображения

Хотя в этом примере используется низкоуровневый класс 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.

Пройди тесты
Лучший чат для C# программистов