Продолжается подписка на наши издания! Вы не забыли подписаться?

Майский СТР – LINQ приобретает форму

Автор: Роджер Дженнингс (Roger Jennings)
Опубликовано: 03.07.2006

Language Integrated Query (LINQ) May 2006 Community Technical Preview (CTP), выпущенный Microsoft 10 мая 2006 года, вносит множество новых и улучшенных возможностей DLinq и XLinq в январскую версию CTP. Новый CTP обновляет C#-компоненты 2005 PDC LINQ Technical Preview, чтобы обеспечить поддержку возможностей DLinq и XLinq в LINQ-проектах на C#. Эти дополнения являются первыми шагами по превращению LINQ в продукт, который будет включен в следующую версию Visual Studio, сейчас имеющую кодовое название "Orcas". Я начну с краткого обзора новых или улучшенных возможностей VB 9.0 и C# 3.0, а затем углублюсь в последние новшества DLinq, и приведу примеры кода.

По непредвиденному стечению обстоятельств Visual Basic кажется, становится самым распространенным языком среди LINQ- и особенно XLinq-разработчиков. C-Omega (C?), предшественник LINQ – производная от C#, а DLinq в версии PDC 2005 работал только в C#-проектах. Однако Erik Meijer – главный двигатель разработки C? – в сентябре 2005 года заявил, что выбирает Visual Basic. Причины его перехода в том, что VB «допускает статическую типизацию там, где это возможно, и динамическую там, где это нужно, в форме ослабленных делегатов, улучшенную поддержку nullable, динамические идентификаторы ... и последнее по очереди, но не по значению – динамические интерфейсы». Эти возможности позволили VB-команде Поля Вика (Paul Vick) обогнать своих C#-коллег в разработке зависимых от предметной области возможностей LINQ, и особенно XLinq.

В майском CTP VB-разработчики получили следующие добавления к январскому CTP:

For VB developers, the May 2006 CTP adds these new features to the January 2006 CTP:

Подробнее о новых возможностях DLinq в VB 9.0 можно прочитать в главе 7 файла DLinq Overview for VB Developers.doc, "New For Spring 2006".

LINQ May 2006 CTP вносит следующие изменения в возможности январского VB CTP:

Для установки VB 9.0 необходимо наличие на компьютере Visual Basic 2005 Express или выше, и если вы хотите запускать примеры DLinq, SQL Server 2000a или SQL Server 2005 Express и выше. DLinq остается привязанным к SQL Server, но Microsoft обещает предоставить помощь другим разработчикам РСУБД, желающим писать DLinq-провайдеры данных. Образца провайдера в CTP нет.

C# 3.0 догоняет VB 9.0

Январская CTP-версия LINQ не включала ничего, связанного с C#. Вот новые возможности C# 3.0, появившиеся в майской версии:

Эти новые возможности ставят C# 3.0 примерно вровень с реализацией LINQ в VB 9.0. Как обычно, VB предлагает легкость в использовании LINQ, DLinq и XLinq, а C# – более изощренные расширения языка.

Для установки обновления C# 3.0 необходимо наличие на компьютере Visual C# 2005 Express или выше, и, если вы хотите запускать примеры DLinq, SQL Server 2000a или SQL Server 2005 Express и выше. В отличие от VB-версии, C# Readme говорит, что для поддержки DLinq Designer нужна минимум VS 2005 Standard Edition.

Установка LINQ May 2006 CTP на машине, где уже есть VS 2005 Standard Edition или выше, проста. Инсталлятор определяет, установлены ли VB и January 2006 CTP, и если да, удаляет CTP. Если в VS 2005 Setup указан C#, инсталлятор удаляет PDC 2005 Technical Preview, если он есть. Затем выдается диалог "Update C# Language Service for LINQ", предлагающий выполнить обновление. Выберите опцию Update C# Language Service и нажмите Next, чтобы завершить установку.

Обновление VB IDE производится автоматически, но для C# поддержку May 2006 CTP нужно включить вручную (на самом деле автор ошибается, т.к. наши эксперименты показали, что в новой версии поддержка IDE для LINQ включается автоматически при инсталляции – прим.ред.). Если не подключить C# IDE, IntelliSense не будет поддерживать LINQ, будут появляться ложные ошибки при сборке, а редактор будет подчеркивать LINQ-код красным. Запустите скрипт C:\Program Files\LINQ Preview\Bin\Install C# IDE Support.vbs, чтобы настроить редактор. Скрипт Uninstall C# IDE Support.vbs возвращает редактор к исходному состоянию.

Привязка DataGridViews к источникам данных DLinq

Эмуляция возможностей связывания данных и управления параллелизмом типизированных DataSet-ов ADO.NET 2.0 существенна для успеха DLinq в VS "Orcas", особенно среди VB-разработчиков. Документ DLinq Designer.doc показывает, как создать форму Customers-Orders-Order Details из control-ов DataGridView, привязанных к источнику данных DLinq. Назначение DLinq Designer – упростить создание DataContext-ов и источников данных DLinq.

Как ни странно, существующие DLinq Data Sources не решают проблем связывания данных в DataGridView, которые я рассматривал в своей недавней статье «Test Drive VB9 and DLinq» и записи в блоге "Anomalies and Issues with VB 9.0 DLinq Code". Недочеты в документации и две ошибки в автогенерируемом VB-коде усложняют процесс эмуляции с помощью DLinq возможностей control-ов DataGridView, связанных с типизированными DataSet-ами.

Вот простейшие шаги для проверки возможностей связывания данных и OCCR-поведения:

Создайте трехуровневый иерархический DLinq Data Source для БД Northwind.

Создайте форму Customers-Orders-Order Details, добавив связанные control-ы DataGridView для каждой сущности источника данных.

Используйте связанные control-ы DataGridView для выполнения типичных операций создания, получения изменения и удаления данных. Добавьте код для обхода недостающей функциональности DataGridView.

Запустите два экземпляра проекта, эмулируя конфликт параллелизма, и оцените работу OCCR.

В файле DLinq Designer.doc содержатся пошаговые инструкции, которые должны продемонстрировать возможности DLinq за пределами простого связывания данных. Эти, приведенные ниже, действия упрощают процесс разработки форм; они также иллюстрируют ошибки в документации и в автогенерированном коде, о которых я упоминал выше. Выполните следующие действия, чтобы создать иерархический источник данных DLinq с тремя уровнями:

Создайте новое LINQ-приложение TestDataBinding.

Откройте Server Explorer, создайте подключение к БД Northwind с Windows-аутентификацией и разверните узел Tables.

Выберите Project, Show All Files, затем разверните узел References и удостоверьтесь, что существует ссылка на пространство имен System.Query (см. рисунок 1).


Рисунок 1. Добавьте в DLinq-проект ссылку на System.Query.

Откройте Solution Explorer, щелкните правой кнопкой по TestDataBinding, выберите Add New Item -> DLinq Objects, переименуйте DLinqObjects1.dlinq в Northwind.dlinq и нажмите Add, чтобы добавить к проекту DLinq Designer.

Перетащите пиктограмму таблицы Customers из Server Explorer в Northwind.dlinq, чтобы создать объект Customer DLinqClass, и нажмите F5, чтобы собрать и запустить проект (собирать проект придется после добавления каждого объекта DLinqClass). Если при сборке появится сообщение об ошибке, см. рисунок 1.

Создайте DLinq Data Source из класса DLinq Customer, выбрав Data, Add New Data Source, чтобы запустить Data Source Configuration Wizard. Выберите Object, нажмите Next, разверните два узла TestDataBinding и выберите объект Customer (см. рисунок 2). Нажмите Next и Finish чтобы создать Customer DLinq Data Source.

Откройте окно Data Sources и нажмите Refresh, чтобы отобразить Customer DLinq Data Source и его свойства (поля). Нажмите F5, чтобы собрать и запустить проект.

Откройте Server Explorer и перетащите в Northwind.dlinq пиктограмму таблицы Orders, чтобы создать Order DLinqClass и Association (линию отношения) с Customer DLinqClass. Нажмите F5, чтобы собрать и запустить проект.

Откройте окно Data Sources и нажмите Refresh, чтобы отобразить Orders DLinq Data Source и его свойства (поля). Нажмите F5, чтобы собрать и запустить проект.


Рисунок 2. Сгенерируйте DLinq Data Source по объекту DLinqClass.

Откройте Server Explorer и перетащите в Northwind.dlinq пиктограмму таблицы Order Details, чтобы создать Order_Details DLinqClass и Association (линию отношения) с Order DLinqClass. Нажмите F5, чтобы собрать и запустить проект.

Теперь у вас есть DLinq-источники данных для Customers, Orders и OrderDetails (см. рисунок 3).


Рисунок 3. Создание объектов DLinqClass для связанных таблиц.

Теперь добавим и свяжем три DataGridView с DLinq-источниками данных Customer, Orders и Order_Details, и обойдем ошибку в документации DLinq Designer следующим образом:

Откройте окно Form1.vb (Design) и перетащите Customer из окна Data Sources на форму, чтобы добавить CustomerBindingSource, CustomerBindingNavigator и CustomerDataGridView (почему Customer один, а остальных источников – несколько, загадка DLinq). Задайте размер DataGridView со случайно упорядоченными колонками в 600 на 130 пикселей.

Повторите шаг 1 для источников данных Orders и Order_Details, но задайте ширину DataGridView для OrderDetails примерно в 500 пикселей. Нажмите F5, чтобы сохранить изменения.

Если хотите, откройте список свойств для каждого DataGridView, нажмите кнопку Columns (Collection), чтобы открыть диалог Edit Columns, и настройте порядок колонок, чтобы переопределить порядок колонок. Удалите колонку Customer из OrdersDataGridView и колонку Order_ из OrderDetailsDataGridView (см. рисунок 4). Нажмите F5, чтобы сохранить изменения.


Рисунок 4. Добавление BindingSource и DataGridView.

Добавьте директиву Imports System.Data.Dlinq.DataQueryExtensions и объявление Private dcNwind As New NorthwindDataContext в начало файла класса Form1.

Добавьте обработчик событий Form1_Load, а затем добавьте в обработчик события инструкцию CustomerBindingSource.DataSource = dcNwind.Customers. ToBindingList().BindingLists из DLinq Data Sources поддерживает только оповещение об изменениях; возможности сортировки и фильтрации отсутствуют.

Нажмите F5, чтобы собрать и запустить проект. Вы получите мистическое сообщение об необработанном исключении "The type 'Order_Detail' has no identity key". Исключение возникает потому, что исходная таблица сущности Order_Detail содержит композитный первичный ключ; в случае типов Customer и Order_ такой проблемы не существует. Нажмите Shift+F5.

Чтобы указать OrderID в качестве уникального ключа, откройте Northwind.dlinq, выберите соответствующее поле Order_Details и откройте список его свойств. Измените значение свойства IsID на True (см. рисунок 5). Соберите и запустите проект.

Заметьте, что строки Order_Detail содержат дубликаты значений. Например, по умолчанию заказ ALFKI 10643 содержит три строки с ProductID 28 (см. рисунок 6). Очевидно, такой результат некорректен, так как OrderID и ProductID – поля составного первичного ключа таблицы Order Details.

В пункте 7 в разделе «Creating Objects from the Toolbox» файла DLinq Designer.doc на стр.9 сказано: «Установите свойство IsID в True (только для колонки OrderID)", что неверно. Нужно также установить в True значение свойства ProductID и пересобрать проект. После этого строки в Order_DetailsDataGridView будут показаны верно.


Рисунок 5. Установите свойство IsID хотя бы одной колонки в True.


Рисунок 6. Укажите все поля составного первичного ключа как "Identity Keys".

Поиск дополнительной информации по IsID выдает только следующий перл насчет атрибута IsIdentity элемента Column (на стр. 49 файла DLinq Overview for VB Developers.doc): «описывает, является ли колонка частью первичного ключа. Эта информация является излишней при наличии элемента PrimaryKey и может быть удалена». К счастью, команда LINQ ее не удалила. Не похоже, чтобы текущая реализация DLinq умела автоматически определять поля составного первичного ключа исходных таблиц.

Проверка возможностей связывания данных DLinq

Настройте форму на сохранение изменений в исходных таблицах, выбрав кнопку CustomerBindingNavigatorSaveItem и задав ее свойству Enabled значение True. Двойным щелчком по кнопке сгенерируйте обработчик события CustomerBindingNavigatorSaveItem_Click и впишите в него следующее:

Me.Validate()
dcNwind.SubmitChanges()

Метод Validate гарантирует, что все изменения корректны, а вызов SubmitChanges отправляет изменения в таблицы БД.

Добавление следующих двух инструкций после dcNwind.SubmitChanges() позволит проверить сохранение обновления без переоткрытия формы:

dcNwind = New NorthwindDataContext
CustomerBindingSource.DataSource = _
    dcNwind.Customers.ToBindingList()

BindingSources из DataTables поддерживают сортировку, фильтрацию. поиск и оповещение об изменениях. Как уже говорилось, BindingSources из DLinq Data Sources поддерживает только оповещение об изменениях, поэтому использовать сортировку в связанных DataGridViews не получится.

Процесс редактирования существующих ячеек DataGridView тестовой формы идентичен аналогичному для DataSet, например, при добавлении новой строки в Customers. Однако добавление новых строк Orders или Order Details существенно отличается. Добавление новой строки в OrdersDataGridView, связанный с DataTable, автоматически добавляет увеличенные значения внешнего ключа OrderID и CustomerID. DLinq Data Source добавляет 0 (значение по умолчанию OrderID) и не вставляет значения CustomerID. Аналогично, добавление новой строки Order_Details в DataTable добавляет увеличенное значение OrderID; DLinq Data Source вставляет в Order_DetailsDataGridView значение 0.

Наиболее серьезный недостаток редактирования в DLinq Data Source – невозможность удалять строки из нижележащих таблиц БД, удаляя их в DataGridView. В January 2006 CTP реализовано каскадное удаление, с сохранением результатов в БД. Например, удаление строки в Customers при вызове SubmitChanges удалит из БД и ее, и все связанные с ней строки Orders и Order Details; аналогично, удаление строки из Orders удалит и все связанные с ней строки в Order Details.

Однако ручное удаление строки в Order_DetailsDataGridView не сохранит изменений в таблице Order_Details. Майская версия CTP не сохраняет никаких удалений, выполняемых в DataGridView. Однако, если попытаться удалить строку из Customers – неважно, с удалением связанных строк из Orders и Order Details или без оного – вы получите сообщение об ошибке, говорящее о нарушении ограничения FK_Orders_Customers. При удалении строки из Orders и связанных с ней строк из Order_Details сообщения о нарушении ограничения FK_Order_Details_Orders не возникает. Похоже, что этот дефект связан с наличием у таблицы Order Details составного первичного ключа.

Чтобы обойти невозможность удалить вручную строки с помощью DataGridView, приходится писать немало кода. Существенные изменения этого кода потребовались из-за изменений в May 2006 CTP. Однако редактор автоматически изменяет в VB LINQ-запросах Select ... From на From ... Select.

Удаление связанных дочерних объектов с кодом требует удаления соответствующего элемента ассоциации, что порождает исключение NullReferenceException. Исключение возникает из-за того, что в автогенерированном коде свойства Customer сущности Order_ и свойства Order_ сущности Order_Detail есть очевидная ошибка. В обоих случаях код перед применением метода Remove устанавливает удаляемую сущность в Nothing. Исправление заключается в изменении порядка инструкций, как показано ниже (ошибочная строка выделена):

If (Not (Me._Customer.Entity) Is Nothing) Then
  'Fix for deletions
  'Me._Customer.Entity = Nothing (moved below)
  Me._Customer.Entity.Orders.Remove(Me)
  Me._Customer.Entity = Nothing
End If

Это серьезная проблема, так как исправления будут, скорее всего, потеряны при повторном генерировании кода.

OCCR (Optimistic Concurrency Conflict Resolution, оптимистическое разрешение конфликтов параллелизма) напоминает обработку конфликтов параллельного доступа для исключений обновления ADO.NET DataAdapter с помощью класса DbConcurrencyException. OCCR применяется путем добавления перечислений ConflictMode.ContinueOnConflict или ConflictMode.FailOnFirstConflict к новой перегрузке метода SubmitChanges. Если выбрать ContinueOnConflict, можно создать код обработки ошибки, чтобы возвратить список всех конфликтов параллелизма для единичного вызова SubmitChanges (см. листинг 1 и рисунок 7). Можно попробовать повторить обновление, применяя метод Resolve с соответствующим членом перечисления RefreshMode, например, KeepChanges.

Листинг 1.

Класс OptimisticConcurrencyException предоставляет сложный механизм обнаружения и разрешения конфликтов параллельного доступа. Это код выводит имя таблицы плюс текущее, исходное и хранящееся в БД значения каждого из полей, вызвавших конфликт параллельного доступа. Можно попробовать повторить обновление, применяя метод Resolve с соответствующим членом перечисления RefreshMode – KeepChanges, KeepCurrentValues, or OverwriteCurrentValues.

Private Sub SubmitChanges()
  ‘ Метод SubmitChanges с перехватом ошибок и отчетом для OCCR
  'SubmitChanges method with error trapping and verbose reporting for OCCR
  Try
    dcNwind.SubmitChanges(ConflictMode.ContinueOnConflict)
  Catch exOC As OptimisticConcurrencyException
    Dim strMsg As String = Nothing
    For Each objCC As OptimisticConcurrencyConflict _
        In exOC.Conflicts
      strMsg += "An optimistic concurrency conflict occurred in table '" _
        + objCC.Table.Name + "'." + vbCrLf
      For Each objMC In objCC.GetMemberConflicts
        strMsg += "Member: '" + objMC.MemberInfo.Name _
          + "'" + vbCrLf
        strMsg += "Current value: " + objMC.CurrentValue.ToString _
          + "'" + vbCrLf
        strMsg += "Original value: " + objMC.OriginalValue.ToString _
          + "'" + vbCrLf
        strMsg += "Database value: " + _
          objMC.DatabaseValue.ToString + "'" + vbCrLf + vbCrLf
      Next
      'objCC.Resolve(RefreshMode.OverwriteCurrentValues)
    Next
    MsgBox(strMsg, MsgBoxStyle.Critical, _
      "Optimistic Concurrency Excepiton on SubmitChanges")
  Catch exSys As Exception
    MsgBox(exSys.Message, MsgBoxStyle.Critical, _
      "System Exception on SubmitChanges")
  End Try
End Sub


Рисунок 7. Конфликты параллельного доступа отражаются в сообщениях об ошибках.

Несмотря на проблемы со значениями IsID, удалением строк из БД и NullReferenceExceptions при программном удалении строк, LINQ и его реализации, DLinq и XLinq, постепенно развиваются в нечто большее, чем средство объектно-реляционного отображения и замена ObjectSpaces.


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

Copyright © 1994-2016 ООО "К-Пресс"