Часто задаваемые вопросы о LINQ: третья часть

ОГЛАВЛЕНИЕ

•    Скачать исходный код Параллельности - 11 Кб
•    Скачать увязку XML - 12.7 Кб
•    Скачать исходный код CRUD - 2.14 Мб
•    Скачать исходный код компилируемого запроса - 849 Кб

Введение и цель

Это третий выпуск в серии часто задаваемых вопросов о LINQ. В этом выпуске рассмотрены часто задаваемые вопросы о LINQ, касающиеся обработки параллельности, компилируемых запросов, реализации CRUD(создание, чтение, обновление, удаление), и увязки простых классов .NET с конфигурацией файла XML. Статьи в формате часто задаваемых вопросов по существу дела и позволяют узнать больше, прочитав меньше.

Ниже дана ссылка на полноценную электронную книгу часто задаваемых вопросов на 400 страниц, охватывающую различные технологии .NET типа Azure, WCF, WWF, Silverlight, WPF, SharePoint, и многое другое: http://www.questpond.com/SampleDotNetInterviewQuestionBook.zip.

Ссылки на первую и вторую часть
•    LINQ FAQ – первая часть для новичков. Это первая часть серии LINQ FAQ, начинающаяся  с того, чем именно является LINQ, и рассказывающая о разных форматах запроса LINQ вроде «сгруппировать по», «упорядочить по», «поиск с критерием» и т. д.
Обязательна  к прочтению для новичков в технологиях LINQ: LINQNewbie.aspx.
•    LINQ FAQ – вторая часть. Здесь показан базовый пример LINQ для SQL, как определить отношения 1-1 и 1-много с помощью LINQ, как оптимизировать запросы LINQ, выполнение хранимых процедур с помощью LINQ, и, наконец, показан простой пример CRUD с использованием LINQ для SQL: LINQFAQPart2.aspx.
Как обрабатывается параллельность в LINQ?

LINQ дает три способа решения конфликтов параллельности. Чтобы решить конфликты параллельности, надо обернуть код LINQ для SQL в блок try и перехватить ChangeConflictException. Затем надо пройти в цикле по коллекции ChangeConflicts, чтобы указать, как должен быть решен конфликт.

catch (ChangeConflictException ex)
{
    foreach (ObjectChangeConflict objchangeconf in objContext.ChangeConflicts)
    {
        objchangeconf.Resolve(RefreshMode.OverwriteCurrentValues);
    }
}

LINQ представляет следующие три способа решения конфликтов параллельности:
•    KeepCurrentValues: Если задана эта опция, и возникают конфликты параллельности, LINQ вызывает значения объекта-сущности LINQ как есть и не проталкивает новые значения из базы данных в объект LINQ.
•    OverwriteCurrentValues: Если задана эта опция, текущие данные объекта LINQ заменяются на значения базы данных.
•    KeepChanges: Самая странная, но иногда полезная опция. Классы могут иметь много свойств. Изменившиеся свойства оставляются как есть, а неизменившиеся свойства извлекаются из базы данных и заменяются.

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

Какие другие средства предоставляет LINQ для тонкой настройки параллельности на уровне поля?

Система параллельности LINQ позволяет контролировать поведение параллельности на уровне поля. С помощью атрибута UpdateCheck можно задать три опции:
•    Never: Не использовать это поле при проверке конфликтов параллельности.
•    Always: Всегда использовать это поле для проверки конфликтов параллельности.
•    WhenChanged: Только если значение члена изменилось, использовать это поле для обнаружения конфликтов параллельности.

Фрагмент кода ниже показывает, как использовать атрибут UpdateCheck для контроля опций параллельности уровня свойства / поля, как сказано выше.

[Column(DbType = "nvarchar(50)",UpdateCheck=UpdateCheck.Never)]
public string CustomerCode
{
    set
    {
        _CustomerCode = value;
    }
    get
    {
        return _CustomerCode;
    }
}

Какие средства сообщения об ошибках предоставляет LINQ при возникновении конфликтов параллельности?

Система параллельности LINQ позволяет задать способ, как должно сообщаться о конфликтах. Система LINQ имеет два способа сообщения о конфликтах:
•    ContinueOnConflict: Эта опция приказывает механизму LINQ продолжить даже при наличии конфликтов и вернуть все конфликты в конце обработки.
•    FailOnFirstConflict: Эта опция помогает остановиться, как только возникнет первый конфликт, и вернуть все конфликты в исходное время, то есть механизм LINQ не продолжает дальше выполнять код.

Обе эти опции задаются как входные данные в методе SubmitChanges с помощью перечисления ConflictMode. Ниже приведен фрагмент кода, задающий режимы конфликта:

objContext.SubmitChanges(ConflictMode.ContinueOnConflict);

Что такое компилируемые запросы?

LINQ предоставляет нечто под названием «компилируемые запросы LINQ». В компилируемых запросах LINQ схема кэшируется в статическом классе. Статический класс является глобальным кэшем. LINQ использует схему запроса из объекта статического класса, а не строит и готовит схему запроса с нуля.

Рисунок: Кэширование запроса LINQ

Всего надо выполнить четыре шага с момента построения запросов LINQ до их запуска. Компилируемые запросы LINQ сокращают эти четыре шага до двух.

Рисунок: Схема запроса обходит много шагов

Какие разные шаги связаны с написанием компилируемых запросов LINQ?

Первый – импортировать пространство имен Data.Linq.

Import namespace
using System.Data.Linq;

Синтаксис для написания компилируемых запросов немного загадочный. Поэтому надо разбить этот синтаксис на маленькие кусочки и постараться увидеть, как выглядит полный синтаксис. Чтобы выполнить компилируемую функцию, пишется функция для указателя. Эта функция должна быть статической, чтобы механизм LINQ мог использовать схему запроса, хранящуюся в объектах статического класса. Функция определяется следующим образом. Она начинается с public static, указывающего, что эта функция статическая. Затем используется ключевое слово Func для определения входных параметров и выходных параметров. Последовательность параметров определяется так:
•    Первый параметр должен быть контекстом данных. Тип данных определяется как DataContext.
•    За ним идет один или больше входных параметров; сейчас есть только один, т. е. код клиента, поэтому тип данных второго параметра определен как string.
•    Покончив со всеми входными параметрами, надо определить тип данных выходных. Сейчас выходной тип данных определен как IQueryable.

Эта функция делегата называется getCustomers.

public static Func<DataContext, string, IQueryable<clsCustomerEntity>> getCustomers

Надо вызвать метод Compiled статического класса CompiledQuery с объектом DataContext и требуемыми определенными входными параметрами с последующим запросом LINQ. В следующем фрагменте не был указан запрос LINQ для минимизации сложностей:

CompiledQuery.Compile((DataContext db, string strCustCode)=> Your LINQ Query );

Два вышеприведенных фрагмента кода объединяются. Ниже показан полный фрагмент кода:

public static Func<DataContext, string, IQueryable<clsCustomerEntity>> 
  getCustomers= CompiledQuery.Compile((DataContext db, string strCustCode)=> Your LINQ Query );

Надо обернуть эту статическую функцию в статический класс. Вышеопределенная функция обертывается в статический класс clsCompiledQuery.

public static class clsCompiledQuery
{
    public static Func<DataContext, string, IQueryable<clsCustomerEntity>>
    getCustomers = CompiledQuery.Compile((DataContext db, string strCustCode)
    => from objCustomer in db.GetTable<clsCustomerEntity>()
    where objCustomer.CustomerCode == strCustCode
    select objCustomer);
}

Использовать компилируемый запрос просто – вызывается статическая функция. Сейчас эта функция возвращает тип данных IEnumerable. Поэтому надо определить сущность клиента IEnumerable, которая будет действовать через функцию делегата getCustomers. Сущность клиента обходится в цикле с помощью класса clsCustomerEntity.

IQueryable<clsCustomerEntity> objCustomers = 
             clsCompiledQuery.getCustomers(objContext, txtCustomerCode.Text);
foreach (clsCustomerEntity objCustomer in objCustomers)
{
    Response.Write(objCustomer.CustomerName + "<br>");
}

Что такое подтверждения внутри памяти и физические подтверждения LINQ?

Объекты-сущности образуют основу технологий LINQ. Данные, отправленные в базу данных, проходят через объекты LINQ. Операции над базой данных выполняются посредством класса DataContext. Как сказано ранее, сущности образуют основу LINQ, поэтому все данные сначала отправляются этим сущностям и затем направляются в фактическую физическую базу данных. Из-за таких особенностей работы подтверждение базы данных является двушаговым процессом: первый шаг - подтверждение внутри памяти, а затем физическое подтверждение. Для выполнения операций внутри памяти DataContext предоставляет методы DeleteOnSubmit и InsertOnSubmit. При вызове этих методов из класса DataContext они добавляют и обновляют данные в памяти объекта-сущности. Эти методы не меняют и не добавляют новых данных в фактическую базу данных. После завершения операций внутри памяти для отправки всех обновлений в базу данных вызывается метод SubmitChanges(). Он, наконец, помещает данные в физическую базу данных.

Рассматривается таблица клиента (customerid, customercode и customername) и показывается, как делаются операции подтверждения внутри памяти и физического подтверждения.


Простой пример CRUD с использованием LINQ

Шаг 1: Создать сущность класса клиента

На первом шаге создается сущность класса клиента, как показывает фрагмент кода ниже:

[Table(Name = "Customer")]
public class clsCustomerEntity
{
    private int _CustomerId;
    private string _CustomerCode;
    private string _CustomerName;

    [Column(DbType = "nvarchar(50)")]
    public string CustomerCode
    {
        set
        {
            _CustomerCode = value;
        }
        get
        {
            return _CustomerCode;
        }
    }

    [Column(DbType = "nvarchar(50)")]
    public string CustomerName
    {
        set
        {
            _CustomerName = value;
        }
        get
        {
            return _CustomerName;
        }
    }

    [Column(DbType = "int", IsPrimaryKey = true,IsDbGenerated=true)]
    public int CustomerId
    {
        set
        {
            _CustomerId = value;
        }
        get
        {
            return _CustomerId;
        }
    }
}

Шаг 2: Create с помощью LINQ

Создать контекст данных

Сначала создается объект DataContext с использованием строки подключения.

DataContext objContext = new DataContext(strConnectionString);

Установить данные для вставки

Создав подключение с помощью объекта DataContext, надо создать объект сущности клиента и установить данные для свойства объекта.

clsCustomerEntity objCustomerData = new clsCustomerEntity();
objCustomerData.CustomerCode = txtCustomerCode.Text;
objCustomerData.CustomerName = txtCustomerName.Text;

Произвести обновление внутри памяти

Затем производится обновление внутри памяти в объектах-сущностях с помощью метода InsertOnSubmit.

objContext.GetTable<clsCustomerEntity>().InsertOnSubmit(objCustomerData);

Произвести конечное физическое подтверждение

Наконец, производится физическое подтверждение в фактической базе данных. Пока не вызван SubmitChanges(), данные не помещены в базу данных.

objContext.SubmitChanges();

Итоговый код create на LINQ

Ниже приведен итоговый собранный код LINQ:

DataContext objContext = new DataContext(strConnectionString);
clsCustomerEntity objCustomerData = new clsCustomerEntity();
objCustomerData.CustomerCode = txtCustomerCode.Text;
objCustomerData.CustomerName = txtCustomerName.Text;
objContext.GetTable<clsCustomerEntity>().InsertOnSubmit(objCustomerData);
objContext.SubmitChanges();

Шаг 3: Update с помощью LINQ

Рассматривается следующая операция над базой данных, т. е. update.

Создать контекст данных

Как обычно, сначала надо создать объект DataContext с использованием строки подключения, как сказано в шаге create.

DataContext objContext = new DataContext(strConnectionString);     

Выбрать объект LINQ клиента, который надо обновить

Посредством запроса LINQ получить объект LINQ, который надо обновить:

var MyQuery = from objCustomer in objContext.GetTable<clsCustomerEntity>()
where objCustomer.CustomerId == Convert.ToInt16(txtCustomerId.Text)
select objCustomer;

Установить новые значения и обновить данные в физической базе данных

Сделать обновления и вызвать SubmitChanges() для осуществления итогового обновления.

clsCustomerEntity objCustomerData = (clsCustomerEntity)MyQuery.First<clsCustomerEntity>();
objCustomerData.CustomerCode = txtCustomerCode.Text;
objCustomerData.CustomerName = txtCustomerName.Text;
objContext.SubmitChanges();

Итоговый код для LINQ update

Ниже показан итоговый код запроса LINQ update:

DataContext objContext = new DataContext(strConnectionString);
var MyQuery = from objCustomer in objContext.GetTable<clsCustomerEntity>()
where objCustomer.CustomerId == Convert.ToInt16(txtCustomerId.Text)
select objCustomer;
clsCustomerEntity objCustomerData = (clsCustomerEntity)MyQuery.First<clsCustomerEntity>();
objCustomerData.CustomerCode = txtCustomerCode.Text;
objCustomerData.CustomerName = txtCustomerName.Text;
objContext.SubmitChanges();

Шаг 4: Delete с помощью LINQ

Рассматривается следующая операция над базой данных –  delete.

DeleteOnSubmit

Не описываются предыдущие шаги типа создания контекста данных и выбора объекта LINQ, оба они объяснены в предыдущем разделе. Для удаления объекта из памяти вызывается DeleteOnSubmit(), а для удаления из итоговой базы данных - SubmitChanges().

objContext.GetTable<clsCustomerEntity>().DeleteOnSubmit(objCustomerData);
objContext.SubmitChanges();

Шаг 5: Не требующие пояснений select и read LINQ

Теперь последний шаг –  выбор и чтение объекта LINQ по критерию. Фрагмент кода ниже показывает, как запустить запрос LINQ и установить значение объекта в пользовательский интерфейс ASP.NET.

DataContext objContext = new DataContext(strConnectionString);
var MyQuery = from objCustomer in objContext.GetTable<clsCustomerEntity>()
where objCustomer.CustomerId == Convert.ToInt16(txtCustomerId.Text)
select objCustomer;

clsCustomerEntity objCustomerData = (clsCustomerEntity)MyQuery.First<clsCustomerEntity>();
txtCustomerCode.Text = objCustomerData.CustomerCode;
txtCustomerName.Text = objCustomerData.CustomerName;

Как с помощью файлов XML увязать атрибуты LINQ с простыми классами .NET?

LINQ обеспечивает увязку XML на базе атрибутов. Можно определить увязку LINQ с чистым классом .NET в файле XML, как показано ниже для класса clsCustomer. Механизм LINQ читает увязку из файла XML и применяет ее к простым классам .NET.

public class clsCustomer
{
    private int _intCustomerId;
    private string _strCustomerName;
    private string _strCustomerCode;

    public int CustomerId
    {
        set
        {
            _intCustomerId = value;
        }
        get
        {
            return _intCustomerId;
        }
    }
    public string CustomerName
    {
        set
        {
            _strCustomerName = value;
        }
        get
        {
            return _strCustomerName;
        }
    }
    public string CustomerCode
    {
        set
        {
            _strCustomerCode = value;
        }
        get
        {
            return _strCustomerCode;
        }
    }
}

Создается простой файл XML, определяющий увязку с членами класса.

<?xml version="1.0" encoding="utf-8"?>
<Database Name="TstServer" xmlns="http://schemas.microsoft.com/linqtosql/mapping/2007">
<Table Name="dbo.Customer" Member="WebAppMappingXML.clsCustomer">
<Type Name="WebAppMappingXML.clsCustomer">
<Column Name="CustomerId" Member="CustomerId" />
<Column Name="CustomerName" Member="CustomerName" />
<Column Name="CustomerCode" Member="CustomerCode" />
</Type>
</Table>
</Database>

Чтобы привязать увязку XML к простому классу .NET, надо создать объект XMLMappingSource, как показано во фрагменте кода ниже.

XmlMappingSource xms = XmlMappingSource.FromUrl(physicalPath + "Mapping.xml");

Надо передать объект XMLMappingSource классу DataContext, показанному в следующем фрагменте кода.

DataContext objContext = new DataContext(strConn, xms);

Наконец, извлекается таблица, и объекты-сущности обходятся в цикле.

var query = from customer in objContext.GetTable<clsCustomer>()
select customer;

foreach (var item in query)
{
    Response.Write(item.CustomerCode + "<br>");
}

Как с помощью файлов XML увязать хранимые процедуры с классами .NET?

Если в проекте есть хранимые процедуры, можно использовать элемент XML Function для определения имени хранимой процедуры в файле XML. Не меняется код клиента для привязки объектов DataContext и XMLMappingsource.

<?xml version="1.0" encoding="utf-8"?>
<Database Name="TstServer" xmlns="http://schemas.microsoft.com/linqtosql/mapping/2007">
<Table Name="dbo.Customer" Member="WebAppMappingXML.clsCustomer">
<Type Name="WebAppMappingXML.clsCustomer">
<Column Name="CustomerId" Member="CustomerId" />
<Column Name="CustomerName" Member="CustomerName" />
<Column Name="CustomerCode" Member="CustomerCode" />
</Type>
</Table>
<Function Name="dbo.sp_getCustomerCode" Method="getCustomerByCode">
<Parameter Name="CustomerCode" Parameter="" />
<ElementType Name="clsCustomer" />
</Function>
</Database>