Поделиться через


LINQ to SQL: запросы LINQ .NET для реляционных данных

 

Динеш Кулкарни (Dinesh Kulkarni), Лука Болоньезе (Luca Bolognese), Мэтт Уоррен (Matt Warren), Андерс Хейлсберг (Anders Hejlsberg), Кит Джордж (Kit George)

Март 2007 г.

Область применения:
   имя Visual Studio Code "Косатки"
   .NET Framework 3.5

Сводка. LINQ to SQL предоставляет инфраструктуру среды выполнения для управления реляционными данными как объектами без потери возможности запроса. Приложение может управлять объектами, пока LINQ to SQL автоматически отслеживает изменения в фоновом режиме. (119 печатных страниц)

Содержимое

Введение
Краткий обзор
   Создание классов сущностей
   The DataContext
   Определение отношений
   Выполнение запросов в связях
   Изменение и сохранение сущностей
In-Depth запросов
   Выполнение запроса
   Идентификация объектов
   Отношения
   Объединения
   Проекции
   Компилированные запросы
   Преобразование SQL
Жизненный цикл сущности
   Отслеживание изменений
   Отправка изменений
   Одновременные изменения
   Transactions
   Хранимые процедуры
Классы сущностей In-Depth
   Использование атрибутов
   Согласованность графов
   Уведомления об изменениях
   Наследование
Дополнительные разделы
   Создание баз данных
   Взаимодействие с ADO.NET
   Изменение разрешения конфликтов
   Вызов хранимых процедур
   Средство генератора классов сущностей
   Справочник по DBML средства генератора
   Многоуровневые сущности
   Внешнее сопоставление
   Примечания о поддержке функций NET Framework
   Поддержка отладки

Введение

Большинство программ, написанных сегодня, так или иначе управляют данными, и часто эти данные хранятся в реляционной базе данных. Тем не менее, существует огромный разрыв между современными языками программирования и базами данных в том, как они представляют информацию и управляют ими. Это несоответствие импеды можно увидеть несколькими способами. Наиболее заметным является то, что языки программирования получают доступ к информации в базах данных через API, требующие указания запросов в виде текстовых строк. Эти запросы являются значительными частями логики программы. Тем не менее, они непрозрачны для языка и не могут воспользоваться функциями проверки во время компиляции и разработки, такими как IntelliSense.

Конечно, различия идут гораздо глубже, чем это. Способ представления информации — модель данных — сильно отличается между ними. Современные языки программирования определяют информацию в виде объектов. Реляционные базы данных используют строки. Объекты имеют уникальное удостоверение, так как каждый экземпляр физически отличается от другого. Строки идентифицируются по значениям первичного ключа. Объекты имеют ссылки, которые идентифицируют экземпляры и связывают их друг с другом. Строки оставляются намеренно, чтобы связанные строки были связаны друг с другом слабо с помощью внешних ключей. Объекты стоят отдельно, существуют до тех пор, пока на них по-прежнему ссылается другой объект. Строки существуют как элементы таблиц и исчезают сразу после их удаления.

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

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

LINQ to SQL, компонент Visual Studio Code Name "Orcas", предоставляет инфраструктуру времени выполнения для управления реляционными данными как объектами без потери возможности запроса. Это делается путем перевода интегрированных с языком запросов в SQL для выполнения базой данных, а затем преобразования табличных результатов обратно в определяемые вами объекты. Приложение сможет управлять объектами, пока LINQ to SQL автоматически отслеживает изменения в фоновом режиме.

  • LINQ to SQL не ввязываться в приложение.
    • Текущие ADO.NET решения можно перенести в LINQ to SQL по частям (совместное использование одних и тех же подключений и транзакций), так как LINQ to SQL является просто еще одним компонентом семейства ADO.NET. LINQ to SQL также имеет обширную поддержку хранимых процедур, что позволяет повторно использовать существующие корпоративные ресурсы.
  • LINQ to SQL приложения легко приступить к работе.
    • Объекты, связанные с реляционными данными, можно определить так же, как и обычные объекты, и только декорировать атрибутами, чтобы определить, как свойства соответствуют столбцам. Конечно, это даже не обязательно делать вручную. Средство времени разработки предоставляется для автоматизации преобразования существующих схем реляционных баз данных в определения объектов.

Вместе LINQ to SQL инфраструктура среды выполнения и средства времени разработки значительно снижают рабочую нагрузку для разработчика приложений базы данных. В следующих главах представлен обзор использования LINQ to SQL для выполнения общих задач, связанных с базой данных. Предполагается, что читатель знаком с Language-Integrated Query и стандартными операторами запроса.

LINQ to SQL не зависит от языка. Любой язык, созданный для предоставления Language-Integrated query, может использовать его для доступа к информации, хранящейся в реляционных базах данных. Примеры в этом документе показаны в C# и Visual Basic. LINQ to SQL также можно использовать с версией компилятора Visual Basic с поддержкой LINQ.

Краткий обзор

Первым шагом в создании приложения LINQ to SQL является объявление классов объектов, которые будут использоваться для представления данных приложения. Рассмотрим следующий пример.

Создание классов сущностей

Начнем с простого класса Customer и свяжите его с таблицей Customers в образце базы данных Northwind. Для этого необходимо применить только настраиваемый атрибут к верхней части объявления класса. LINQ to SQL определяет атрибут Table для этой цели.

C#

[Table(Name="Customers")]
public class Customer
{
   public string CustomerID;
   public string City;
}

Visual Basic

<Table(Name:="Customers")> _
Public Class Customer
   Public CustomerID As String
   Public City As String
End Class

Атрибут Table имеет свойство Name , которое можно использовать для указания точного имени таблицы базы данных. Если свойство Name не указано, LINQ to SQL предполагает, что таблица базы данных имеет то же имя, что и класс . В базе данных будут храниться только экземпляры классов, объявленных как таблицы. Экземпляры этих типов классов называются сущностями. Сами классы называются классами сущностей.

Помимо связывания классов с таблицами необходимо обозначать каждое поле или свойство, которые планируется связать со столбцом базы данных. Для этого LINQ to SQL определяет атрибут Column.

C#

[Table(Name="Customers")]
public class Customer
{
   [Column(IsPrimaryKey=true)]
   public string CustomerID;
   [Column]
   public string City;
}

Visual Basic

<Table(Name:="Customers")> _
Public Class Customer
   <Column(IsPrimaryKey:=true)> _
   Public CustomerID As String

   <Column> _
   Public City As String

End Class

Атрибут Column имеет различные свойства, которые можно использовать для настройки точного сопоставления полей и столбцов базы данных. Одним из свойств заметки является свойство Id . Он сообщает LINQ to SQL, что столбец базы данных является частью первичного ключа в таблице.

Как и в случае с атрибутом Table , необходимо указать сведения в атрибуте Column , только если они отличаются от того, что может быть выведено из объявления поля или свойства. В этом примере необходимо сообщить LINQ to SQL, что поле CustomerID является частью первичного ключа в таблице, но не нужно указывать точное имя или тип.

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

The DataContext

DataContext — это канал main, с помощью которого вы извлекаете объекты из базы данных и повторно отправьте изменения. Вы используете его так же, как и подключение ADO.NET. На самом деле DataContext инициализируется с помощью заданного подключения или строки подключения. Цель DataContext — преобразовать запросы для объектов в SQL-запросы, выполненные к базе данных, а затем собрать объекты из результатов. DataContext позволяет выполнять интегрированные с языком запросы, реализуя тот же шаблон оператора, что и стандартные операторы запроса, такие как Where и Select.

Например, dataContext можно использовать для получения объектов клиентов, город которых — Лондон, следующим образом:

C#

// DataContext takes a connection string 
DataContext db = new   DataContext("c:\\northwind\\northwnd.mdf");
// Get a typed table to run queries
Table<Customer> Customers = db.GetTable<Customer>();
// Query for customers from London
var q =
   from c in Customers
   where c.City == "London"
   select c;
foreach (var cust in q)
   Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City);

Visual Basic

' DataContext takes a connection string 
Dim db As DataContext  = New DataContext("c:\northwind\northwnd.mdf")
' Get a typed table to run queries
Dim Customers As Customers(Of Customer) = db.GetTable(Of Customer)()
' Query for customers from London
Dim londonCustomers = From customer in Customers _
                      Where customer.City = "London" _
                      Select customer
For Each cust in londonCustomers
   Console.WriteLine("id = " & cust.CustomerID & ", City = " & cust.City)
Next

Каждая таблица базы данных представлена в виде коллекции Table , доступной с помощью метода GetTable() с помощью класса сущностей для ее идентификации. Рекомендуется объявить строго типизированный dataContext вместо того, чтобы полагаться на базовый класс DataContext и метод GetTable(). Строго типизированный объект DataContext объявляет все коллекции Таблиц как члены контекста.

C#

public partial class Northwind : DataContext
{
   public Table<Customer> Customers;
   public Table<Order> Orders;
   public Northwind(string connection): base(connection) {}
}

Visual Basic

Partial Public Class Northwind 
              Inherits DataContext

   Public Customers As Table(Of Customers)
   Public Orders As Table(Of Orders)
         Public Sub New(ByVal connection As String)
            MyBase.New(connection)
   End Sub
End Class

Затем запрос для клиентов из Лондона можно выразить более просто:

C#

Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");
var q =
   from c in db.Customers
   where c.City == "London"
   select c;
foreach (var cust in q)
   Console.WriteLine("id = {0}, City = {1}",cust.CustomerID, cust.City);

Visual Basic

Dim db = New Northwind("c:\northwind\northwnd.mdf")
Dim londonCustomers = From cust In db.Customers _
                      Where cust.City = "London" _
                      Select cust
For Each cust in londonCustomers
   Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City) 

Next

Мы продолжим использовать строго типизированный класс Northwind для оставшейся части обзорного документа.

Определение отношений

Связи в реляционных базах данных обычно моделируются как значения внешнего ключа, ссылающиеся на первичные ключи в других таблицах. Для перехода между ними необходимо явно объединить две таблицы с помощью операции реляционного соединения. Объекты, с другой стороны, ссылаются друг на друга с помощью ссылок на свойства или коллекций ссылок, перемещаемых с помощью точечной нотации. Очевидно, что пунктирное создание проще, чем объединение, так как при каждом переходе не нужно запоминать явное условие соединения.

Для таких связей данных, которые всегда будут одинаковыми, очень удобно кодировать их как ссылки на свойства в классе сущности. LINQ to SQL определяет атрибут Association, который можно применить к члену, используемому для представления связи. Связь похожа на связь между внешним ключом и первичным ключами, которая создается путем сопоставления значений столбцов между таблицами.

C#

[Table(Name="Customers")]
public class Customer
{
   [Column(Id=true)]
   public string CustomerID;
   ...
   private EntitySet<Order> _Orders;
   [Association(Storage="_Orders", OtherKey="CustomerID")]
   public EntitySet<Order> Orders {
      get { return this._Orders; }
      set { this._Orders.Assign(value); }
   }
}

Visual Basic

<Table(Name:="Customers")> _
Public Class Customer

   <Column(Id:=true)> _
   Public CustomerID As String
   ...
   Private _Orders As EntitySet(Of Order)
                  <Association(Storage:="_Orders", OtherKey:="CustomerID")> _
         Public Property Orders() As EntitySet(Of Order)
            Get
               Return Me._Orders
            End Get
            Set(ByVal value As EntitySet(Of Order))
            End Set
   End Property

End Class

Класс Customer теперь имеет свойство, которое объявляет связь между клиентами и их заказами. Свойство Orders имеет тип EntitySet , так как отношение "один ко многим". Мы используем свойство OtherKey в атрибуте Association , чтобы описать, как выполняется эта связь. Он задает имена свойств в связанном классе, которые будут сравниваться с этим. Также было свойство ThisKey , которое мы не указали. Как правило, мы используем его для перечисления членов на этой стороне связи. Тем не менее, пропуская его, мы позволяем LINQ to SQL выводить их из членов, составляющих первичный ключ.

Обратите внимание на то, как это обратное в определении класса Order .

C#

[Table(Name="Orders")]
public class Order
{
   [Column(Id=true)]
   public int OrderID;
   [Column]
   public string CustomerID;
   private EntityRef<Customer> _Customer;    
   [Association(Storage="_Customer", ThisKey="CustomerID")]
   public Customer Customer {
      get { return this._Customer.Entity; }
      set { this._Customer.Entity = value; }
   }
}

Visual Basic

<Table(Name:="Orders")> _
Public Class Order

   <Column(Id:=true)> _
   Public OrderID As String
   <Column> _
   Public CustomerID As String
   Private _Customer As EntityRef(Of Customer)
         <Association(Storage:="_Customer", ThisKey:="CustomerID")> _
         Public Property Customer() As Customer
            Get
               Return Me._Customer.Entity
            End Get
            Set(ByVal value As Customer)
               Me._Customers.Entity = value
            End Set
   End Property
End Class

Класс Order использует тип EntityRef для описания связи с клиентом. Использование класса EntityRef требуется для поддержки отложенной загрузки (рассматривается далее). Атрибут Association для свойства Customer указывает свойство ThisKey , так как невычислимые члены теперь находятся на этой стороне связи.

Кроме того, ознакомьтесь со свойством Storage . Он сообщает LINQ to SQL, какой частный член используется для хранения значения свойства. Это позволяет LINQ to SQL обходить методы доступа к общедоступным свойствам, когда они сохраняют и извлекают их значение. Это важно, если вы хотите LINQ to SQL, чтобы избежать пользовательской бизнес-логики, записанной в методы доступа. Если свойство хранилища не указано, вместо него будут использоваться открытые методы доступа. Вы также можете использовать свойство Storage с атрибутами Column .

После введения связей в классах сущностей объем кода, который необходимо написать, увеличивается по мере внедрения поддержки уведомлений и согласованности графов. К счастью, существует инструмент (описанный далее), который можно использовать для создания всех необходимых определений в виде разделяемых классов, что позволяет использовать сочетание созданного кода и пользовательской бизнес-логики.

В остальной части этого документа предполагается, что средство использовалось для создания полного контекста данных Northwind и всех классов сущностей.

Выполнение запросов в связях

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

C#

var q =
   from c in db.Customers
   from o in c.Orders
   where c.City == "London"
   select new { c, o };

Visual Basic

Dim londonCustOrders = From cust In db.Customers, ord In cust.Orders _
                       Where cust.City = "London" _
                       Select Customer = cust, Order = ord

В приведенном выше запросе используется свойство Orders для формирования перекрестного продукта между клиентами и заказами, создавая новую последовательность пар "Клиент " и "Заказ ".

Можно также сделать обратное.

C#

var q =
   from o in db.Orders
   where o.Customer.City == "London"
   select new { c = o.Customer, o };

Visual Basic

Dim londonCustOrders = From ord In db.Orders _
                       Where ord.Customer.City = "London" _
                       Select Customer = ord.Customer, Order = ord

В этом примере запрашиваются заказы, а связь "Клиент " используется для доступа к информации о связанном объекте Customer .

Изменение и сохранение сущностей

Немногие приложения создаются с учетом только запросов. Данные также должны быть созданы и изменены. LINQ to SQL обеспечивает максимальную гибкость при управлении и сохранении изменений, внесенных в объекты. Как только объекты сущности становятся доступными (путем их извлечения с помощью запроса или создания заново), вы можете управлять ими как обычными объектами в приложении, изменяя их значения или добавляя и удаляя их из коллекций по своему выбору. LINQ to SQL отслеживает все изменения и готова передать их обратно в базу данных сразу после завершения.

В приведенном ниже примере используются классы Customer и Order , созданные средством на основе метаданных всей базы данных-образца Northwind. Определения классов не показаны для краткости.

C#

Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");
// Query for a specific customer
string id = "ALFKI";
var cust = db.Customers.Single(c => c.CustomerID == id);
// Change the name of the contact
cust.ContactName = "New Contact";
// Create and add a new Order to Orders collection
Order ord = new Order { OrderDate = DateTime.Now };
cust.Orders.Add(ord);
// Ask the DataContext to save all the changes
db.SubmitChanges();

Visual Basic

Dim db As New Northwind("c:\northwind\northwnd.mdf")
' Query for a specific customer
Dim id As String = "ALFKI"
Dim targetCustomer = (From cust In db.Customers _ 
                     Where cust.CustomerID = id).First
' Change the name of the contact
targetCustomer.ContactName = "New Contact"
' Create and add a new Order to Orders collection
Dim id = New Order With { .OrderDate = DateTime.Now }
targetCustomer.Orders.Add(ord)
' Ask the DataContext to save all the changes
db.SubmitChanges()

При вызове Метода SubmitChanges() LINQ to SQL автоматически создает и выполняет команды SQL для передачи изменений обратно в базу данных. Это поведение также можно переопределить с помощью пользовательской логики. Пользовательская логика может вызывать хранимую процедуру базы данных.

In-Depth запросов

LINQ to SQL предоставляет реализацию стандартных операторов запросов для объектов, связанных с таблицами в реляционной базе данных. В этой главе описываются LINQ to SQL аспекты запросов.

Выполнение запроса

Независимо от того, пишете ли вы запрос как высокоуровневое выражение запроса или создаете один из отдельных операторов, создаваемый запрос не является императивным оператором, который выполняется немедленно. Это описание. Например, в объявлении ниже локальная переменная q ссылается на описание запроса, а не на результат его выполнения.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c;
foreach (Customer c in q)
   Console.WriteLine(c.CompanyName);

Visual Basic

Dim londonCustomers = From cust In db.Customers _
                       where cust.City = "London"
For Each cust  In londonCustomers
   Console.WriteLine(cust.CompanyName) 
Next

Фактическим типом q в этом экземпляре является IQueryable<Customer>. Это не до тех пор, пока приложение не попытается перечислить содержимое запроса, которое оно фактически выполняет. В этом примере оператор foreach вызывает выполнение.

Объект IQueryable аналогичен объекту команды ADO.NET. Наличие одного из них не означает, что запрос был выполнен. Объект команды удерживает строку, описывающую запрос. Аналогичным образом объект IQueryable содержит описание запроса, закодированного в виде структуры данных, известной как выражение. Объект команды имеет метод ExecuteReader(), который вызывает выполнение, возвращая результаты в виде DataReader. Объект IQueryable имеет метод GetEnumerator(), который вызывает выполнение, возвращая результаты в качестве клиента> IEnumerator<.

Следовательно, если запрос перечисляется дважды, он будет выполнен дважды.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c;
// Execute first time
foreach (Customer c in q)
   Console.WriteLine(c.CompanyName);
// Execute second time
foreach (Customer c in q)
   Console.WriteLine(c.CompanyName);

Visual Basic

Dim londonCustomers = From cust In db.Customers _
                       where cust.City = "London"
' Execute first time
For Each cust In londonCustomers
   Console.WriteLine(cust.CompanyName) 
Next
' Execute second time
For Each cust In londonCustomers
   Console.WriteLine(cust.CustomerID) 
Next

Такое поведение называется отложенным выполнением. Как и в ADO.NET объекте команды, можно удерживать запрос и повторно выполнить его.

Конечно, средства записи приложений часто должны четко определить, где и когда выполняется запрос. Было бы неожиданным, если бы приложение выполнило запрос несколько раз, просто потому, что ему нужно было изучить результаты несколько раз. Например, может потребоваться привязать результаты запроса к чему-то вроде DataGrid. Элемент управления может перечислять результаты каждый раз, когда он рисует на экране.

Чтобы избежать многократного выполнения, преобразуйте результаты в любое количество стандартных классов коллекций. Результаты легко преобразовать в список или массив с помощью стандартных операторов запросов ToList() или ToArray().

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c;
// Execute once using ToList() or ToArray()
var list = q.ToList();
foreach (Customer c in list)
   Console.WriteLine(c.CompanyName);
foreach (Customer c in list)
   Console.WriteLine(c.CompanyName);

Visual Basic

Dim londonCustomers = From cust In db.Customers _
                       where cust.City = "London"
' Execute once using ToList() or ToArray()
Dim londonCustList = londonCustomers.ToList()
' Neither of these iterations re-executes the query
For Each cust In londonCustList
   Console.WriteLine(cust.CompanyName)
Next
For Each cust In londonCustList
   Console.WriteLine(cust.CompanyName)
Next 

Одно из преимуществ отложенного выполнения заключается в том, что запросы могут создаваться по частям, а выполнение выполняется только после завершения построения. Вы можете начать с создания части запроса, назначить ее локальной переменной, а затем через некоторое время применить к ней дополнительные операторы.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c;
if (orderByLocation) {
   q =
      from c in q
      orderby c.Country, c.City
      select c;
}
else if (orderByName) {
   q =
      from c in q
      orderby c.ContactName
      select c;
}
foreach (Customer c in q)
   Console.WriteLine(c.CompanyName);

Visual Basic

Dim londonCustomers = From cust In db.Customers _
                       where cust.City = "London"
if orderByLocation Then
   londonCustomers = From cust in londonCustomers _
                     Order By cust.Country, cust.City

Else If orderByName Then
   londonCustomers = From cust in londonCustomers _
                     Order By cust.ContactName
End If
For Each cust In londonCustList
   Console.WriteLine(cust.CompanyName)
Next 

В этом примере q начинается как запрос для всех клиентов в Лондоне. Позже он преобразуется в упорядоченный запрос в зависимости от состояния приложения. Отложив выполнение, запрос можно создать в соответствии с конкретными потребностями приложения, не требуя рискованных операций со строками.

Идентификация объектов

Объекты в среде выполнения имеют уникальное удостоверение. Если две переменные ссылаются на один и тот же объект, они фактически ссылаются на один и тот же экземпляр объекта. По этой причине изменения, внесенные через путь через одну переменную, сразу видны через другую. Строки в таблице реляционной базы данных не имеют уникального удостоверения. Однако у них есть первичный ключ, и этот первичный ключ может быть уникальным, а это означает, что ни один из двух строк не может использовать один и тот же ключ. Однако это ограничивает только содержимое таблицы базы данных. Таким образом, пока мы взаимодействуем с данными только с помощью удаленных команд, это примерно то же самое.

Однако это происходит редко. Чаще всего данные извлекается из базы данных на другой уровень, где приложение управляет ими. Очевидно, что эта модель предназначена для поддержки LINQ to SQL. При извлечении данных из базы данных в виде строк не ожидается, что две строки, представляющие одни и те же данные, фактически соответствуют тем же экземплярам строк. Если вы дважды запрашиваете определенного клиента, вы получите две строки данных, каждая из которых содержит одну и ту же информацию.

Тем не менее, с объектами вы ожидаете что-то совершенно другое. Вы ожидаете, что если вы снова запросите у DataContext те же сведения, он фактически возвратит вам тот же экземпляр объекта. Вы ожидаете этого, так как объекты имеют особое значение для вашего приложения, и вы ожидаете, что они будут вести себя как обычные объекты. Вы разработали их как иерархии или графы, и вы, безусловно, ожидаете получить их как таковые, без орд реплицированных экземпляров только потому, что вы дважды просили одно и то же.

По этой причине DataContext управляет удостоверением объекта. Каждый раз, когда новая строка извлекается из базы данных, она регистрируется в таблице удостоверений по ее первичному ключу и создается новый объект . При повторном извлечении той же строки исходный экземпляр объекта передается приложению. Таким образом, DataContext преобразует концепцию удостоверений (ключей) баз данных в концепцию языков (экземпляров). Приложение видит объект только в том состоянии, в который он был впервые получен. Новые данные, если они отличаются, выбрасываются.

Вы можете быть озадачены этим, так как почему любое приложение выбрасывает данные? Как оказалось, так LINQ to SQL управляет целостностью локальных объектов и поддерживает оптимистические обновления. Так как после первоначального создания объекта происходят только изменения, внесенные приложением, цель приложения ясна. Если изменения сторонней стороной произошли в промежуточный период, они будут определены во время вызова SubmitChanges(). Дополнительные сведения см. в разделе Одновременные изменения.

Обратите внимание, что в случае, если база данных содержит таблицу без первичного ключа, LINQ to SQL позволяет отправлять запросы по таблице, но не разрешает обновления. Это связано с тем, что платформа не может определить, какую строку следует обновить, учитывая отсутствие уникального ключа.

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

Отношения

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

C#

var q =
   from o in db.Orders
   where o.Customer.City == "London"
   select o;

Visual Basic

Dim londonOrders = From ord In db.Orders _
                       where ord.Customer.City = "London"

Если бы свойства связи не существовали, их придется записывать вручную в виде соединений так же, как в SQL-запросе.

C#

var q =
   from c in db.Customers
   join o in db.Orders on c.CustomerID equals o.CustomerID
   where c.City == "London"
   select o;

Visual Basic

Dim londonOrders = From cust In db.Customers _
                            Join ord In db.Orders _
                            On cust.CustomerID Equals ord.CustomerID _
                   Where ord.Customer.City = "London" _
                   Select ord

Свойство relationship позволяет определить эту конкретную связь после использования более удобного синтаксиса точки. Однако это не причина, по которой существуют свойства связи. Они существуют потому, что мы, как правило, определяем наши предметные объектные модели как иерархии или графы. Выбранные объекты имеют ссылки на другие объекты. Это только счастливое совпадение, что, поскольку связи между объектами соответствуют связям в стиле внешнего ключа в базах данных, доступ к свойствам приводит к удобному способу записи соединений.

Поэтому наличие свойств связи на стороне результатов запроса важнее, чем в составе самого запроса. Как только вы получите в руки определенного клиента, его определение класса сообщает вам, что у клиентов есть заказы. Поэтому при просмотре свойства Orders определенного клиента вы ожидаете увидеть коллекцию, заполненную всеми заказами клиента, так как это фактически контракт, объявленный путем определения классов таким образом. Вы ожидаете увидеть заказы там, даже если вы не особенно просили заказы вперед. Ожидается, что объектная модель будет поддерживать иллюзию того, что это расширение базы данных в памяти с немедленной доступностью связанных объектов.

LINQ to SQL реализует метод, называемый отложенной загрузкой, чтобы помочь сохранить эту иллюзию. При запросе объекта фактически извлекаются только те объекты, которые вы запрашивали. Связанные объекты не извлекаются автоматически одновременно с основным объектом. Однако тот факт, что связанные объекты еще не загружены, не наблюдается, так как при попытке получить к ним доступ отправляется запрос на их получение.

C#

var q =
   from o in db.Orders
   where o.ShipVia == 3
   select o;
foreach (Order o in q) {
   if (o.Freight > 200)
      SendCustomerNotification(o.Customer);
   ProcessOrder(o);
}

Visual Basic

Dim shippedOrders = From ord In db.Orders _
                    where ord.ShipVia = 3
For Each ord In shippedOrders
   If ord.Freight > 200 Then
      SendCustomerNotification(ord.Customer) 
      ProcessOrder(ord)
   End If
Next

Например, может потребоваться запросить определенный набор заказов, а затем лишь иногда отправлять уведомления по электронной почте определенным клиентам. Вам не нужно будет получать все данные клиента с каждым заказом. Отложенная загрузка позволяет отложить затраты на получение дополнительных сведений до тех пор, пока это не потребуется.

Конечно, может быть и обратное. Возможно, у вас есть приложение, которое должно одновременно искать данные о клиентах и заказах. В этом случае необходимы оба набора данных. Вы знаете, что ваше приложение будет детализировать заказы каждого клиента, как только вы получите их. Было бы неудачно запускать отдельные запросы на заказы для каждого клиента. То, что вы действительно хотите, это получить данные о заказах вместе с клиентами.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c;
foreach (Customer c in q) {
   foreach (Order o in c.Orders) {
      ProcessCustomerOrder(o);
   }
}

Visual Basic

Dim londonCustomers = From cust In db.Customer _
                   Where cust.City = "London"
For Each cust In londonCustomers
   For Each ord In cust.Orders
      ProcessCustomerOrder(ord) 
   End If
Next

Конечно, вы всегда можете найти способ объединить клиентов и заказы в запросе, формируя перекрестный продукт и извлекая все относительные биты данных в виде одной большой проекции. Но тогда результаты не будут сущностями. Сущности — это объекты с идентификатором, которые можно изменить, в то время как результаты будут проекциями, которые нельзя изменить и сохранить. Хуже того, вы будете получать огромное количество избыточных данных, так как каждый клиент повторяет для каждого заказа в выходных данных плоского соединения.

Вам действительно нужен способ одновременного получения набора связанных объектов — разграниченной части графа, чтобы вы никогда не извлекали больше или меньше, чем было необходимо для предполагаемого использования.

LINQ to SQL позволяет запросить немедленную загрузку области объектной модели именно по этой причине. Это делается путем предоставления спецификации DataShape для DataContext. Класс DataShape используется для указания платформе о том, какие объекты следует извлечь при извлечении определенного типа. Для этого используется метод LoadWith , как показано ниже.

C#

DataShape ds = new DataShape();
ds.LoadWith<Customer>(c => c.Orders);
db.Shape = ds;
var q = 
   from c in db.Customers
   where c.City == "London"
   select c;

Visual Basic

Dim ds As DataShape = New DataShape()
ds.LoadWith(Of Customer)(Function(c As Customer) c.Orders)
db.Shape = ds
Dim londonCustomers = From cust In db.Customers _
                      Where cust.City = "London" _
                      Select cust

В предыдущем запросе все заказы для всех клиентов , проживающих в Лондоне, извлекаются при выполнении запроса, чтобы последовательный доступ к свойству Orders в объекте Customer не запускал запрос к базе данных.

Класс DataShape также можно использовать для указания вложенных запросов, применяемых к навигации по связям. Например, если вы хотите получить только заказы , которые были отправлены сегодня, можно использовать метод AssociateWith в DataShape , как показано ниже:

C#

DataShape ds = new DataShape();
ds.AssociateWith<Customer>(
   c => c.Orders.Where(p => p.ShippedDate != DateTime.Today));
db.Shape = ds;
var q = 
   from c in db.Customers
   where c.City == "London"
   select c;
foreach(Customer c in q) {
   foreach(Order o in c.Orders) {}
}

Visual Basic

Dim ds As DataShape = New DataShape()
ds.AssociateWith(Of Customer)( _
         Function(cust As Customer) From cust In db.Customers _
                                 Where order.ShippedDate <> Today _
                                 Select cust)
db.Shape = ds
Dim londonCustomers = From cust In db.Customers _
                      Where cust.City = "London" _
                      Select cust
For Each cust in londonCustomers
   For Each ord In cust.Orders …
   Next
   Next

В предыдущем коде внутренний оператор foreach выполняет итерацию по заказам , которые были отправлены сегодня, так как именно такие заказы были получены из базы данных.

Важно отметить два факта о классе DataShape :

  1. После назначения DataShapeобъекту DataContext изменить объект DataShape невозможно. Любой вызов метода LoadWith или AssociateWith для такого метода DataShape вернет ошибку во время выполнения.

  2. Невозможно создать циклы с помощью LoadWith или AssociateWith. Например, следующее приводит к возникновению ошибки во время выполнения:

    C#

    DataShape ds = new DataShape();
    ds.AssociateWith<Customer>(
             c=>c.Orders.Where(o=> o.Customer.Orders.Count() < 35);
    

    Visual Basic

    Dim ds As DataShape = New DataShape()
    ds.AssociateWith(Of Customer)( _
             Function(cust As Customer) From ord In cust.Orders _
                          Where ord.Customer.Orders.Count() < 35)
    

Объединения

Большинство запросов к объектным моделям в значительной степени зависят от навигации по ссылкам на объекты в объектной модели. Однако существуют интересные "связи" между сущностями, которые не могут быть записаны в объектной модели в качестве ссылок. Например , Customer.Orders — это полезная связь, основанная на связях внешнего ключа в базе данных Northwind. Однако поставщики и клиенты в одном городе или стране являются нерегламентированными отношениями, которые не основаны на связи внешнего ключа и не могут быть зафиксированы в объектной модели. Соединения предоставляют дополнительный механизм для обработки таких связей. LINQ to SQL поддерживает новые операторы соединения, появившиеся в LINQ.

Рассмотрим следующую проблему: поиск поставщиков и клиентов, базирующихся в одном городе. Следующий запрос возвращает названия поставщиков и клиентов и общий город в виде плоского результата. Это эквивалент внутреннего равного соединения в реляционных базах данных:

C#

var q = 
   from s in db.Suppliers
   join c in db.Customers on s.City equals c.City
   select new {
      Supplier = s.CompanyName,
      Customer = c.CompanyName,
      City = c.City
   };

Visual Basic

Dim customerSuppliers = From sup In db.Suppliers _
                        Join cust In db.Customers _
                              On sup.City Equals cust.City _
                        Select Supplier = sup.CompanyName, _
                        CustomerName = cust.CompanyName, _
                        City = cust.City

Приведенный выше запрос исключает поставщиков, которые не находятся в том же городе, что и определенный клиент. Однако бывают случаи, когда мы не хотим удалять одну из сущностей в нерегламентированном отношении. В следующем запросе перечислены все поставщики с группами клиентов для каждого из поставщиков. Если у конкретного поставщика нет клиентов в одном городе, результатом будет пустая коллекция клиентов, соответствующая этому поставщику. Обратите внимание, что результаты не являются плоскими— у каждого поставщика есть связанная коллекция. Фактически это обеспечивает групповое соединение — соединяет две последовательности и группирует элементы второй последовательности элементами первой последовательности.

C#

var q = 
   from s in db.Suppliers
   join c in db.Customers on s.City equals c.City into scusts
   select new { s, scusts };

Visual Basic

Dim customerSuppliers = From sup In db.Suppliers _
                        Group Join cust In db.Customers _
                              On sup.City Equals cust.City _
                              Into supCusts _
                        Select Supplier = sup, _
                        Customers = supCusts

Групповое присоединение также может быть расширено на несколько коллекций. Следующий запрос расширяет приведенный выше запрос, перечисляя сотрудников, которые находятся в том же городе, что и поставщик. Здесь в результате отображается поставщик с (возможно, пустыми) коллекциями клиентов и сотрудников.

C#

var q = 
   from s in db.Suppliers
   join c in db.Customers on s.City equals c.City into scusts
   join e in db.Employees on s.City equals e.City into semps
   select new { s, scusts, semps };

Visual Basic

Dim customerSuppliers = From sup In db.Suppliers _
                        Group Join cust In db.Customers _
                              On sup.City Equals cust.City _
                              Into supCusts _
                        Group Join emp In db.Employees _
                              On sup.City Equals emp.City _
                              Into supEmps _
                        Select Supplier = sup, _
                        Customers = supCusts, Employees = supEmps

Результаты объединения группы также можно преобразовать в плоскую структуру. Результатом объединения групп между поставщиками и клиентами является несколько записей для поставщиков с несколькими клиентами в их городе — по одной на каждого клиента. Пустые коллекции заменяются значениями NULL. Это эквивалентно левому внешнему равному соединению в реляционных базах данных.

C#

var q = 
   from s in db.Suppliers
   join c in db.Customers on s.City equals c.City into sc
   from x in sc.DefaultIfEmpty()
   select new {
      Supplier = s.CompanyName, 
      Customer = x.CompanyName, 
      City = x.City 
   };

Visual Basic

Dim customerSuppliers = From sup In db.Suppliers _
                        Group Join cust In db.Customers _
                              On sup.City Equals cust.City _
                              Into supCusts _
                        Select Supplier = sup, _
                        CustomerName = supCusts.CompanyName, sup.City

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

Проекции

До сих пор мы рассмотрели только запросы для получения сущностей — объектов, непосредственно связанных с таблицами базы данных. Мы не должны ограничивать себя только этим. Прелесть языка запросов заключается в том, что вы можете получать сведения в любой форме. При этом вы не сможете воспользоваться преимуществами автоматического отслеживания изменений или управления удостоверениями. Однако вы можете получить только нужные данные.

Например, вам может потребоваться просто знать названия компаний всех клиентов в Лондоне. В этом случае нет особых оснований извлекать все объекты клиента, чтобы просто выбрать имена. Вы можете проецируемые имена как часть запроса.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select c.CompanyName;

Visual Basic

Dim londonCustomerNames = From cust In db.Customer _
                          Where cust.City = "London" _
                          Select cust.CompanyName

В этом случае q становится запросом, который получает последовательность строк.

Если вы хотите получить больше, чем просто одно имя, но не достаточно для получения всего объекта клиента, можно указать любое подмножество, создав результаты как часть запроса.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new { c.CompanyName, c.Phone };

Visual Basic

Dim londonCustomerInfo = From cust In db.Customer _
                         Where cust.City = "London" _
                         Select cust.CompanyName, cust.Phone

В этом примере анонимный инициализатор объектов используется для создания структуры, содержащей название компании и номер телефона. Возможно, вы не знаете, что вызывать тип, но с неявно типизированным объявлением локальной переменной на языке, который вам не обязательно требуется.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new { c.CompanyName, c.Phone };
foreach(var c in q)
   Console.WriteLine("{0}, {1}", c.CompanyName, c.Phone);

Visual Basic

Dim londonCustomerInfo = From cust In db.Customer _
                         Where cust.City = "London" _
                         Select cust.CompanyName, cust.Phone
For Each cust In londonCustomerInfo 
   Console.WriteLine(cust.CompanyName & ", " & cust.Phone) 
Next

При немедленном использовании данных анонимные типы являются хорошей альтернативой явному определению классов для хранения результатов запроса.

Вы также можете создавать перекрестные продукты целых объектов, хотя это может быть редко.

C#

var q =
   from c in db.Customers
   from o in c.Orders
   where c.City == "London"
   select new { c, o };

Visual Basic

Dim londonOrders = From cust In db.Customer, _
                   ord In db.Orders _
                   Where cust.City = "London" _
                   Select Customer = cust, Order = ord

Этот запрос создает последовательность пар объектов клиента и заказа.

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

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new {Name = c.ContactName, c.Phone} into x
   orderby x.Name
   select x;

Visual Basic

Dim londonItems = From cust In db.Customer _
                  Where cust.City = "London" _
                  Select Name = cust.ContactName, cust.Phone _
                  Order By Name

Однако будьте осторожны с использованием параметризованных конструкторов на данном этапе. Это технически допустимо, но для LINQ to SQL невозможно отслеживать, как использование конструктора влияет на состояние члена, не понимая фактический код внутри конструктора.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new MyType(c.ContactName, c.Phone) into x
   orderby x.Name
   select x;

Visual Basic

Dim londonItems = From cust In db.Customer _
                  Where cust.City = "London" _
                  Select MyType = New MyType(cust.ContactName, cust.Phone) _
                  Order By MyType.Name

Так как LINQ to SQL попытки преобразовать запрос в чисто реляционные локально определенные типы объектов SQL недоступны на сервере для создания. Все создание объектов фактически откладывается до тех пор, пока данные не будут извлечены из базы данных. Вместо фактических конструкторов созданный SQL использует обычную проекцию столбца SQL. Так как переводчик запросов не может понять, что происходит во время вызова конструктора, он не может определить значение поля ИмяMyType.

Вместо этого рекомендуется всегда использовать инициализаторы объектов для кодирования проекций.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new MyType { Name = c.ContactName, HomePhone = c.Phone } into x
   orderby x.Name
   select x;

Visual Basic

Dim londonCustomers = From cust In db.Customer _
                      Where cust.City = "London" _
                      Select Contact = New With {.Name = cust.ContactName, _
                      .Phone = cust.Phone} _
                      Order By Contact.Name

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

C#

var e =
   new XElement("results",
      from c in db.Customers
      where c.City == "London"
      select new XElement("customer",
         new XElement("name", c.ContactName),
         new XElement("phone", c.Phone)
      )
   );

Visual Basic

      Dim x = <results>
                  <%= From cust In db.Customers _
                      Where cust.City = "London" _
                      Select <customer>
                         <name><%= cust.ContactName %></name>
                         <phone><%= cust.Phone %></phone>
                      </customer> 
                 %>
        </results>

При желании можно даже использовать сложное вложение конструкторов объектов, как в этом примере, который создает XML непосредственно из результата запроса. Он работает до тех пор, пока это последняя проекция запроса.

Тем не менее, даже если вызовы конструктора понятны, вызовы локальных методов могут не быть. Если для окончательной проекции требуется вызов локальных методов, маловероятно, что LINQ to SQL сможет обязать. Вызовы методов, которые не имеют известного преобразования в SQL, нельзя использовать как часть запроса. Одним из исключений из этого правила являются вызовы методов, которые не имеют аргументов, зависимых от переменных запроса. Они не считаются частью переведенного запроса и обрабатываются как параметры.

Однако для реализации сложных проекций (преобразований) может потребоваться локальная процедурная логика. Чтобы использовать собственные локальные методы в окончательной проекции, необходимо проецировать дважды. Первая проекция извлекает все значения данных, на которые необходимо ссылаться, а вторая проекция выполняет преобразование. Между этими двумя проекциями вызывается оператор AsEnumerable(), который перемещает обработку в этот момент из LINQ to SQL запроса в локально выполняемый.

C#

var q =
   from c in db.Customers
   where c.City == "London"
   select new { c.ContactName, c.Phone };
var q2 =
   from c in q.AsEnumerable()
   select new MyType {
      Name = DoNameProcessing(c.ContactName),
      Phone = DoPhoneProcessing(c.Phone)
   };

Visual Basic

Dim londonCustomers = From cust In db.Customer _
                      Where cust.City = "London" _
                      Select cust.ContactName, cust.Phone

Dim processedCustomers = From cust In londonCustomers.AsEnumerable() _
                         Select Contact = New With { _
                         .Name = DoNameProcessing(cust.ContactName), _
                         .Phone = DoPhoneProcessing(cust.Phone)}

Примечание Оператор AsEnumerable(), в отличие от ToList() и ToArray(), не вызывает выполнение запроса. Он по-прежнему откладывается. Оператор AsEnumerable() просто изменяет статическую типизацию запроса, превращая IQueryable<T> (iQueryable (ofT) в IEnumerable<T> (IEnumerable (ofT) в Visual Basic), что позволяет компилятору обрабатывать остальную часть запроса как локально выполненную.

Компилированные запросы

Во многих приложениях часто выполняются похожие запросы. В таких случаях можно повысить производительность, скомпилируя запрос один раз и выполняя его несколько раз в приложении с разными параметрами. Этот результат получается в LINQ to SQL с помощью класса CompiledQuery. В следующем коде показано, как определить скомпилированный запрос:

C#

static class Queries
{
   public static Func<Northwind, string, IQueryable<Customer>>
      CustomersByCity = CompiledQuery.Compile((Northwind db, string city) =>
         from c in db.Customers where c.City == city select c);
}

Visual Basic

Class Queries
   public Shared Function(Of Northwind, String, IQueryable(Of Customer)) _      CustomersByCity = CompiledQuery.Compile( _
                Function(db As Northwind, city As String) _
                From cust In db.Customers Where cust.City = city)
End Class

Метод Compile возвращает делегат, который можно кэшировать и выполнять несколько раз, просто изменяя входные параметры. В приведенном ниже коде показан соответствующий пример:

C#

public IEnumerable<Customer> GetCustomersByCity(string city) {
         Northwind db = new Northwind();
         return Queries.CustomersByCity(myDb, city);
}

Visual Basic

Public Function GetCustomersByCity(city As String) _ 
               As IEnumerable(Of Customer)
         Dim db As Northwind = New Northwind()
         Return Queries.CustomersByCity(myDb, city)
End Function

Преобразование SQL

LINQ to SQL фактически не выполняет запросы; это делает реляционная база данных. LINQ to SQL преобразует написанные вами запросы в эквивалентные SQL-запросы и отправляет их на сервер для обработки. Так как выполнение отложено, LINQ to SQL может проверить весь запрос, даже если он собран из нескольких частей.

Так как сервер реляционной базы данных фактически не выполняет IL (за исключением интеграции со средой CLR в SQL Server 2005), запросы не передаются на сервер как IL. Фактически они передаются в виде параметризованных SQL-запросов в текстовой форме.

Конечно, SQL, даже T-SQL с интеграцией со средой CLR, не может выполнять различные методы, которые локально доступны вашей программе. Поэтому создаваемые запросы должны быть преобразованы в эквивалентные операции и функции, доступные в среде SQL.

Большинство методов и операторов в встроенных типах .NET Framework имеют прямые преобразования в SQL. Некоторые из них могут быть созданы из доступных функций. Те, которые не могут быть переведены, запрещены, что создает исключения во время выполнения, если вы пытаетесь их использовать. Далее в документе есть раздел, в который подробно описаны методы платформы, реализованные для преобразования в SQL.

Жизненный цикл сущности

LINQ to SQL — это не просто реализация стандартных операторов запросов для реляционных баз данных. Помимо перевода запросов, это служба, которая управляет объектами на протяжении всего времени их существования, помогая поддерживать целостность данных и автоматизировать процесс преобразования изменений обратно в хранилище.

В типичном сценарии объекты извлекаются с помощью одного или нескольких запросов, а затем обрабатываются тем или иным способом, пока приложение не будет готово к отправке изменений обратно на сервер. Этот процесс может повторяться несколько раз, пока приложение больше не будет использовать эти сведения. На этом этапе объекты освобождаются средой выполнения так же, как и обычные объекты. Однако данные остаются в базе данных. Даже после удаления из их существования во время выполнения объекты, представляющие одни и те же данные, по-прежнему могут быть извлечены. В этом смысле истинное время существования объекта существует за пределами любого отдельного проявления во время выполнения.

В этой главе основное внимание уделяется жизненному циклу сущности , в котором цикл относится к временному интервалу одного проявления объекта сущности в определенном контексте времени выполнения. Цикл начинается, когда DataContext узнает о новом экземпляре, и заканчивается, когда объект или DataContext больше не требуется.

Отслеживание изменений

После извлечения сущностей из базы данных вы можете управлять ими по мере необходимости. Они являются вашими объектами; используйте их по мере необходимости. По мере этого LINQ to SQL отслеживает изменения, чтобы сохранить их в базе данных при вызове Метода SubmitChanges().

LINQ to SQL начинает отслеживать сущности в тот момент, когда они извлекаются из базы данных, прежде чем вы когда-либо приложите к ним руки. Действительно, служба управления удостоверениями , о которой говорилось ранее, уже начала работу. Отслеживание изменений стоит очень мало дополнительных затрат, пока вы не начнете вносить изменения.

C#

Customer cust = db.Customers.Single(c => c.CustomerID == "ALFKI");
cust.CompanyName = "Dr. Frogg's Croakers";

Visual Basic

' Query for a specific customer
Dim id As String = "ALFKI"
Dim targetCustomer = (From cust In db.Customers _ 
                      Where cust.CustomerID = id).First
targetCustomer.CompanyName = "Dr. Frogg's Croakers"

Как только в приведенном выше примере будет присвоено значение CompanyName, LINQ to SQL узнает об изменении и сможет записать его. Исходные значения всех элементов данных сохраняются службой отслеживания изменений.

Служба отслеживания изменений также записывает все манипуляции со свойствами связи. Свойства связи используются для установления связей между сущностями, даже если они могут быть связаны значениями ключей в базе данных. Нет необходимости напрямую изменять элементы, связанные с ключевыми столбцами. LINQ to SQL автоматически синхронизирует их перед отправкой изменений.

C#

Customer cust1 = db.Customers.Single(c => c.CustomerID == custId1);
foreach (Order o in db.Orders.Where(o => o.CustomerID == custId2)) {
   o.Customer = cust1;
}

Visual Basic

Dim targetCustomer = (From cust In db.Customers _ 
                      Where cust.CustomerID = custId1).First

For Each ord In (From o In db.Orders _ 
                 Where o.CustomerID = custId2) 
   o.Customer = targetCustomer
Next

Вы можете переместить заказы от одного клиента к другому, просто сделав назначение его свойству Клиента . Так как связь существует между клиентом и заказом, вы можете изменить связь, изменив обе стороны. Вы могли бы так же легко удалить их из коллекции Orderscust2 и добавить их в коллекцию заказов cust1, как показано ниже.

C#

Customer cust1 = db.Customers.Single(c => c.CustomerID == custId1);
Customer cust2 = db.Customers.Single(c => c.CustomerID == custId2); 
// Pick some order
Order o = cust2.Orders[0]; 
// Remove from one, add to the other
cust2.Orders.Remove(o);
cust1.Orders.Add(o);
// Displays 'true'
Console.WriteLine(o.Customer == cust1);

Visual Basic

Dim targetCustomer1 = (From cust In db.Customers _ 
                       Where cust.CustomerID = custId1).First
Dim targetCustomer2 = (From cust In db.Customers _ 
                       Where cust.CustomerID = custId1).First
' Pick some order
Dim o As Order = targetCustomer2.Orders(0) 
' Remove from one, add to the other
targetCustomer2.Orders.Remove(o)
targetCustomer1.Orders.Add(o)
' Displays 'True'
MsgBox(o.Customer = targetCustomer1)

Конечно, если присвоить связи значение NULL, вы на самом деле полностью избавишься от связи. При присвоении свойству customer заказа значения NULL заказ фактически удаляется из списка клиента.

C#

Customer cust = db.Customers.Single(c => c.CustomerID == custId1); 
// Pick some order
Order o = cust.Orders[0];
// Assign null value
o.Customer = null;
// Displays 'false'
Console.WriteLine(cust.Orders.Contains(o));

Visual Basic

Dim targetCustomer = (From cust In db.Customers _ 
                       Where cust.CustomerID = custId1).First
' Pick some order
Dim o As Order = targetCustomer.Orders(0)
' Assign null value
o.Customer = Nothing
' Displays 'False'
Msgbox(targetCustomer.Orders.Contains(o))

Автоматическое обновление обеих сторон связи имеет важное значение для обеспечения согласованности графа объектов. В отличие от обычных объектов, связи между данными часто являются двунаправленными. LINQ to SQL позволяет использовать свойства для представления связей. Однако он не предлагает службу для автоматической синхронизации этих двунаправленных свойств. Это уровень обслуживания, который необходимо включить непосредственно в определения классов. Классы сущностей, созданные с помощью средства создания кода, обладают этой возможностью. В следующей главе мы покажем, как это сделать для собственных рукописных классов.

Однако важно отметить, что удаление связи не означает, что объект был удален из базы данных. Помните, что время существования базовых данных сохраняется в базе данных до тех пор, пока строка не будет удалена из таблицы. Единственный способ фактически удалить объект — удалить его из коллекции Table .

C#

Customer cust = db.Customers.Single(c => c.CustomerID == custId1); 
// Pick some order
Order o = cust.Orders[0];
// Remove it directly from the table (I want it gone!)
db.Orders.Remove(o);
// Displays 'false'.. gone from customer's Orders
Console.WriteLine(cust.Orders.Contains(o));
// Displays 'true'.. order is detached from its customer
Console.WriteLine(o.Customer == null);

Visual Basic

Dim targetCustomer = (From cust In db.Customers _ 
                          Where cust.CustomerID = custId1).First
' Pick some order
Dim o As Order = targetCustomer.Orders(0)
' Remove it directly from the table (I want it gone!)
db.Orders.Remove(o)
' Displays 'False'.. gone from customer’s Orders
Msgbox(targetCustomer.Orders.Contains(o))
' Displays 'True'.. order is detached from its customer
Msgbox(o.Customer = Nothing)

Как и в случае с другими изменениями, заказ фактически не был удален. Он просто выглядит таким образом для нас, так как он был удален и отсоединены от остальных наших объектов. Когда объект order был удален из таблицы Orders , он был помечен для удаления службой отслеживания изменений. Фактическое удаление из базы данных будет происходить при отправке изменений при вызове Метода SubmitChanges(). Обратите внимание, что сам объект никогда не удаляется. Среда выполнения управляет временем существования экземпляров объектов, поэтому она сохраняется до тех пор, пока вы сохраняете ссылку на нее. Однако после удаления объекта из таблицы и отправки изменений он больше не отслеживается службой отслеживания изменений.

Единственный другой момент, когда сущность остается без отслеживания, это когда она существует, прежде чем DataContext узнает о ней. Это происходит всякий раз, когда вы создаете новые объекты в коде. Вы можете использовать экземпляры классов сущностей в приложении, не извлекая их из базы данных. Отслеживание изменений и управление удостоверениями применяются только к тем объектам, о которых известно DataContext . Поэтому ни служба не будет включена для вновь созданных экземпляров, пока вы не добавите их в DataContext.

Это может произойти одним из двух способов. Метод Add() можно вызвать для связанной коллекции Table вручную.

C#

Customer cust =
   new Customer {
      CustomerID = "ABCDE",
      ContactName = "Frond Smooty",
      CompanyTitle = "Eggbert's Eduware",
      Phone = "888-925-6000"
   };
// Add new customer to Customers table
db.Customers.Add(cust);

Visual Basic

Dim targetCustomer = New Customer With { _
         .CustomerID = “ABCDE”, _
         .ContactName = “Frond Smooty”, _
         .CompanyTitle = “Eggbert’s Eduware”, _
         .Phone = “888-925-6000”}
' Add new customer to Customers table
db.Customers.Add(cust)

Кроме того, можно присоединить новый экземпляр к объекту, о чем уже известно DataContext .

C#

// Add an order to a customer's Orders
cust.Orders.Add(
   new Order { OrderDate = DateTime.Now }
); 

Visual Basic

' Add an order to a customer's Orders
targetCustomer.Orders.Add( _
   New Order With { .OrderDate = DateTime.Now } )

DataContext обнаружит новые экземпляры объекта, даже если они подключены к другим новым экземплярам.

C#

// Add an order and details to a customer's Orders
Cust.Orders.Add(
   new Order {
      OrderDate = DateTime.Now,
      OrderDetails = {
         new OrderDetail {
            Quantity = 1,
            UnitPrice = 1.25M,
            Product = someProduct
         }
      }
   }
); 

Visual Basic

' Add an order and details to a customer's Orders
targetCustomer.Orders.Add( _
   New Order With { _
      .OrderDate = DateTime.Now, _
      .OrderDetails = New OrderDetail With { _
               .Quantity = 1,
               .UnitPrice = 1.25M,
               .Product = someProduct 
      }
   } )

По сути, DataContext распознает любую сущность в графе объектов, которая в настоящее время не отслеживается как новый экземпляр, независимо от того, вызвали ли вы метод Add().

Использование dataContext, доступного только для чтения

Во многих сценариях не требуется обновлять сущности, полученные из базы данных. Одним из очевидных примеров является отображение таблицы клиентов на веб-странице. Во всех таких случаях можно повысить производительность, проинструктируя DataContext не отслеживать изменения сущностей. Это достигается путем указания свойства ObjectTracking в DataContext на значение false, как показано в следующем коде:

C#

      db.ObjectTracking = false;
      
      var q = db.Customers.Where( c => c.City = "London");
      foreach(Customer c in q)
         Display(c);

Visual Basic

db.ObjectTracking = False
      
      Dim londonCustomers = From cust In db.Customer _
                      Where cust.City = "London"
      For Each c in londonCustomers
         Display(c)
Next

Отправка изменений

Независимо от количества изменений, внесенных в объекты, эти изменения были внесены только в реплики в памяти. С фактическими данными в базе данных пока ничего не произошло. Передача этих сведений на сервер не будет происходить, пока вы явно не запросите ее путем вызова SubmitChanges() в DataContext.

C#

Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");
// make changes here
db.SubmitChanges();

Visual Basic

Dim db As New Northwind("c:\northwind\northwnd.mdf")
' make changes here
db.SubmitChanges()

При вызове SubmitChanges()DataContext попытается преобразовать все изменения в эквивалентные команды SQL, вставив, обновив или удалив строки в соответствующих таблицах. При желании эти действия можно переопределить с помощью собственной пользовательской логики, однако порядок отправки управляется службой DataContext , известной как обработчик изменений.

Первое, что происходит при вызове SubmitChanges(), заключается в том, что набор известных объектов проверяется, чтобы определить, были ли к ним присоединены новые экземпляры. Эти новые экземпляры добавляются в набор отслеживаемых объектов. Затем все объекты с ожидающих изменений упорядочены в последовательность объектов на основе зависимостей между ними. Те объекты, изменения которых зависят от других объектов, секвенируются после их зависимостей. Ограничения внешнего ключа и ограничения уникальности в базе данных играют важную роль в определении правильного порядка изменений. Затем, непосредственно перед передачей каких-либо фактических изменений, начинается транзакция для инкапсуляции ряда отдельных команд, если только они не находятся в область. Наконец, изменения в объектах по очереди претворяются в команды SQL и отправляются на сервер.

На этом этапе все ошибки, обнаруженные базой данных, приведут к прерыванию процесса отправки и возникновению исключения. Откат всех изменений в базе данных будет выполняться так, как если бы ни одна из отправок никогда не была отправлена. DataContext по-прежнему будет иметь полную запись всех изменений, поэтому можно попытаться устранить проблему и повторно отправить их, вызвав SubmitChanges() еще раз.

C#

Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");
// make changes here 
try {
   db.SubmitChanges();
}
catch (Exception e) {
   // make some adjustments
   ...
   // try again
   db.SubmitChanges();
}

Visual Basic

Dim db As New Northwind("c:\northwind\northwnd.mdf")
' make changes here 
Try 
   db.SubmitChanges()
Catch e As Exception
   ' make some adjustments
   ...
   ' try again
   db.SubmitChanges()
End Try

Когда транзакция вокруг отправки завершится успешно, DataContext примет изменения в объектах, просто забыв сведения об отслеживании изменений.

Одновременные изменения

Существует множество причин, по которым вызов SubmitChanges() может завершиться ошибкой. Возможно, вы создали объект с недопустимым первичным ключом; объект , который уже используется, или со значением, которое нарушает некоторые ограничения проверка базы данных. Такие проверки трудно внедрить в бизнес-логику, так как они часто требуют абсолютного знания состояния всей базы данных. Однако наиболее вероятной причиной сбоя является то, что кто-то еще внес изменения в объекты перед вами.

Конечно, это было бы невозможно, если бы вы блокировали каждый объект в базе данных и использовали полностью сериализованную транзакцию. Однако такой стиль программирования (пессимистичный параллелизм) используется редко, так как он является дорогостоящим, и реальные столкновения редко происходят. Наиболее популярной формой управления одновременными изменениями является использование формы оптимистичного параллелизма. В этой модели никакие блокировки для строк базы данных не принимаются. Это означает, что в период между первым извлечением объектов и отправкой изменений в базе данных могло произойти любое количество изменений.

Таким образом, если вы не хотите идти с политикой, которая выигрывает последнее обновление, обтирая все, что произошло до вас, вы, вероятно, хотите быть оповещены о том, что базовые данные были изменены кем-то другим.

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

Вы можете управлять степенью, в которой DataContext обнаруживает конфликты изменений при определении классов сущностей. Каждому атрибуту Column присвоено свойство UpdateCheck , которому можно присвоить одно из трех значений: Always, Never и WhenChanged. Если значение не задано, по умолчанию для атрибута Column используется значение Always, то есть значения данных, представленные этим элементом, всегда проверяются на наличие конфликтов, т. е. если нет очевидного средства разбиения связей, например метки версии. Атрибут Column имеет свойство IsVersion , позволяющее указать, является ли значение данных меткой версии, поддерживаемой базой данных. Если версия существует, она используется только для определения того, произошел ли конфликт.

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

C#

while (retries < maxRetries) {
   Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");

   // fetch objects and make changes here

   try {
      db.SubmitChanges();
      break;
   }
   catch (ChangeConflictException e) {
      retries++;
   }
}

Visual Basic

Do While retries < maxRetries
   Dim db As New Northwind("c:\northwind\northwnd.mdf")

   ' fetch objects and make changes here

   Try
      db.SubmitChanges()
      Exit Do
   
   catch cce As ChangeConflictException
      retries += 1
   End Try
Loop

Если вы вносите изменения на среднем уровне или сервере, самое простое, что можно сделать, чтобы исправить конфликт изменений, — просто начать заново и повторить попытку, воссоздав контекст и повторно применив изменения. Дополнительные параметры описаны в следующем разделе.

Transactions

Транзакция — это служба, предоставляемая базами данных или любым другим диспетчером ресурсов, которая может использоваться для автоматического выполнения ряда отдельных действий. это означает, что либо они все преуспеют, либо они все этого не делают. Если они этого не делают, то они также автоматически отключаются, прежде чем все остальное может произойти. Если транзакция еще не находится в область, DataContext автоматически запустит транзакцию базы данных для защиты обновлений при вызове SubmitChanges().

Вы можете управлять типом используемой транзакции, уровнем ее изоляции или фактическим охватом, инициируя ее самостоятельно. Изоляция транзакций, которую будет использовать DataContext , называется ReadCommitted.

C#

Product prod = db.Products.Single(p => p.ProductID == 15);

if (prod.UnitsInStock > 0)
   prod.UnitsInStock--;

using(TransactionScope ts = new TransactionScope()) {
   db.SubmitChanges();
   ts.Complete();
}

Visual Basic

Dim product = (From prod In db.Products _ 
                         Where prod.ProductID = 15).First

If product.UnitsInStock > 0) Then
   product.UnitsInStock -= 1
End If

Using ts As TransactionScope = New TransactionScope())
   db.SubmitChanges()
   ts.Complete()
End Using

В приведенном выше примере инициируется полностью сериализованная транзакция путем создания новой транзакции область объекте . Все команды базы данных, выполняемые в область транзакции, будут защищены транзакцией.

C#

Product prod = db.Products.Single(p => p.ProductId == 15);

if (prod.UnitsInStock > 0)
   prod.UnitsInStock--;

using(TransactionScope ts = new TransactionScope()) {
   db.ExecuteCommand("exec sp_BeforeSubmit");
   db.SubmitChanges();
   ts.Complete();
}

Visual Basic

Dim product = (From prod In db.Products _ 
                         Where prod.ProductID = 15).First

If product.UnitsInStock > 0) Then
   product.UnitsInStock -= 1
End If

Using ts As TransactionScope = New TransactionScope())
   db.ExecuteCommand(“exec sp_BeforeSubmit”)
   db.SubmitChanges()
   ts.Complete()
End Using

В этой измененной версии того же примера используется метод ExecuteCommand() в DataContext для выполнения хранимой процедуры в базе данных непосредственно перед отправкой изменений. Независимо от того, что выполняет хранимая процедура с базой данных, мы можем быть уверены, что ее действия являются частью одной транзакции.

Если транзакция завершается успешно, DataContext выдает все накопленные сведения об отслеживании и обрабатывает новые состояния сущностей как неизменившиеся. Однако при сбое транзакции изменения объектов не откатываются. Это обеспечивает максимальную гибкость при решении проблем во время отправки изменений.

Кроме того, вместо новой транзакции TransactionScope можно использовать локальную транзакцию SQL. LINQ to SQL предоставляет эту возможность для интеграции функций LINQ to SQL в уже существующие приложения ADO.NET. Тем не менее, если вы идете этим маршрутом, вам нужно будет нести ответственность за гораздо больше.

C#

Product prod = q.Single(p => p.ProductId == 15);

if (prod.UnitsInStock > 0)
   prod.UnitsInStock--;

db.Transaction = db.Connection.BeginTransaction();
try {
   db.SubmitChanges();
   db.Transaction.Commit();
}
catch {
   db.Transaction.Rollback();
   throw;
}
finally {
   db.Transaction = null;
}

Visual Basic

Dim product = (From prod In db.Products _ 
                         Where prod.ProductID = 15).First

If product.UnitsInStock > 0) Then
   product.UnitsInStock -= 1
End If

db.Transaction = db.Connection.BeginTransaction()
Try
   db.SubmitChanges()
   db.Transaction.Commit()

catch e As Exception
   db.Transaction.Rollback()
   Throw e
Finally
   db.Transaction = Nothing
End Try

Как видите, использование транзакции базы данных, управляемой вручную, немного более задействолено. Необходимо не только запустить его самостоятельно, но и явно сообщить DataContext , чтобы он использовался, назначив его свойству Transaction . Затем необходимо использовать блок try-catch, чтобы включить логику отправки, не забывая явно сообщить транзакции о фиксации и явно сообщить DataContext о принятии изменений или прервать транзакции в случае сбоя в любой момент. Кроме того, не забудьте задать свойству Transaction значение NULL по завершении.

Хранимые процедуры

При вызове Метода SubmitChanges() LINQ to SQL создает и выполняет команды SQL для вставки, обновления и удаления строк в базе данных. Эти действия могут быть переопределены разработчиками приложений, а вместо них можно использовать пользовательский код для выполнения требуемых действий. Таким образом, обработчик изменений может автоматически вызывать альтернативные средства, такие как хранимые в базе данных процедуры.

Рассмотрим хранимую процедуру обновления единиц на складе для таблицы Products в образце базы данных Northwind. Sql-объявление процедуры выглядит следующим образом.

SQL

create proc UpdateProductStock
   @id               int,
   @originalUnits    int,
   @decrement         int
as

Вы можете использовать хранимую процедуру вместо обычной автоматически созданной команды обновления, определив метод в строго типизированном dataContext. Даже если класс DataContext автоматически создается средством создания кода LINQ to SQL, эти методы все равно можно указать в разделяемом классе.

C#

public partial class Northwind : DataContext
{
   ...

   public void UpdateProduct(Product original, Product current) {
      // Execute the stored procedure for UnitsInStock update
      if (original.UnitsInStock != current.UnitsInStock) {
         int rowCount = this.ExecuteCommand(
            "exec UpdateProductStock " +
            "@id={0}, @originalUnits={1}, @decrement={2}",
            original.ProductID,
            original.UnitsInStock,
            (original.UnitsInStock - current.UnitsInStock)
         );
         if (rowCount < 1)
            throw new Exception("Error updating");
      }
      ...
   }
}

Visual Basic

Partial Public Class Northwind
         Inherits DataContext

   ...

   Public Sub UpdateProduct(original As Product, current As Product)
      ‘ Execute the stored procedure for UnitsInStock update
      If original.UnitsInStock <> current.UnitsInStock Then
         Dim rowCount As Integer = ExecuteCommand( _
            "exec UpdateProductStock " & _
            "@id={0}, @originalUnits={1}, @decrement={2}", _
            original.ProductID, _
            original.UnitsInStock, _
            (original.UnitsInStock - current.UnitsInStock) )
         If rowCount < 1 Then
            Throw New Exception(“Error updating”)
         End If
      End If
      ...
   End Sub
End Class

Сигнатура метода и универсального параметра указывает DataContext использовать этот метод вместо созданной инструкции update. Исходные и текущие параметры используются LINQ to SQL для передачи исходной и текущей копии объекта указанного типа. Два параметра доступны для обнаружения конфликтов оптимистичного параллелизма.

Примечание Если вы переопределяете логику обновления по умолчанию, вы несете ответственность за обнаружение конфликтов.

Хранимая процедура UpdateProductStock вызывается с помощью метода ExecuteCommand()DataContext. Он возвращает количество затронутых строк и имеет следующую сигнатуру:

C#

public int ExecuteCommand(string command, params object[] parameters);

Visual Basic

Public Function ExecuteCommand(command As String, _
         ParamArray parameters() As Object) As Integer

Массив объектов используется для передачи параметров, необходимых для выполнения команды.

Как и в случае с методом обновления, можно указать методы insert и delete. Методы вставки и удаления принимают только один параметр типа сущности для обновления. Например, методы вставки и удаления экземпляра Product можно указать следующим образом:

C#

public void InsertProduct(Product prod) { ... }
public void DeleteProudct(Product prod) { ... }

Visual Basic

Public Sub InsertProduct(prod As Product)  ... 
Public Sub DeleteProudct(prod As Product)  ... 

In-Depth классов сущностей

Использование атрибутов

Класс сущностей похож на любой обычный класс объектов, который можно определить как часть приложения, за исключением того, что он помечен специальными сведениями, которые связывают его с определенной таблицей базы данных. Эти заметки создаются в качестве настраиваемых атрибутов в объявлении класса. Атрибуты имеют смысл только при использовании класса в сочетании с LINQ to SQL. Они аналогичны атрибутам сериализации XML в платформа .NET Framework. Эти атрибуты data предоставляют LINQ to SQL достаточно информации для преобразования запросов к объектам в SQL-запросы к базе данных, а изменения объектов — в команды вставки, обновления и удаления SQL.

Кроме того, можно представить сведения о сопоставлении с помощью XML-файла сопоставления вместо атрибутов. Этот сценарий более подробно описан в разделе Внешнее сопоставление.

Атрибут Database

Атрибут Database используется для указания имени базы данных по умолчанию, если оно не предоставлено соединением. Атрибуты базы данных можно применять к строго типизированным объявлениям DataContext. Этот атрибут является необязательным.

Атрибут Database

Свойство Тип Описание
Имя Строка Указывает имя базы данных. Сведения используются только в том случае, если само подключение не указывает имя базы данных. Если этот атрибут Database не существует в объявлении контекста и он не указан соединением, то предполагается, что база данных имеет то же имя, что и класс контекста.

C#

[Database(Name="Database#5")]
public class Database5 : DataContext {
   ...
}

Visual Basic

<Database(Name:="Database#5")> _
Public Class Database5 
               Inherits DataContext
   ...
End Class

Атрибут таблицы

Атрибут Table используется для обозначения класса в качестве класса сущностей, связанного с таблицей базы данных. Классы с атрибутом Table будут обрабатываться специально LINQ to SQL.

Атрибут таблицы

Свойство Тип Описание
Имя Строка Указывает имя таблицы. Если эти сведения не указаны, предполагается, что таблица имеет то же имя, что и класс сущностей.

C#

[Table(Name="Customers")]
public class Customer {
   ...
}

Visual Basic

<Table(Name:="Customers")> _
Public Class Customer 
   ...
End Class

Атрибут столбца

Атрибут Column используется для обозначения члена класса сущностей, представляющего столбец в таблице базы данных. Его можно применить к любому полю или свойству, общедоступному, частному или внутреннему. Только элементы, определенные как столбцы, сохраняются, когда LINQ to SQL сохраняет изменения в базе данных.

Атрибут столбца

Свойство Тип Описание
Имя Строка Имя столбца в таблице или представлении. Если значение не указано, предполагается, что столбец имеет то же имя, что и член класса.
Память Строка Имя базового хранилища. Если он указан, он сообщает LINQ to SQL, как обойти метод доступа к открытому свойству для элемента данных и взаимодействовать с самим необработанным значением. Если LINQ to SQL не указано, получает и задает значение с помощью общедоступного метода доступа.
DBType Строка Тип столбца базы данных, указанный с помощью типов базы данных и модификаторов. Это будет точный текст, используемый для определения столбца в команде объявления таблицы T-SQL. Если значение не указано, тип столбца базы данных выводится из типа-члена. Конкретный тип базы данных необходим только в том случае, если предполагается, что для создания экземпляра базы данных будет использоваться метод CreateDatabase( ).
IsPrimaryKey Bool Если задано значение true, член класса представляет столбец, который является частью первичного ключа таблицы. Если в качестве идентификатора назначено несколько членов класса, первичный ключ считается составной частью связанных столбцов.
IsDbGenerated Логическое Определяет, что значение столбца элемента создается базой данных автоматически. Первичные ключи, обозначенные как IsDbGenerated=true , также должны иметь DBType с модификатором IDENTITY . IsDbGenerated члены синхронизируются сразу после вставки строки данных и становятся доступными после завершения SubmitChanges().
IsVersion Логическое Определяет тип столбца элемента как метку времени базы данных или номер версии. Номера версий увеличиваются, а столбцы меток времени обновляются базой данных при каждом обновлении связанной строки. Элементы с IsVersion=true синхронизируются сразу после обновления строки данных. Новые значения видны после завершения SubmitChanges().
UpdateCheck UpdateCheck Определяет, как LINQ to SQL реализует обнаружение конфликтов оптимистического параллелизма. Если ни один элемент не назначен как IsVersion=true , обнаружение выполняется путем сравнения исходных значений элементов с текущим состоянием базы данных. Вы можете управлять тем, какие члены LINQ to SQL использовать во время обнаружения конфликтов, присвоив каждому члену значение перечисления UpdateCheck.
  • Всегда: всегда используйте этот столбец для обнаружения конфликтов
  • Никогда: никогда не используйте этот столбец для обнаружения конфликтов
  • WhenChanged: используйте этот столбец только в том случае, если элемент был изменен приложением.
IsDiscriminator Логическое Определяет, содержит ли член класса значение дискриминатора для иерархии наследования.
Выражение Строка Не влияет на работу LINQ to SQL, но используется во время .CreateDatabase() как необработанное выражение SQL, представляющее выражение вычисляемого столбца.
CanBeNull Логическое Указывает, что значение может содержать значение NULL. Обычно это выводится из типа СРЕДЫ CLR элемента сущности. Используйте этот атрибут, чтобы указать, что строковое значение представлено в базе данных как столбец, не допускающий значения NULL.
AutoSync AutoSync Указывает, синхронизируется ли столбец автоматически из значения, созданного базой данных при командах вставки или обновления. Допустимые значения для этого тега: OnInsert, Always и Never.

Типичный класс сущностей будет использовать атрибуты Column для общедоступных свойств и хранить фактические значения в закрытых полях.

C#

private string _city;

[Column(Storage="_city", DBType="NVarChar(15)")]
public string City {
   get { ... }
   set { ... }
}

Visual Basic

Private _city As String

<Column(Storage:="_city", DBType:="NVarChar(15)")> _
public Property City As String
   Get
   set
End Property

DbType указывается только для того, чтобы метод CreateDatabase() смог создать таблицу с наиболее точным типом. В противном случае знание о том, что базовый столбец ограничен 15 символами, не используется.

Члены, представляющие первичный ключ типа базы данных, часто связываются с автоматически создаваемыми значениями.

C#

private string _orderId;

[Column(Storage="_orderId", IsPrimaryKey=true, IsDbGenerated = true,
   DBType="int NOT NULL IDENTITY")]
public string OrderId {
   get { ... }
   set { ... }
}

Visual Basic

Private _orderId As String

<Column(Storage:="_orderId", IsPrimaryKey:=true, _
           IsDbGenerated:= true, DBType:="int NOT NULL IDENTITY")> _
public Property OrderId As String
   Get
   Set
End Property

Если вы указали DBType, обязательно включите модификатор IDENTITY . LINQ to SQL не будет расширять пользовательский указанный тип DBType. Однако если тип DBType не указан, LINQ to SQL выявит, что при создании базы данных с помощью метода CreateDatabase() требуется модификатор IDENTITY.

Аналогичным образом, если свойство IsVersion имеет значение true, dbType должен указать правильные модификаторы для обозначения номера версии или столбца метки времени. Если dbType не указан, LINQ to SQL выдаст правильные модификаторы.

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

C#

private string _customerId;

[Column(Storage="_customerId", DBType="NCHAR(5) ")]
public string CustomerID {
   get { ... }
}

Visual Basic

Private _customerId As String

<Column(Storage:="_customerId", DBType:="NCHAR(5)")> _
Public Property CustomerID As String
   Get
End Property

Свойство CustomerID заказа можно сделать доступным только для чтения, не определяя метод доступа set. LINQ to SQL по-прежнему можно получить и задать базовое значение с помощью элемента хранилища.

Вы также можете сделать элемент полностью недоступным для остальной части приложения, разместив атрибут Column в частном элементе. Это позволяет классу сущностей содержать сведения, относящиеся к бизнес-логике класса, не предоставляя ее в целом. Несмотря на то, что частные члены являются частью переведенных данных, так как они являются частными, вы не можете ссылаться на них в запросе, интегрированном с языком.

По умолчанию все члены используются для обнаружения конфликтов оптимистического параллелизма. Вы можете управлять тем, используется ли определенный элемент, указав его значение UpdateCheck .

C#

[Column(Storage="_city", UpdateCheck=UpdateCheck.WhenChanged)]
public string City {
   get { ... }
   set { ... }
}

Visual Basic

<Column(Storage:="_city", UpdateCheck:=UpdateCheck.WhenChanged)> _
Public Property City As String
   Get
   Set
End Property

В следующей таблице показаны допустимые сопоставления между типами баз данных и соответствующими типами CLR. Используйте эту таблицу в качестве руководства при определении типа CLR, используемого для представления определенного столбца базы данных.

Допустимые сопоставления типа базы данных и соответствующего типа CLR

Тип базы данных Тип .NET CLR Комментарии
bit, tinyint, smallint, int, bigint Bye, Int16, Uint16, Int32, Uint32, Int64, Uint64 Возможные преобразования с потерями. Значения могут не округлить.
bit Логическое  
decimal, numeric, smallmoney, money Decimal Разница в масштабе может привести к преобразованию с потерями. Не может округлить.
real, float Single, Double Различия в точности.
char, varchar, text, nchar, nvarchar, ntext Строка Возможные различия языкового стандарта.
datetime, smalldatetime Дата и время Разная точность может привести к потере преобразования и проблемам с обходом.
UNIQUEIDENTIFIER Guid Различные правила сортировки. Сортировка может работать неправильно.
TIMESTAMP Byte[] (Byte() в Visual Basic), Binary Массив байтов рассматривается как скалярный тип. Пользователь отвечает за выделение достаточного хранилища при вызове конструктора. Он считается неизменяемым и не отслеживается на наличие изменений.
binary, varbinary Byte[] (Byte() в Visual Basic), Binary  

Атрибут связи

Атрибут Association используется для обозначения свойства, представляющего связь базы данных, например связь внешнего ключа с первичным ключом.

Атрибут связи

Свойство Тип Описание
Имя Строка Имя ассоциации. Это часто совпадает с именем ограничения внешнего ключа базы данных. Он используется, когда CreateDatabase() используется для создания экземпляра базы данных для создания соответствующего ограничения. Он также используется для различения нескольких связей в одном классе сущностей, ссылающихся на один и тот же целевой класс сущности. В этом случае свойства связи на сторонах связи (если они определены) должны иметь одинаковое имя.
Память Строка Имя базового элемента хранилища. Если он указан, он сообщает LINQ to SQL, как обойти метод доступа к открытому свойству для элемента данных и взаимодействовать с самим необработанным значением. Если не указано, LINQ to SQL получает и задает значение с помощью открытого метода доступа. Рекомендуется, чтобы все члены ассоциации были свойствами с определенными отдельными элементами хранилища.
ThisKey Строка Разделенный запятыми список имен одного или нескольких членов этого класса сущностей, представляющих ключевые значения на этой стороне связи. Если этот параметр не указан, предполагается, что элементы являются элементами, составляющими первичный ключ.
OtherKey Строка Разделенный запятыми список имен одного или нескольких членов класса целевой сущности, представляющих значения ключей на другой стороне связи. Если этот параметр не указан, предполагается, что члены являются элементами, составляющими первичный ключ другого класса сущностей.
IsUnique Логическое Значение true , если для внешнего ключа есть ограничение уникальности, указывающее на истинное отношение 1:1. Это свойство редко используется, так как отношениями 1:1 практически невозможно управлять в базе данных. В основном модели сущностей определяются с помощью связей 1:n, даже если разработчики приложений обрабатывают их как 1:1.
IsForeignKey Логическое Значение true , если целевой тип "other" связи является родительским для исходного типа. При связях между внешним ключом и первичным ключами сторона, удерживаемая внешним ключом, является дочерним ключом, а сторона, владеющей первичным ключом, является родительским ключом.
DeleteRule Строка Используется для добавления поведения удаления к этой связи. Например, "CASCADE" добавит "ON DELETE CASCADE" к связи FK. Если задано значение NULL, поведение удаления не добавляется.

Свойства связи представляют одну ссылку на другой экземпляр класса сущности или коллекцию ссылок. Одноэлементные ссылки должны быть закодированы в классе сущностей с помощью типа значения EntityRef<T>(EntityRef (OfT) в Visual Basic), чтобы сохранить фактическую ссылку. Тип EntityRef — это то, как LINQ to SQL обеспечивает отложенную загрузку ссылок.

C#

class Order
{
   ...
   private EntityRef<Customer> _Customer;

   [Association(Name="FK_Orders_Customers", Storage="_Customer",
      ThisKey="CustomerID")]
   public Customer Customer {
      get { return this._Customer.Entity; }
      set { this._Customer.Entity = value;
            // Additional code to manage changes }
   }
}

Visual Basic

Class Order

   ...
   Private _customer As EntityRef(Of Customer)

   <Association(Name:="FK_Orders_Customers", _
            Storage:="_Customer", ThisKey:="CustomerID")> _
   public Property Customer() As Customer
      Get  
         Return _customer.Entity
      End Get   
   Set (value As Customer)
      _customer.Entity = value
      ‘ Additional code to manage changes
   End Set
End Class

Открытое свойство типируется как Customer, а не EntityRef<Customer>. Важно не предоставлять тип EntityRef как часть общедоступного API, так как ссылки на этот тип в запросе не будут преобразованы в SQL.

Аналогичным образом, свойство связи, представляющее коллекцию, должно использовать тип коллекции EntitySet<T> (EntitySet(OfT) в Visual Basic) для хранения связи.

C#

class Customer
{
   ...
   private EntitySet<Order> _Orders;

   [Association(Name="FK_Orders_Customers", Storage="_Orders",
      OtherKey="CustomerID")]
   public EntitySet<Order> Orders {
      get { return this._Orders; }
      set { this._Orders.Assign(value); }
   }
} 

Visual Basic

Class Customer

   ...
   Private _Orders As EntitySet(Of Order)

   <Association(Name:="FK_Orders_Customers", _
         Storage:="_Orders", OtherKey:="CustomerID")> _
   public Property Orders() As EntitySet(Of Order)
      Get
           Return _Orders
      End Get
   Set (value As EntitySet(Of Order))
      _Orders.Assign(value)
   End Property
End Class

Однако, так как EntitySet<T> (EntitySet(OfT) в Visual Basic является коллекцией, в качестве возвращаемого типа можно использовать EntitySet . Кроме того, можно скрыть истинный тип коллекции с помощью интерфейса ICollection<T> (ICollection(OfT) в Visual Basic).

C#

class Customer
{
   ...

   private EntitySet<Order> _Orders;

   [Association(Name="FK_Orders_Customers", Storage="_Orders",
      OtherKey="CustomerID")]
   public ICollection<Order> Orders {
      get { return this._Orders; }
      set { this._Orders.Assign(value); }
   }
}

Visual Basic

Class Customer

   ...
   Private _orders As EntitySet(Of Order)

   <Association(Name:="FK_Orders_Customers", _
         Storage:="_Orders", OtherKey:="CustomerID")> _
   public Property Orders() As ICollection (Of Order)
      Get
           Return _orders
      End Get
Set (value As ICollection (Of Order))
         _orders.Assign(value)
      End Property
End Class

Обязательно используйте метод Assign() в EntitySet , если вы предоставляете открытый метод задания для свойства . Это позволяет классу сущностей продолжать использовать один и тот же экземпляр коллекции, так как он уже может быть привязан к службе отслеживания изменений.

Атрибут ResultType

Этот атрибут задает тип элемента перечисляемой последовательности, которая может быть возвращена функцией, объявленной для возврата интерфейса IMultipleResults . Этот атрибут можно указать несколько раз.

Атрибут ResultType

Свойство Тип Описание
Тип Тип Тип возвращаемых результатов.

Атрибут StoredProcedure

Атрибут StoredProcedure используется для объявления того, что вызов метода, определенного в типе DataContext или Schema , преобразуется как вызов хранимой процедуры базы данных.

Атрибут StoredProcedure

Свойство Тип Описание
Имя Строка Имя хранимой процедуры в базе данных. Если не указано, то предполагается, что хранимая процедура имеет то же имя, что и метод.

Атрибут функции

Атрибут Function используется для объявления того, что вызов метода, определенного в DataContext или Schema , преобразуется как вызов определяемой пользователем скалярной или возвращающей табличное значение функции базы данных.

Атрибут функции

Свойство Тип Описание
Имя Строка Имя функции в базе данных. Если этот параметр не указан, предполагается, что функция имеет то же имя, что и метод.

Атрибут параметра

Атрибут Parameter используется для объявления сопоставления между методом и параметрами хранимой процедуры базы данных или определяемой пользователем функции.

Атрибут параметра

Свойство Тип Описание
Имя Строка Имя параметра в базе данных. Если параметр не указан, он выводится из имени параметра метода.
DBType Строка Тип параметра, указанный с помощью типов баз данных и модификаторов.

Атрибут InheritanceMapping

Атрибут InheritanceMapping используется для описания соответствия между определенными дискриминационными кодами и подтипом наследования. Все атрибуты InheritanceMapping , используемые для иерархии наследования, должны быть объявлены в корневом типе иерархии.

Атрибут InheritanceMapping

Propety Тип Описание
Код Объект Дискриминаторное значение кода.
Тип Тип Подтип Наследования. Это может быть любой неавстрактный тип в иерархии наследования, включая корневой тип.
IsDefault Логическое Определяет, является ли указанный подтип наследования типом по умолчанию, созданным при обнаружении LINQ to SQL дискриминационного кода, не определенного атрибутами InheritanceMapping. Один из атрибутов InheritanceMapping должен быть объявлен с помощью IsDefault как true.

Согласованность графов

Граф — это общий термин для структуры данных объектов, ссылающихся друг на друга по ссылкам. Иерархия (или дерево) — это вырожденная форма графа. Объектные модели, относящиеся к предметной области, часто описывают сеть ссылок, которые лучше всего описываются как граф объектов. Работоспособность графа объектов жизненно важна для стабильности приложения. Вот почему важно обеспечить согласованность ссылок в графе с бизнес-правилами и (или) ограничениями, определенными в базе данных.

LINQ to SQL автоматически не управляет согласованность ссылок на связи. Если отношения являются двунаправленными, изменение одной стороны связи должно автоматически обновить другую. Обратите внимание, что обычные объекты обычно ведут себя таким образом, поэтому маловероятно, что вы спроектировали бы объекты таким образом в противном случае.

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

C#

public class Customer() {
   this._Orders =
      new EntitySet<Order>(
         new Action<Order>(this.attach_Orders),
         new Action<Order>(this.detach_Orders));
);}

Visual Basic

Public Class Customer()
         _Orders = New EntitySet(Of Order)( _
              New Action(Of Order)(attach_Orders), _
                 New Action(Of Order)(detach_Orders))
      End Class
);}

Тип EntitySet<T> (EntitySet(OfT) в Visual Basic) имеет конструктор, который позволяет предоставить два делегата для использования в качестве обратных вызовов: первый — при добавлении элемента в коллекцию, второй — при его удалении. Как видно из примера, код, указанный для этих делегатов, может и должен быть написан для обновления свойства обратной связи. Таким образом свойство Customer экземпляра Order автоматически изменяется при добавлении заказа в коллекцию Orders клиента.

Реализация связи на другом конце не так проста. EntityRef<T> (EntityRef(OfT) в Visual Basic) — это тип значения, определенный для того, чтобы содержать как можно меньше дополнительных затрат на фактическую ссылку на объект. В ней нет места для пары делегатов. Вместо этого код, управляющий согласованностью графа для одноэлементных ссылок, должен быть внедрен в сами методы доступа свойств.

C#

[Association(Name="FK_Orders_Customers", Storage="_Customer",
   ThisKey="CustomerID")]
public Customer Customer {
   get {
      return this._Customer.Entity;
   }
   set {
      Customer v = this._Customer.Entity;
      if (v != value) {
         if (v != null) {
            this._Customer.Entity = null;
            v.Orders.Remove(this);
         }
         this._Customer.Entity = value;
         if (value != null) {
            value.Orders.Add(this);
         }
      }
   }
}

Visual Basic

<Association(Name:="FK_Orders_Customers", _
         Storage:="_Customer", ThisKey:="CustomerID")> _
Public Property Customer As Customer 
   Get
      Return _Customer.Entity
   End Get
   Set (value As Customer)
      Dim cust As Customer v = _customer.Entity
      if cust IsNot value Then
         If cust IsNot Nothing Then
            _Customer.Entity = Nothing
            cust.Orders.Remove(Me)
         End If

         _customer.Entity = value
         if value IsNot Nothing Then
            value.Orders.Add(Me)
         End If
      End If
   End Set
End Property

Взгляните на метод задания. При изменении свойства Customer экземпляр заказа сначала удаляется из текущей коллекции заказов клиента, а затем только позже добавляется в коллекцию нового клиента. Обратите внимание, что перед вызовом Remove() фактическая ссылка на сущность имеет значение NULL. Это делается, чтобы избежать рекурсии при вызове метода Remove( ). Помните, что EntitySet будет использовать делегаты обратного вызова, чтобы присвоить свойству Customer этого объекта значение NULL. То же самое происходит непосредственно перед вызовом Метода Add(). Фактическая ссылка на сущность обновляется до нового значения. Это снова сократит любую потенциальную рекурсию и, конечно, выполнит задачу сеттера в первую очередь.

Определение связи "один к одному" очень похоже на определение связи "один ко многим" со стороны одноэлементной ссылки. Вместо вызова Add() и Remove() назначается новый объект или значение NULL , чтобы разорвать связь.

Опять же, крайне важно, чтобы свойства связи поддерживали согласованность графа объектов. Если граф объектов в памяти не согласуется с данными базы данных, при вызове метода SubmitChanges создается исключение во время выполнения. Рассмотрите возможность использования средства создания кода для обеспечения согласованности.

Уведомления об изменениях

Объекты могут участвовать в процессе отслеживания изменений. Это не обязательно, но они могут значительно сократить объем накладных расходов, необходимых для отслеживания потенциальных изменений объектов. Вполне вероятно, что приложение получит гораздо больше объектов из запросов, чем в конечном итоге будет изменено. Без упреждающей помощи со стороны объектов служба отслеживания изменений ограничена тем, как она может реально отслеживать изменения.

Так как в среде выполнения нет истинной службы перехвата, формальное отслеживание фактически не выполняется. Вместо этого повторяющиеся копии объектов сохраняются при их первом извлечении. Позже при вызове SubmitChanges() эти копии используются для сравнения с предоставленными вами копиями. Если их значения отличаются, объект был изменен. Это означает, что для каждого объекта требуется две копии в памяти, даже если вы никогда не изменяете их.

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

C#

[Table(Name="Customers")]
public partial class Customer: INotifyPropertyChanging {

   public event PropertyChangingEventHandler PropertyChanging;

   private void OnPropertyChanging() {
      if (this.PropertyChanging != null) {
         this.PropertyChanging(this, emptyEventArgs);
      }
   }

   private string _CustomerID;

   [Column(Storage="_CustomerID", IsPrimaryKey=true)]
   public string CustomerID {
      get {
         return this._CustomerID;
      }
      set {
         if ((this._CustomerID != value)) {
            this.OnPropertyChanging("CustomerID");
            this._CustomerID = value;
         }
      }
   }
}

Visual Basic

<Table(Name:="Customers")> _
Partial Public Class Customer 
         Inherits INotifyPropertyChanging
Public Event PropertyChanging As PropertyChangingEventHandler _
        Implements INotifyPropertyChanging.PropertyChanging

   Private Sub OnPropertyChanging()
         RaiseEvent PropertyChanging(Me, emptyEventArgs)
   End Sub

   private _customerID As String 

   <Column(Storage:="_CustomerID", IsPrimaryKey:=True)>
   public Property CustomerID() As String
      Get
         Return_customerID
      End Get
      Set (value As Customer)
         If _customerID IsNot value Then
            OnPropertyChanging(“CustomerID”)
            _CustomerID = value
         End IF
      End Set
   End Function
End Class

Чтобы упростить отслеживание изменений, классы сущностей должны реализовывать интерфейс INotifyPropertyChanging . Для этого требуется только определить событие с именем PropertyChanging— служба отслеживания изменений затем регистрируется в событии, когда объекты поступают в его владение. Все, что вам нужно сделать, — это вызвать это событие непосредственно перед изменением значения свойства.

Не забудьте поместить ту же логику вызова событий в методы задания свойств связи. Для EntitySets создайте события в поставляемых делегатах.

C#

public Customer() {
   this._Orders =
      new EntitySet<Order>(
         delegate(Order entity) {
            this.OnPropertyChanging("Orders");
            entity.Customer = this;
         },
         delegate(Order entity) {
            this.onPropertyChanging("Orders");
            entity.Customer = null;
         }
      );
}

Visual Basic

Dim _orders As EntitySet(Of Order)
Public Sub New()
   _orders = New EntitySet(Of Order)( _
      AddressOf OrderAdding, AddressOf OrderRemoving)
End Sub

Sub OrderAdding(ByVal o As Order)
   OnPropertyChanging()
   o.Customer = Me
End Sub

Sub OrderRemoving(ByVal o As Order)
   OnPropertyChanging()
   o.Customer = Nothing
End Sub

Наследование

LINQ to SQL поддерживает сопоставление одной таблицы, при котором вся иерархия наследования хранится в одной таблице базы данных. Таблица содержит плоское объединение всех возможных столбцов данных для всей иерархии, и каждая строка содержит значения NULL в столбцах, которые не применимы к типу экземпляра, представленного строкой. Стратегия однотабличного сопоставления представляет собой простейшее представление наследования и обеспечивает высокую производительность для многих различных категорий запросов.

Сопоставление

Чтобы реализовать это сопоставление с помощью LINQ to SQL, необходимо указать следующие атрибуты и свойства атрибутов в корневом классе иерархии наследования:

  • Атрибут [Table] (<Table> в Visual Basic).
  • Атрибут [InheritanceMapping] (<InheritanceMapping> в Visual Basic) для каждого класса в структуре иерархии. Для неавстрактных классов этот атрибут должен определять свойство Code (значение, которое отображается в таблице базы данных в столбце Дискриминатор наследования, чтобы указать, к какому классу или подклассу принадлежит эта строка данных) и свойство Type (которое указывает, какой класс или подкласс обозначает значение ключа).
  • Свойство IsDefault в одном атрибуте [InheritanceMapping] (<InheritanceMapping> в Visual Basic). Это свойство служит для обозначения резервного сопоставления в случае, если дискриминаторное значение из таблицы базы данных не соответствует ни одному из значений Code в сопоставлениях наследования.
  • Свойство IsDiscriminator для атрибута [Column] (<Column> в Visual Basic), означающее, что это столбец, содержащий значение Code для сопоставления наследования.

Особые атрибуты или свойства подклассов не требуются. Обратите внимание, что подклассы не имеют атрибута [Table] (<Table> в Visual Basic).

В следующем примере данные, содержащиеся в подклассах Car и Truck , сопоставляются с отдельной таблицей базы данных Vehicle. (Чтобы упростить пример, в примере кода для сопоставления столбцов используются поля, а не свойства.)

C#

[Table]
[InheritanceMapping(Code = "C", Type = typeof(Car))]
[InheritanceMapping(Code = "T", Type = typeof(Truck))]
[InheritanceMapping(Code = "V", Type = typeof(Vehicle),
   IsDefault = true)]
public class Vehicle
{
   [Column(IsDiscriminator = true)]
   public string Key;
   [Column(IsPrimaryKey = true)]
   public string VIN;
   [Column]
   public string MfgPlant;
}
public class Car : Vehicle
{
   [Column]
   public int TrimCode;
   [Column]
   public string ModelName;
}

public class Truck : Vehicle
{
   [Column]
   public int Tonnage;
   [Column]
   public int Axles;
}

Visual Basic

<Table> _
<InheritanceMapping(Code:="C", Type:=Typeof(Car))> _
<InheritanceMapping(Code:="T", Type:=Typeof(Truck))> _
<InheritanceMapping(Code:="V", Type:=Typeof(Vehicle), _
              IsDefault:=true)> _
Public Class Vehicle

   <Column(IsDiscriminator:=True)> _
   Public Key As String
   <Column(IsPrimaryKey:=True)> _
   Public VIN As String
   <Column> _
   Public MfgPlant As String
End Class
Public Class Car
       Inherits Vehicle
   <Column> _
   Public TrimCode As Integer
   <Column> _
   Public ModelName As String
End Class

Public class Truck
       Inherits Vehicle 
   <Column> _
   public Tonnage As Integer
   <Column> _
   public Axles As Integer
End Class

Схема классов выглядит следующим образом:

Рис. 1. Схема классов транспортного средства

При просмотре итоговой схемы базы данных в Обозреватель сервера вы увидите, что все столбцы сопоставлены с одной таблицей, как показано ниже:

Рис. 2. Столбцы, сопоставленные с одной таблицей

Обратите внимание, что типы столбцов, представляющих поля в подтипах, должны быть допускающими значение NULL или должны иметь значение по умолчанию. Это необходимо для успешного выполнения команд insert.

Выполнение запроса

В следующем коде показано, как можно использовать производные типы в запросах:

C#

var q = db.Vehicle.Where(p => p is Truck);
//or
var q = db.Vehicle.OfType<Truck>();
//or
var q = db.Vehicle.Select(p => p as Truck).Where(p => p != null);
foreach (Truck p in q)
   Console.WriteLine(p.Axles);

Visual Basic

Dim trucks = From veh In db.Vehicle _ 
             Where TypeOf(veh) Is Truck

For Each truck In trucks
   Console.WriteLine(p.Axles) 
Next

Продвинутый уровень

Иерархию можно расширить далеко за пределами уже предоставленного простого примера.

Пример 1

Ниже приведена гораздо более глубокая иерархия и более сложный запрос:

C#

[Table]
[InheritanceMapping(Code = "V", Type = typeof(Vehicle), IsDefault = true)]
[InheritanceMapping(Code = "C", Type = typeof(Car))]
[InheritanceMapping(Code = "T", Type = typeof(Truck))]
[InheritanceMapping(Code = "S", Type = typeof(Semi))]
[InheritanceMapping(Code = "D", Type = typeof(DumpTruck))]
public class Truck: Vehicle { ... }

public class Semi: Truck { ... }

public class DumpTruck: Truck { ... }

...
// Get all trucks along with a flag indicating industrial application.

db.Vehicles.OfType<Truck>.Select(t => 
   new {Truck=t, IsIndustrial=t is Semi || t is DumpTruck }
);

Visual Basic

<Table> _
<InheritanceMapping(Code:="V", Type:=Typeof(Vehicle), IsDefault:=True)> _
<InheritanceMapping(Code:="C", Type:=Typeof(Car))> _
<InheritanceMapping(Code:="T", Type:=Typeof(Truck))> _
<InheritanceMapping(Code:="S", Type:=Typeof(Semi))> _
<InheritanceMapping(Code:="D", Type:=Typeof(DumpTruck))> _
Public Class Truck
       InheritsVehicle
Public Class Semi
       Inherits Truck

Public Class DumpTruck
       InheritsTruck 
...
' Get all trucks along with a flag indicating industrial application.
Dim trucks = From veh In db.Vehicle _ 
             Where Typeof(veh) Is Truck And _ 
             IsIndustrial = (Typeof(veh) Is Semi _ 
             Or Typeof(veh) Is DumpTruck)

Пример 2

Следующая иерархия включает интерфейсы:

C#

[Table]
[InheritanceMapping(Code = "V", Type = typeof(Vehicle),
   IsDefault = true)]
[InheritanceMapping(Code = "C", Type = typeof(Car))]
[InheritanceMapping(Code = "T", Type = typeof(Truck))]
[InheritanceMapping(Code = "S", Type = typeof(Semi))]
[InheritanceMapping(Code = "H", Type = typeof(Helicopter))]

public class Truck: Vehicle
public class Semi: Truck, IRentableVehicle
public class Helicopter: Vehicle, IRentableVehicle

Visual Basic

<Table> _
<InheritanceMapping(Code:="V", Type:=TypeOf(Vehicle),
   IsDefault:=True) > _
<InheritanceMapping(Code:="C", Type:=TypeOf(Car)) > _
<InheritanceMapping(Code:="T", Type:=TypeOf(Truck)) > _
<InheritanceMapping(Code:="S", Type:=TypeOf(Semi)) > _
<InheritanceMapping(Code:="H", Type:=TypeOf(Helicopter)) > _
Public Class Truck
       Inherits Vehicle
Public Class Semi
       InheritsTruck, IRentableVehicle
Public Class Helicopter
       InheritsVehicle, IRentableVehicle

Возможные запросы:

C#

// Get commercial vehicles ordered by cost to rent.
db.Vehicles.OfType<IRentableVehicle>.OrderBy(cv => cv.RentalRate);

// Get all non-rentable vehicles
db.Vehicles.Where(v => !(v is IRentableVehicle));

Visual Basic

' Get commercial vehicles ordered by cost to rent.
Dim rentableVehicles = From veh In _ 
                       db.Vehicles.OfType(Of IRentableVehicle).OrderBy( _ 
                       Function(cv) cv.RentalRate)

' Get all non-rentable vehicles
Dim unrentableVehicles = From veh In _ 
                         db.Vehicles.OfType(Of Vehicle).Where( _ 
                         Function(uv) Not (TypeOf(uv) Is IRentableVehicle))

Дополнительные разделы

Создание баз данных

Так как классы сущностей имеют атрибуты, описывающие структуру таблиц и столбцов реляционной базы данных, эти сведения можно использовать для создания новых экземпляров базы данных. Можно вызвать метод CreateDatabase() в DataContext, чтобы LINQ to SQL создать новый экземпляр базы данных со структурой, определенной вашими объектами. Это может потребоваться по многим причинам: вы создаете приложение, которое автоматически устанавливает себя в клиентской системе, или клиентское приложение, которому требуется локальная база данных для сохранения состояния автономного режима. Для этих сценариев идеально подходит CreateDatabase(), особенно если доступен известный поставщик данных, например SQL Server Express 2005.

Однако атрибуты данных могут не кодировать все данные о существующей структуре базы данных. Содержимое определяемых пользователем функций, хранимых процедур, триггеров и ограничений проверка не представлено атрибутами. Функция CreateDatabase() создает только реплика базы данных, используя известные ей сведения, то есть структуру базы данных и типы столбцов в каждой таблице. Тем не менее, для различных баз данных этого достаточно.

Ниже приведен пример создания базы данных с именем MyDVDs.mdf:

C#

[Table(Name="DVDTable")]
public class DVD
{
   [Column(Id = true)]
   public string Title;
   [Column]
   public string Rating;
}

public class MyDVDs : DataContext
{
   public Table<DVD> DVDs;

   public MyDVDs(string connection) : base(connection) {}
}

Visual Basic

<Table(Name:="DVDTable")> _
Public Class DVD

   <Column(Id:=True)> _
   public Title As String
   <Column> _
   Public Rating As String
End Class

Public Class MyDVDs  
         Inherits DataContext

   Public DVDs As Table(Of DVD)

   Public Sub New(connection As String) 
End Class

Объектную модель можно использовать для создания базы данных с помощью SQL Server Express 2005 следующим образом:

C#

MyDVDs db = new MyDVDs("c:\\mydvds.mdf");
db.CreateDatabase();

Visual Basic

Dim db As MyDVDs = new MyDVDs("c:\mydvds.mdf")
db.CreateDatabase()

LINQ to SQL также предоставляет API для удаления существующей базы данных перед созданием новой. Приведенный выше код создания базы данных можно изменить, чтобы сначала проверка для существующей версии базы данных с помощью DatabaseExists(), а затем удалить его с помощью DeleteDatabase().

C#

MyDVDs db = new MyDVDs("c:\\mydvds.mdf");

if (db.DatabaseExists()) {
   Console.WriteLine("Deleting old database...");
   db.DeleteDatabase();
}

db.CreateDatabase();

Visual Basic

Dim db As MyDVDs = New MyDVDs("c:\mydvds.mdf")

If (db.DatabaseExists()) Then
   Console.WriteLine("Deleting old database...")
   db.DeleteDatabase()
End If

db.CreateDatabase()

После вызова CreateDatabase() новая база данных может принимать запросы и команды, такие как SubmitChanges(), чтобы добавить объекты в MDF-файл.

Кроме того, можно использовать CreateDatabase() с номером SKU, отличном от SQL Server Express, используя MDF-файл или только имя каталога. Все зависит от того, что вы используете для строки подключения. Сведения в строке подключения используются для определения базы данных, которая будет существовать, а не обязательно той, которая уже существует. LINQ to SQL вылавливает соответствующие биты информации и использует их для определения базы данных для создания и на каком сервере. Конечно, для этого потребуются права администратора базы данных или эквивалентные права на сервере.

Взаимодействие с ADO.NET

LINQ to SQL входит в семейство ADO.NET технологий. Он основан на службах, предоставляемых моделью поставщика ADO.NET, поэтому можно сочетать LINQ to SQL код с существующими ADO.NET приложениями.

При создании LINQ to SQL DataContext вы можете предоставить ему существующее подключение ADO.NET. Все операции с DataContext, включая запросы, будут использовать предоставленное подключение. Если подключение уже открыто LINQ to SQL будет соблюдать ваши полномочия по подключению и оставить его как есть, когда завершите работу с ним. Обычно LINQ to SQL закрывает подключение сразу после завершения операции, если транзакция не находится в область.

C#

SqlConnection con = new SqlConnection( ... );
con.Open(); 
...

// DataContext takes a connection
Northwind db = new Northwind(con);
...

var q =
   from c in db.Customers
   where c.City == "London"
   select c;

Visual Basic

Dim con As SqlConnection = New SqlConnection( ... )
con.Open()
...

' DataContext takes a connection
Dim db As Northwind = new Northwind(con)
...

Dim q = From c In db.Customers _
        Where c.City = "London" _
        Select c

Вы всегда можете получить доступ к подключению, используемому dataContext , с помощью свойства Connection и закрыть его самостоятельно.

C#

db.Connection.Close(); 

Visual Basic

db.Connection.Close()

Вы также можете предоставить DataContext с собственной транзакцией базы данных, если приложение уже инициировало ее и вы хотите, чтобы DataContext воспроизводился вместе с ней.

C#

IDbTransaction = con.BeginTransaction();
...

db.Transaction = myTransaction;
db.SubmitChanges();
db.Transaction = null;

Visual Basic

Dim db As IDbTransaction = con.BeginTransaction()
...

db.Transaction = myTransaction
db.SubmitChanges()
db.Transaction = Nothing

Всякий раз, когда задана транзакция , DataContext будет использовать ее при выполнении запроса или при выполнении команды. Не забудьте присвоить свойству значение NULL по завершении.

Однако предпочтительным способом выполнения транзакций с платформа .NET Framework является использование объекта TransactionScope. Это позволяет выполнять распределенные транзакции, которые работают между базами данных и другими диспетчерами ресурсов памяти. Идея заключается в том, что области транзакций начинаются дешево, только повышая их до полного уровня при распределенной транзакции, когда они фактически ссылаются на несколько баз данных или несколько подключений в область транзакции.

C#

using(TransactionScope ts = new TransactionScope()) {
   db.SubmitChanges();
   ts.Complete();
}

Visual Basic

Using ts As TransactionScope= New TransactionScope()
   db.SubmitChanges()
   ts.Complete()
End Using

Выполнение инструкций SQL напрямую

Подключения и транзакции — это не единственный способ взаимодействия с ADO.NET. Вы можете обнаружить, что в некоторых случаях средства запроса или отправки изменений DataContext недостаточно для специализированной задачи, которую вы можете выполнить. В таких случаях можно использовать DataContext для выполнения необработанных команд SQL непосредственно в базе данных.

Метод ExecuteQuery() позволяет выполнить необработанный SQL-запрос и преобразовать результат запроса непосредственно в объекты. Например, если предположить, что данные для класса Customer распределены по двум таблицам customer1 и customer2, следующий запрос возвращает последовательность объектов Customer .

C#

IEnumerable<Customer> results = db.ExecuteQuery<Customer>(
   @"select c1.custid as CustomerID, c2.custName as ContactName
      from customer1 as c1, customer2 as c2
      where c1.custid = c2.custid"
);

Visual Basic

Dim results As IEnumerable(Of Customer) = _
          db.ExecuteQuery(Of Customer)( _
          "select c1.custid as CustomerID, " & _
          "c2.custName as ContactName " & _
          "from customer1 as c1, customer2 as c2 "& _
          "where c1.custid = c2.custid" )

Если имена столбцов в табличных результатах соответствуют свойствам столбцов класса сущностей, LINQ to SQL материализует объекты из любого SQL-запроса.

Метод ExecuteQuery() также разрешает параметры. В следующем коде выполняется параметризованный запрос:

C#

IEnumerable<Customer> results = db.ExecuteQuery<Customer>(
   "select contactname from customers where city = {0}",
   "London"
);

Visual Basic

Dim results As IEnumerable(Of Customer) = _
          db.ExecuteQuery(Of Customer)( _
   "select contactname from customers where city = {0}", _
   "London" )

Параметры выражаются в тексте запроса с помощью той же фигурной нотации, что и Console.WriteLine() и String.Format(). На самом деле String.Format() вызывается в предоставленной строке запроса, заменяя фигурные параметры созданными именами параметров, такими как p0, @p1 ..., p(n).

Изменение разрешения конфликтов

Описание

Конфликт изменений возникает, когда клиент пытается отправить изменения в объект, и одно или несколько значений, используемых в проверка обновления, были обновлены в базе данных с момента последнего их чтения клиентом.

Примечание В оптимистических проверках параллелизма участвуют только члены, сопоставленные как UpdateCheck.Always или UpdateCheck.WhenChanged . Для членов с пометкой UpdateCheck.Never проверка не выполняется.

Разрешение этого конфликта включает в себя обнаружение элементов объекта в конфликте, а затем решение о том, что с ним делать. Обратите внимание, что оптимистичный параллелизм может быть не лучшей стратегией в конкретной ситуации. Иногда вполне разумно "пусть последнее обновление выиграет".

Обнаружение, создание отчетов и разрешение конфликтов в LINQ to SQL

Разрешение конфликтов — это процесс обновления конфликтующего элемента путем повторного запроса к базе данных и выверки любых различий. При обновлении объекта средство отслеживания изменений содержит старые исходные значения и новые значения базы данных. LINQ to SQL затем определяет, находится ли объект в конфликте. Если это так, LINQ to SQL определяет, какие члены участвуют. Если новое значение базы данных для элемента отличается от старого исходного (который использовался для обновления проверка, который завершился сбоем), это конфликт. Все конфликты членов добавляются в список конфликтов.

Например, в следующем сценарии User1 начинает подготовку обновления, запрашивая в базе данных строку. Перед отправкой изменений User1 пользователь User2 изменил базу данных. Сбой отправки User1, так как значения, ожидаемые для столбцов B и C, изменились.

Конфликт обновления базы данных

Пользователь Col A Столбец Б Col C
Исходное состояние Алексеи Мария Sales
Пользователь 1 Алексей   Marketing
Пользователь 2   Инна Служба

В LINQ to SQL объекты, которые не обновляются из-за конфликтов оптимистичного параллелизма, вызывают исключение (ChangeConflictException). Вы можете указать, следует ли создавать исключение при первом сбое или следует ли пытаться выполнять все обновления с любыми сбоями, накапливаемых и сообщаемых в исключении.

// [C#]
db.SubmitChanges(ConflictMode.FailOnFirstConflict);
db.SubmitChanges(ConflictMode.ContinueOnConflict);
' [Visual Basic]
db.SubmitChanges(ConflictMode.FailOnFirstConflict)
db.SubmitChanges(ConflictMode.ContinueOnConflict)

При возникновении исключение предоставляет доступ к коллекции ObjectChangeConflict . Доступны сведения о каждом конфликте (сопоставленном с одной неудачной попыткой обновления), включая доступ к списку MemberConflicts . Каждый конфликт члена сопоставляется с одним членом в обновлении, которое привело к сбою проверки блокировки.

Обработка конфликтов

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

Здесь User1 выбирает слияние значений базы данных с текущими клиентскими значениями, чтобы значения базы данных были перезаписаны только в том случае, если текущий набор изменений также изменил это значение. (См. пример 1 далее в этом разделе.)

В приведенном выше сценарии после разрешения конфликта результат в базе данных выглядит следующим образом:

KeepChanges

  Col A Столбец Б Col C
KeepChanges Альфред (пользователь 1) Мэри (пользователь 2) Маркетинг (пользователь 1)
  • Col A: Появится изменение Пользователя1 (Альфред).
  • Col B: Появится изменение Пользователя 2 (Мэри). Это значение было объединено, так как пользователь User1 не изменил его.
  • Col C: Появится изменение Пользователя1 (маркетинг). Изменение User2 (служба) не объединяется, так как пользователь User1 также изменил этот элемент.

Ниже Пользователь1 выбирает перезапись всех значений базы данных текущими значениями. (См. пример 2 далее в этом разделе.)

После обновления будут отправлены изменения User1. Результат в базе данных выглядит следующим образом:

KeepCurrentValues

  Col A Столбец Б Col C
KeepCurrentValues Альфред (пользователь 1) Мария (оригинал) Маркетинг (пользователь 1)
  • Col A: Появится изменение Пользователя1 (Альфред).
  • Col B: Оригинальная Мария остается; Изменение User2 будет отменено.
  • Col C: Появится изменение Пользователя1 (маркетинг). Изменение User2 (служба) будет отменено.

В следующем сценарии User1 разрешает перезаписывать текущие значения базы данных в клиенте. (См. пример 3 далее в этом разделе.)

В приведенном выше сценарии после разрешения конфликта результат в базе данных выглядит следующим образом:

OverwriteCurrentValues

  Col A Столбец Б Col C
OverwriteCurrentValues Альфредс (оригинал) Мэри (пользователь 2) Служба (пользователь 2)
  • Col A: исходное значение (Alfreds) остается; Значение User1 (Alfred) отбрасывается.
  • Col B: появится изменение Пользователя 2 (Мэри).
  • Col C: появится изменение пользователя 2 (служба). Изменение Пользователя1 (маркетинг) будет отменено.

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

Примеры

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

Пример 1

В этом примере конфликты разрешаются "автоматически". То есть значения базы данных объединяются со значениями текущего клиента, если клиент также не изменил это значение (KeepChanges). Проверка или настраиваемая обработка конфликтов отдельных членов не выполняется.

C#

try {
   context.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e) {
   //automerge database values into current for members
   //that client has not modified
   context.ChangeConflicts.Resolve(RefreshMode.KeepChanges);
}
//submit succeeds on second try
context.SubmitChanges(ConflictMode.FailOnFirstConflict);

Visual Basic

Try 
   context.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
   ' automerge database values into current for members
   ' that client has not modified   context.ChangeConflicts.Resolve(RefreshMode.KeepChanges)
End Try
' submit succeeds on second try
context.SubmitChanges(ConflictMode.FailOnFirstConflict)

Пример 2

В этом примере конфликты разрешаются снова без какой-либо пользовательской обработки. Но на этот раз значения базы данных не объединяются с текущими клиентскими значениями.

C#

try {
   context.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e) {
   foreach (ObjectChangeConflict cc in context.ChangeConflicts) {
      //No database values are automerged into current
      cc.Resolve(RefreshMode.KeepCurrentValues);
   }
}

Visual Basic

Try 
   context.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
   For Each cc As ObjectChangeConflict In context.ChangeConflicts
      ‘No database values are automerged into current
      cc.Resolve(RefreshMode.KeepCurrentValues)
   Next
End Try

Пример 3

Здесь опять же, пользовательская обработка не выполняется. Но в этом случае все клиентские значения обновляются с учетом текущих значений базы данных.

C#

try {
   context.SubmitChanges(ConflictMode.ContinueOnConflict); 
}
catch (ChangeConflictException e) {
   foreach (ObjectChangeConflict cc in context.ChangeConflicts) {
      //No database values are automerged into current
      cc.Resolve(RefreshMode.OverwriteCurrentValues);
   }
}

Visual Basic

Try 
   context.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
   For Each cc As ObjectChangeConflict In context.ChangeConflicts
      ' No database values are automerged into current
      cc.Resolve(RefreshMode. OverwriteCurrentValues)
   Next
End Try

Пример 4

В этом примере показан способ доступа к информации о конфликтующей сущности.

C#

try {
   user1.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e) {
   Console.WriteLine("Optimistic concurrency error");
   Console.ReadLine();
   foreach (ObjectChangeConflict cc in user1.ChangeConflicts) {
      ITable table = cc.Table;
      Customers entityInConflict = (Customers)cc.Object;
      Console.WriteLine("Table name: {0}", table.Name);
      Console.Write("Customer ID: ");
      Console.WriteLine(entityInConflict.CustomerID);
   }
}

Visual Basic

Try 
   context.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
   Console.WriteLine("Optimistic concurrency error")
   Console.ReadLine()
   For Each cc As ObjectChangeConflict In context.ChangeConflicts
      Dim table As ITable = cc.Table
      Dim entityInConflict As Customers = CType(cc.Object, Customers)
      Console.WriteLine("Table name: {0}", table.Name)
      Console.Write("Customer ID: ")
      Console.WriteLine(entityInConflict.CustomerID)
   Next
End Try

Пример 5

В этом примере добавляется цикл по отдельным членам. Здесь можно предоставить пользовательскую обработку любого элемента.

Примечание Добавление с помощью System.Reflection; для предоставления MemberInfo.

C#

try {
   user1.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e) {
   Console.WriteLine("Optimistic concurrency error");
   Console.ReadLine();
   foreach (ObjectChangeConflict cc in user1.ChangeConflicts) {
      ITable table = cc.Table;
      Customers entityInConflict = (Customers)cc.Object;
      Console.WriteLine("Table name: {0}", table.Name);
      Console.Write("Customer ID: ");
      Console.WriteLine(entityInConflict.CustomerID);
      foreach (MemberChangeConflict mc in         cc.MemberConflicts) {
         object currVal = mc.CurrentValue;
         object origVal = mc.OriginalValue;
         object databaseVal = mc.DatabaseValue;
         MemberInfo mi = mc. Member;
         Console.WriteLine("Member: {0}", mi.Name);
         Console.WriteLine("current value: {0}", currVal);
         Console.WriteLine("original value: {0}", origVal);
         Console.WriteLine("database value: {0}", databaseVal);
         Console.ReadLine();
      }
   }
}

Visual Basic

Try 
   user1.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch e As ChangeConflictException
   Console.WriteLine("Optimistic concurrency error")
   Console.ReadLine()
   For Each cc As ObjectChangeConflict In context.ChangeConflicts
      Dim table As ITable = cc.Table
      Dim entityInConflict As Customers = CType(cc.Object, Customers)
      Console.WriteLine("Table name: {0}", table.Name)
      Console.Write("Customer ID: ")
      Console.WriteLine(entityInConflict.CustomerID)
   For Each mc As MemberChangeConflict In   cc.MemberConflicts
         Dim currVal As Object = mc.CurrentValue
         Dim origVal As Object = mc.OriginalValue
         Dim databaseVal As Object = mc.DatabaseValue
         Dim mi As MemberInfo = mc.Member
         Console.WriteLine("Member: {0}", mi.Name)
         Console.WriteLine("current value: {0}", currVal)
         Console.WriteLine("original value: {0}", origVal)
         Console.WriteLine("database value: {0}", databaseVal)
         Console.ReadLine()
      Next

   Next
End Try

Вызов хранимых процедур

LINQ to SQL поддерживает хранимые процедуры и определяемые пользователем функции. LINQ to SQL сопоставляет эти определяемые базой данных абстракции с созданными кодом клиентскими объектами, чтобы получить к ним строго типизированный доступ из клиентского кода. Эти методы можно легко обнаружить с помощью Технологии IntelliSense, а сигнатуры методов максимально похожи на сигнатуры процедур и функций, определенных в базе данных. Результирующий набор, возвращаемый вызовом сопоставленной процедуры, является строго типизированной коллекцией. LINQ to SQL может автоматически создавать сопоставленные методы, но также поддерживает сопоставление вручную в ситуациях, когда вы решили не использовать создание кода.

LINQ to SQL сопоставляет хранимые процедуры и функции с методами с помощью атрибутов. Атрибуты StoredProcedure, Parameter и Function поддерживают свойство Name, а атрибут Parameter также поддерживает свойство DBType. Вот два примера.

C#

   [StoredProcedure()]
   public IEnumerable<CustOrderHistResult> CustOrderHist(
      [Parameter(Name="CustomerID", DBType="NChar(5)")] string customerID) {

      IExecuteResult result = this.ExecuteMethodCall(this, 
         ((MethodInfo)(MethodInfo.GetCurrentMethod())), customerID);

      return ((IEnumerable<CustOrderHistResult>)(result.ReturnValue));
   }

[Function(Name="[dbo].[ConvertTemp]")]
public string ConvertTemp(string string) { ... }

Visual Basic

<StoredProcedure()> _
   Public Function CustOrderHist( _
         <Parameter(Name:="CustomerID", DBType:="NChar(5)")> _
         customerID As String) As IEnumerable(Of CustOrderHistResult)

         Dim result As IExecuteResult = ExecuteMethodCall(Me, _
                 CType(MethodInfo.GetCurrentMethod(), MethodInfo), customerID)

         Return CType(result.ReturnValue, IEnumerable(Of CustOrderHistResult))
   End Function

<Function(Name:="[dbo].[ConvertTemp]")> _
Public Function ConvertTemp(str As String) As String

В следующих примерах показаны сопоставления для различных типов хранимых процедур.

Пример 1

Следующая хранимая процедура принимает один входной параметр и возвращает целое число:

CREATE PROCEDURE GetCustomerOrderCount(@CustomerID nchar(5))
AS
Declare @count int
SELECT @count = COUNT(*) FROM ORDERS WHERE CustomerID = @CustomerID
RETURN @count

Сопоставленный метод выглядит следующим образом:

C#

[StoredProcedure(Name = "GetCustomerOrderCount")]
public int GetCustomerOrderCount(
      [Parameter(Name = "CustomerID")] string customerID) {
         IExecuteResult result = this.ExecuteMethodCall(this, 
            ((MethodInfo)(MethodInfo.GetCurrentMethod())), customerID); 
          return (int) result.ReturnValue;
}

Visual Basic

<StoredProcedure (Name:="GetCustomerOrderCount")> _
public Function GetCustomerOrderCount( _
      <Parameter(Name:= "CustomerID")> customerID As String) As Integer
         Dim result As IExecuteResult = ExecuteMethodCall(Me, _
               CType(MethodInfo.GetCurrentMethod(), MethodInfo), customerID)
          return CInt(result.ReturnValue)
End Function

Пример 2

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

CREATE PROCEDURE VariableResultShapes(@shape int)
AS
if(@shape = 1)
   select CustomerID, ContactTitle, CompanyName from customers
else if(@shape = 2)
   select OrderID, ShipName from orders

Сопоставленный метод выглядит следующим образом:

C#

      [StoredProcedure(Name = "VariableResultShapes")]
      [ResultType(typeof(Customer))]
      [ResultType(typeof(Order))]
      public IMultipleResults VariableResultShapes(System.Nullable<int> shape) {
         IExecuteResult result = this.ExecuteMethodCallWithMultipleResults(this,
            ((MethodInfo)(MethodInfo.GetCurrentMethod())), shape);
         return (IMultipleResults) result.ReturnValue;
      }

Visual Basic

<StoredProcedure(Name:= "VariableResultShapes")> _
      <ResultType(typeof(Customer))> _
      <ResultType(typeof(Order))> _
   public VariableResultShapes(shape As Integer?) As IMultipleResults
      Dim result As IExecuteResult =
                ExecuteMethodCallWithMultipleResults(Me, _
               CType(MethodInfo.GetCurrentMethod(), MethodInfo), shape)
         return CType(result.ReturnValue, IMultipleResults)
      End Function

Эту хранимую процедуру можно использовать следующим образом:

C#

      IMultipleResults result = db.VariableResultShapes(1);
      foreach (Customer c in result.GetResult<Customer>()) {
         Console.WriteLine(c.CompanyName);
      }

      result = db.VariableResultShapes(2);
      foreach (Order o in result.GetResult<Order>()) {
         Console.WriteLine(o.OrderID);
      }           

Visual Basic

Dim result As IMultipleResults = db.VariableResultShapes(1)
      For Each c As Customer In result.GetResult(Of Customer)()
         Console.WriteLine(c.CompanyName)
      Next

      result = db.VariableResultShapes(2);
      For Each o As Order In result.GetResult(Of Order)()
         Console.WriteLine(o.OrderID)
      Next 
         
      }           

Здесь необходимо использовать шаблон GetResult , чтобы получить перечислитель правильного типа на основе ваших знаний о хранимой процедуре. LINQ to SQL может создавать все возможные типы проекций, но не может узнать, в каком порядке они будут возвращены. Единственный способ узнать, какие типы проекций соответствуют сопоставленному методу, — использовать созданные комментарии к коду в методах.

Пример 3

Ниже приведен T-SQL хранимой процедуры, которая возвращает несколько результирующих фигур последовательно:

CREATE PROCEDURE MultipleResultTypesSequentially
AS
select * from products
select * from customers

LINQ to SQL сопоставить эту процедуру так же, как в примере 2 выше. Однако в этом случае есть два последовательных результальных наборов.

C#

[StoredProcedure(Name="MultipleResultTypesSequentially")]      
[ResultType(typeof(Product))]
[ResultType(typeof(Customer))]
public IMultipleResults MultipleResultTypesSequentially() {
   return ((IMultipleResults)(
      this.ExecuteMethodCallWithMultipleResults (this, 
         ((MethodInfo)(MethodInfo.GetCurrentMethod()))).ReturnValue
      )
   );
}

Visual Basic

<StoredProcedure(Name:="MultipleResultTypesSequentially")> _
<ResultType(typeof(Customer))> _
<ResultType(typeof(Order))> _
public Function MultipleResultTypesSequentially() As IMultipleResults
   Return CType( ExecuteMethodCallWithMultipleResults (Me, _
         CType(MethodInfo.GetCurrentMethod(), MethodInfo)), _
         IMultipleResults).ReturnValue
      
End Function

Эту хранимую процедуру можно использовать следующим образом:

C#

      IMultipleResults sprocResults = db.MultipleResultTypesSequentially();

      //first read products
      foreach (Product p in sprocResults.GetResult<Product>()) {
         Console.WriteLine(p.ProductID);
      }

      //next read customers
      foreach (Customer c in sprocResults.GetResult<Customer>()){
         Console.WriteLine(c.CustomerID);
      }

Visual Basic

Dim sprocResults As IMultipleResults = db.MultipleResultTypesSequentially()

   ' first read products
   For Each P As Product In sprocResults.GetResult(Of Product)()
      Console.WriteLine(p.ProductID)
   Next

   ' next read customers
   For Each c As Customer c In sprocResults.GetResult(Of Customer)()
      Console.WriteLine(c.CustomerID) 
   Next

Пример 4

LINQ to SQL сопоставляет out параметры с ссылочными параметрами (ссылка ключевое слово), а для типов значений объявляет параметр как допускающий значение NULL (например, int?). Процедура в следующем примере принимает один входной параметр и возвращает out параметр .

CREATE PROCEDURE GetCustomerCompanyName(
   @customerID nchar(5),
   @companyName nvarchar(40) output
   )
AS
SELECT @companyName = CompanyName FROM Customers
WHERE CustomerID=@CustomerID

Сопоставленный метод выглядит следующим образом:

C#

      [StoredProcedure(Name = "GetCustomerCompanyName")]
      public int GetCustomerCompanyName(
         string customerID, ref string companyName) {

         IExecuteResult result =
            this.ExecuteMethodCall(this,
               ((MethodInfo)(MethodInfo.GetCurrentMethod())),
               customerID, companyName);

         companyName = (string)result.GetParameterValue(1);
         return (int)result.ReturnValue;
      }

Visual Basic

   <StoredProcedure(Name:="GetCustomerCompanyName")> _
      Public Function GetCustomerCompanyName( _
               customerID As String, ByRef companyName As String) As Integer

      Dim result As IExecuteResult = ExecuteMethodCall(Me, _
               CType(MethodInfo.GetCurrentMethod(), MethodInfo), customerID, _
               companyName)

         companyName = CStr(result.GetParameterValue(1))
         return CInt(result.ReturnValue)
      End Function

В этом случае метод не имеет явного возвращаемого значения, но возвращаемое значение по умолчанию в любом случае сопоставляется. Для выходного параметра используется соответствующий выходной параметр.

Приведенную выше хранимую процедуру можно вызвать следующим образом:

C#

string CompanyName = "";
string customerID = "ALFKI";
db.GetCustomerCompanyName(customerID, ref CompanyName);
Console.WriteLine(CompanyName);

Visual Basic

Dim CompanyName As String = ""
Dim customerID As String = "ALFKI"
db.GetCustomerCompanyName(customerID, CompanyName)
Console.WriteLine(CompanyName)

Определяемые пользователем функции

LINQ to SQL поддерживает как скалярные, так и табличные функции, а также встроенный аналог обеих функций.

LINQ to SQL обрабатывает встроенные скалярные вызовы аналогично тому, как вызываются системные функции. Обратите внимание на следующий запрос:

C#

var q =
   from p in db.Products
   select
      new {
         pid = p.ProductID,
         unitp = Math.Floor(p.UnitPrice.Value)
      };

Visual Basic

Dim productInfos = From prod In db.Products _
                   Select p.ProductID, price = Math.Floor(p.UnitPrice.Value)

Здесь вызов метода Math.Floor преобразуется в вызов системной функции FLOOR. Аналогичным образом вызов функции, сопоставленной с определяемой пользователем функцией, преобразуется в вызов определяемой пользователем функции в SQL.

Пример 1

Ниже приведена скалярная пользовательская функция ReverseCustName(). В SQL Server функция может быть определена следующим образом:

CREATE FUNCTION ReverseCustName(@string varchar(100))
RETURNS varchar(100)
AS
BEGIN
   DECLARE @custName varchar(100)
   -- Impl. left as exercise for the reader
   RETURN @custName
END

С этой пользовательской функцией можно сопоставить клиентский метод, определенный в классе схемы, с помощью приведенного ниже кода. Обратите внимание, что тело метода создает выражение, которое фиксирует намерение вызова метода и передает это выражение в DataContext для преобразования и выполнения. (Это прямое выполнение происходит только в том случае, если вызывается функция.)

C#

[Function(Name = "[dbo].[ReverseCustName]")]
public string ReverseCustName(string string1) {
   IExecuteResult result = this.ExecuteMethodCall(this,
      (MethodInfo)(MethodInfo.GetCurrentMethod())), string1);
   return (string) result.ReturnValue;
}

Visual Basic

Function(Name:= "[dbo].[ReverseCustName]")> _
Public Function ReverseCustName(string1 As String) As String

    Dim result As IExecuteResult = ExecuteMethodCall(Me, _
             CType(MethodInfo.GetCurrentMethod(), MethodInfo), string1)
   return CStr(result.ReturnValue)

Пример 2

В следующем запросе вы увидите встроенный вызов созданного метода UDF ReverseCustName. В этом случае функция выполняется не сразу. Sql, созданный для этого запроса, преобразуется в вызов определяемой пользователем функции в базе данных (см. код SQL после запроса).

C#

var q =
   from c in db.Customers
   select
      new {
         c.ContactName,
         Title = db.ReverseCustName(c.ContactTitle)
      };

Visual Basic

Dim customerInfos = From cust In db.Customers _
                    Select c.ContactName, _
                    Title = db.ReverseCustName(c.ContactTitle)



SELECT [t0].[ContactName],
   dbo.ReverseCustName([t0].[ContactTitle]) AS [Title]
FROM [Customers] AS [t0]

При вызове той же функции вне запроса LINQ to SQL создает простой запрос из выражения вызова метода со следующим синтаксисом SQL (где параметр @p0 привязан к передаваемой константе):

В LINQ to SQL:

C#

string str = db.ReverseCustName("LINQ to SQL");

Visual Basic

Dim str As String = db.ReverseCustName("LINQ to SQL")

Преобразует в:

SELECT dbo.ReverseCustName(@p0)

Пример 3

Функция с табличным значением (TVF) возвращает один результирующий набор (в отличие от хранимых процедур, которые могут возвращать несколько результирующих фигур). Так как тип возвращаемого значения TVF — table, можно использовать TVF в любом месте SQL, где можно использовать таблицу, и обрабатывать TVF так же, как таблицу.

Рассмотрим следующее SQL Server определение функции с табличным значением:

CREATE FUNCTION ProductsCostingMoreThan(@cost money)
RETURNS TABLE
AS
RETURN
   SELECT ProductID, UnitPrice
   FROM Products
   WHERE UnitPrice > @cost

Эта функция явно указывает, что она возвращает TABLE, поэтому структура возвращаемого результированного набора неявно определена. LINQ to SQL сопоставляет функцию следующим образом:

C#

       [Function(Name = "[dbo].[ProductsCostingMoreThan]")]
      public IQueryable<Product> ProductsCostingMoreThan(
            System.Nullable<decimal> cost) {

         return this.CreateMethodCallQuery<Product>(this,
            (MethodInfo)MethodInfo.GetCurrentMethod(),
            cost);
      }

Visual Basic

   <Function(Name:="[dbo].[ProductsCostingMoreThan]")> _
      Public Function ProductsCostingMoreThan(
            cost As System.Nullable(Of Decimal)) As IQueryable(Of Product)

    Return CreateMethodCallQuery(Of Product)(Me, _
             CType(MethodInfo.GetCurrentMethod(), MethodInfo), cost)

В следующем коде SQL показано, что вы можете присоединиться к таблице, возвращаемой функцией , и обрабатывать ее так же, как любую другую таблицу:

SELECT p2.ProductName, p1.UnitPrice
FROM dbo.ProductsCostingMoreThan(80.50)
AS p1 INNER JOIN Products AS p2 ON p1.ProductID = p2.ProductID

В LINQ to SQL запрос будет отображаться следующим образом (с использованием нового синтаксиса join):

C#

var q =
   from p in db.ProductsCostingMoreThan(80.50m)
   join s in db.Products on p.ProductID equals s.ProductID
   select new {p.ProductID, s.UnitPrice};

Visual Basic

Dim productInfos = From costlyProd In db.ProductsCostingMoreThan(80.50m) _
                   Join prod In db.Products _
                   On costlyProd.ProductID Equals prod.ProductID _
                   Select costlyProd.ProductID, prod.UnitPrice

ограничения LINQ to SQL хранимых процедур

LINQ to SQL поддерживает создание кода для хранимых процедур, возвращающих статически определенные результирующие наборы. Таким образом, генератор кода LINQ to SQL не поддерживает следующее:

  • Хранимые процедуры, использующие динамический SQL для возврата результирующих наборов. Если хранимая процедура содержит условную логику для создания динамической инструкции SQL, LINQ to SQL не может получить метаданные для набора результатов, так как запрос, используемый для создания набора результатов, неизвестен до времени выполнения.
  • Хранимые процедуры, которые создают результаты на основе временной таблицы.

Средство генератора классов сущностей

Если у вас есть существующая база данных, не нужно создавать полную объектную модель вручную, чтобы представить ее. Дистрибутив LINQ to SQL поставляется со средством SQLMetal. Это программа командной строки, которая автоматизирует задачу создания классов сущностей путем вывода соответствующих классов из метаданных базы данных.

SQLMetal можно использовать для извлечения метаданных SQL из базы данных и создания исходного файла, содержащего объявления классов сущностей. Кроме того, можно разделить процесс на два этапа, сначала создав XML-файл, представляющий метаданные SQL, а затем преобразовав этот XML-файл в исходный файл, содержащий объявления классов. Этот процесс разделения позволяет сохранить метаданные в виде файла, чтобы их можно было изменить. Процесс извлечения, создающий файл, делает несколько выводов о соответствующих именах классов и свойств, заданных именами таблиц и столбцов базы данных. Возможно, вам потребуется изменить XML-файл, чтобы генератор мог получить более приятные результаты или скрыть аспекты базы данных, которые не должны присутствовать в объектах.

Самый простой сценарий использования SQLMetal — непосредственное создание классов из существующей базы данных. Вот как можно вызвать средство:

C#

SqlMetal /server:.\SQLExpress /database:Northwind /pluralize /namespace:nwind /code:Northwind.cs

Visual Basic

SqlMetal /server:.\SQLExpress /database:Northwind /pluralize /namespace:nwind /code:Northwind.vb /language:vb

При выполнении средства создается файл Northwind.cs или VB, содержащий объектную модель, созданную при чтении метаданных базы данных. Такое использование хорошо работает, если имена таблиц в базе данных похожи на имена объектов, которые вы хотите создать. В противном случае вам потребуется использовать двухэтапный подход.

Чтобы указать SQLMetal создать DBML-файл, используйте средство следующим образом:

SqlMetal /server:.\SQLExpress /database:Northwind /pluralize
   /xml:Northwind.dbml

После создания файла dbml можно добавить в него заметки с помощью атрибута класса и свойства , чтобы описать сопоставление таблиц и столбцов с классами и свойствами. Завершив создание примечаний к dbml-файлу, можно создать объектную модель, выполнив следующую команду:

C#

SqlMetal /namespace:nwind /code:Northwind.cs Northwind.dbml

Visual Basic

SqlMetal /namespace:nwind /code:Northwind.vb Northwind.dbml /language:vb

Сигнатура использования SQLMetal выглядит следующим образом:

SqlMetal [options] [filename]

Ниже приведена таблица с доступными параметрами командной строки для SQLMetal.

Параметры командной строки для SQLMetal

Параметр Описание
/server:<name> Указывает сервер, к которому нужно подключиться для доступа к базе данных.
/database:<name> Указывает имя базы данных для чтения метаданных.
/user:<name> Идентификатор пользователя для входа для сервера.
/password:<name> Пароль для входа для сервера.
/views Извлечение представлений базы данных.
/functions Извлечение функций базы данных.
/sprocs Извлечение хранимых процедур.
/code[:<filename>] Указывает, что выходными данными средства является исходный файл объявлений классов сущностей.
/language:<language> Используйте Visual Basic или C# (по умолчанию).
/xml[:<filename>] Указывает, что выходными данными средств является DBML-файл, описывающий метаданные базы данных и первое приближение догадок имен классов и свойств.
/map[:<filename>] Указывает, что вместо атрибутов следует использовать внешний файл сопоставления.
/pluralize Указывает, что средство должно выполнять во множественном числе или де-плюралистику на английском языке по именам таблиц, чтобы создать соответствующие имена классов и свойств.
/namespace:<name> Указывает пространство имен, в которое будут созданы классы сущностей.
/timeout:<seconds> Значение времени ожидания в секундах, используемое для команд базы данных.

Примечание Чтобы извлечь метаданные из MDF-файла, необходимо указать имя MDF-файла после всех остальных параметров. Если параметр /server не указан, предполагается localhost .

Справочник по DBML средства генератора

Файл DBML (язык сопоставления баз данных) — это в первую очередь описание метаданных SQL для данной базы данных. Он извлекается с помощью SQLMetal путем просмотра метаданных базы данных. Этот же файл также используется SQLMetal для создания объектной модели по умолчанию для представления базы данных.

Ниже приведен прототип синтаксиса DBML:

<?xml version="1.0" encoding="utf-16"?>
<Database Name="Northwind" EntityNamespace="Mappings.FunctionMapping"
   ContextNamespace="Mappings.FunctionMapping"
   Provider="System.Data.Linq.SqlClient.Sql2005Provider"
   xmlns="https://schemas.microsoft.com/dsltools/LINQ to SQLML">
   <Table Name="Categories">
      <Type Name="Category">
         <Column Name="CategoryID" Type="System.Int32"
            DbType="Int NOT NULL IDENTITY" IsReadOnly="False" 
            IsPrimaryKey="True" IsDbGenerated="True" CanBeNull="False" />
         <Column Name="CategoryName" Type="System.String"
            DbType="NVarChar(15) NOT NULL" CanBeNull="False" />
         <Column Name="Description" Type="System.String"
            DbType="NText" CanBeNull="True" UpdateCheck="Never" />
         <Column Name="Picture" Type="System.Byte[]"
            DbType="Image" CanBeNull="True" UpdateCheck="Never" />
         <Association Name="FK_Products_Categories" Member="Products"
            ThisKey="CategoryID" OtherKey="CategoryID"
            OtherTable="Products" DeleteRule="NO ACTION" />
      </Type>
   </Table>

   <Function Name="GetCustomerOrders">
      <Parameter Name="customerID" Type="System.String" DbType="NChar(5)" />
      <ElementType Name="GetCustomerOrdersResult">
         <Column Name="OrderID" Type="System.Int32"
            DbType="Int" CanBeNull="True" />
         <Column Name="ShipName" Type="System.String"
            DbType="NVarChar(40)" CanBeNull="True" />
         <Column Name="OrderDate" Type="System.DateTime"
            DbType="DateTime" CanBeNull="True" />
         <Column Name="Freight" Type="System.Decimal"
            DbType="Money" CanBeNull="True" />
      </ElementType>
   </Function>
</Database>

Элементы и их атрибуты описаны следующим образом.

База данных

Это самый внешний элемент в формате XML. Этот элемент слабо сопоставляется с атрибутом Database в созданном dataContext.

Атрибуты базы данных

attribute Тип По умолчанию Описание
Имя Строка None Имя базы данных. При наличии и при создании DataContext вложит к нему атрибут Database с этим именем. Также используется в качестве имени класса DataContext , если атрибут класса отсутствует.
EntityNamespace Уровень согласованности "Строгая" None Пространство имен по умолчанию для классов, созданных из элементов Type в элементах Table. Если здесь не указано пространство имен, в корневом пространстве имен создаются классы сущностей.
ContextNamespace Строка None Пространство имен по умолчанию для созданного класса DataContext . Если здесь не указано пространство имен, в корневом пространстве имен создается класс DataContext .
Класс Строка Database.Name Имя созданного класса DataContext . Если значение отсутствует, используйте атрибут Name элемента Database.
AccessModifier AccessModifier Общедоступные Уровень доступности созданного класса DataContext . Допустимые значения: Public, Protected, Internal и Private.
BaseType Строка "System.Data.Linq.DataContext" Базовый тип класса DataContext .
Поставщик Строка "System.Data.Linq.SqlClient.Sql2005Provider" Поставщик DataContext использует поставщик Sql2005 в качестве поставщика по умолчанию.
ExternalMapping Логическое значение Неверно Укажите, используется ли DBML для создания внешнего файла сопоставления.
Сериализация SerializationMode SerializationMode.None Укажите, являются ли созданные классы DataContext и сущности сериализуемыми.

Атрибуты Sub-Element базы данных

Sub-Element Тип элемента Диапазон вхождения Описание
<Таблица> Таблица 0 без ограничений Представляет SQL Server таблицу или представление, которое будет сопоставлено либо с одним типом, либо с иерархией наследования.
<Компонент> Функция 0 без ограничений Представляет SQL Server хранимую процедуру или функцию базы данных, которая будет сопоставлена с методом в созданном классе DataContext.
<Соединение> Подключение 0—1 Представляет подключение к базе данных, которое будет использоваться DataContext .

Таблица

Этот элемент представляет таблицу базы данных (или представление), которая будет сопоставлена либо с одним типом, либо с иерархией наследования. Этот элемент слабо сопоставляется с атрибутом Table в созданном классе сущностей.

Атрибуты таблицы

attribute Тип По умолчанию Описание
Имя Строка (обязательно) Имя таблицы в базе данных. При необходимости служит основой для имени адаптера таблицы по умолчанию.
Член Строка Table.Name Имя поля-члена, созданного для этой таблицы в классе DataContext .
AccessModifier AccessModifier Общедоступные Уровень доступности ссылки на таблицу<T> в DataContext. Допустимые значения: Public, Protected, Internal и Private.

Атрибуты Sub-Element таблицы

Sub-Element Тип элемента Диапазон вхождов Описание
<Тип> Тип 1-1 Представляет иерархию типа или наследования, сопоставленную с этой таблицей.
<InsertFunction> TableFunction 0—1 Метод для вставки. При его наличии создается метод InsertT .
<UpdateFunction> TableFunction 0—1 Метод обновления. При его наличии создается метод UpdateT .
<DeleteFunction> TableFunction 0—1 Метод для удаления. При его наличии создается метод DeleteT .

Тип

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

Тип также может представлять компонент иерархии наследования с несколькими типами, сопоставленными с одной и той же таблицей. В этом случае элементы Type вложены для представления отношений наследования "родитель-потомок" и отличаются в базе данных указанным объектом InheritanceCode .

Атрибуты типов

attribute Тип По умолчанию Описание
Имя Строка (обязательно) Имя создаваемого типа СРЕДЫ CLR.
Код наследования Строка Нет Если этот тип участвует в наследовании, он может иметь связанный код наследования, чтобы различать типы СРЕДЫ CLR при загрузке строк из таблицы. Тип, чей Код наследования соответствует значению столбца IsDiscriminator, используется для создания экземпляра загруженного объекта. Если код наследования отсутствует, созданный класс сущностей является абстрактным.
IsInheritanceDefault Логическое значение Неверно Если это верно для типа в иерархии наследования, этот тип будет использоваться при загрузке строк, которые не соответствуют ни одному из определенных кодов наследования.
AccessModifier AccessModifier Общедоступные Уровень доступности создаваемого типа CLR. Допустимые значения: Public, Protected, Internal и Private.
Идентификатор Строка Нет Тип может иметь уникальный идентификатор. Идентификатор типа может использоваться другими таблицами или функциями. Идентификатор отображается только в DBML-файле, но не в объектной модели.
Idref Строка Нет IdRef используется для ссылки на идентификатор другого типа. Если idRef присутствует в элементе типа, элемент type должен содержать только сведения о IdRef . IdRef отображается только в DBML-файле, но не в объектной модели.

Атрибуты Sub-Element типов

Sub-Element Тип элемента Диапазон вхождов Описание
<Столбец> Столбец 0 без ограничений Представляет свойство в этом типе, которое будет привязано к полю в таблице этого типа.
<Взаимосвязь> Взаимосвязь 0 без ограничений Представляет свойство в этом типе, которое будет привязано к одному концу связи внешнего ключа между таблицами.
<Тип> Подтип 0 без ограничений Представляет подтипы этого типа в иерархии наследования.

Подтип

Этот элемент представляет производный тип в иерархии наследования. Это будет создано в новый тип CLR со столбцами и связями, указанными в этом типе. Атрибуты наследования для подтипов не создаются.

По сравнению с Типом элементы SubType не имеют AccessModifier , так как все производные типы должны быть открытыми. Подтипы не могут повторно использоваться другими таблицами и функциями, поэтому в них нет идентификаторов и идентификаторов IdRef .

Атрибуты подтипа

attribute Тип По умолчанию Описание
Имя Строка (обязательно) Имя создаваемого типа СРЕДЫ CLR.
Код наследования Строка None Если этот тип участвует в наследовании, он может иметь связанный код наследования для различения типов CLR при загрузке строк из таблицы. Тип, чей код наследования соответствует значению столбца IsDiscriminator , используется для создания экземпляра загруженного объекта. Если код наследования отсутствует, созданный класс сущностей является абстрактным.
IsInheritanceDefault Логическое значение Неверно Если это верно для типа в иерархии наследования, этот тип будет использоваться при загрузке строк, которые не соответствуют ни одному из определенных кодов наследования.

Атрибуты Sub-Element subType

Sub-Element Тип элемента Диапазон вхождения Описание
<Столбец> Столбец 0 без ограничений Представляет свойство в этом типе, которое будет привязано к полю в таблице этого типа.
<Взаимосвязь> Взаимосвязь 0 без ограничений Представляет свойство в этом типе, которое будет привязано к на одном конце связи внешнего ключа между таблицами.
<Тип> Подтип 0 без ограничений Представляет подтипы этого типа в иерархии наследования.

Столбец

Этот элемент представляет столбец в таблице, сопоставленный со свойством (и резервным полем) в классе. Однако элемент Column не будет присутствовать на любом конце связи внешнего ключа, так как он полностью представлен (на обоих концах) элементами Association.

Атрибуты столбца

Атрибуты Тип По умолчанию Описание
Имя Строка None Имя поля базы данных, с который будет сопоставляться этот столбец.
Член Строка имя; Имя свойства СРЕДЫ CLR, которое должно быть создано для содержащего типа.
Память Строка _Член Имя частного резервного поля СРЕДЫ CLR, в которое будет храниться значение этого столбца. Не удаляйте хранилище при сериализации, даже если оно используется по умолчанию.
AccessModifier AccessModifier Общедоступные Уровень доступности создаваемого свойства CLR. Допустимые значения: Public, Protected, Internal и Private.
Тип Строка (требуется) Имя типа создаваемого свойства CLR и резервного поля. Это может быть что угодно, от полного имени до простого прямого имени класса, если имя в конечном итоге будет находиться в область при компиляции созданного кода.
DbType Строка None Полный SQL Server тип (включая заметки, например NOT NULL) для этого столбца. Используется LINQ to SQL, если вы предоставляете его для оптимизации созданных запросов и для более конкретного при выполнении CreateDatabase(). Всегда сериализуйте DbType.
IsReadOnly Логическое значение Неверно Если задан параметр IsReadOnly , метод задания свойств не создается, то есть пользователи не могут изменять значение этого столбца с помощью этого объекта.
IsPrimaryKey Логическое значение Неверно Указывает, что этот столбец участвует в первичном ключе таблицы. Эти сведения необходимы для правильной работы LINQ to SQL.
IsDbGenerated Логическое значение Неверно Указывает, что данные этого поля создаются базой данных. Это относится главным образом к полям счетчика и вычисляемым полям. Назначение значений этим полям не имеет смысла, поэтому они автоматически становятся IsReadOnly.
CanBeNull Логическое None Указывает, что значение может содержать значение NULL. Если вы хотите фактически использовать значения NULL в среде CLR, необходимо по-прежнему указать ClrType как T, допускающий значение<> NULL.
UpdateCheck UpdateCheck Всегда (если хотя бы один другой элемент не имеет isVersion , а затем — Never) Указывает, следует ли LINQ to SQL использовать этот столбец при обнаружении конфликтов оптимистического параллелизма. Обычно все столбцы участвуют по умолчанию, если нет столбца IsVersion , который затем участвует сам по себе. Может иметь значение Always, Never или WhenChanged (это означает, что столбец участвует, если его собственное значение изменилось).
IsDiscriminator Логическое значение Неверно Указывает, содержит ли это поле код дискриминатора, используемый для выбора между типами в иерархии наследования.
Выражение Строка None Не влияет на работу LINQ to SQL, но используется во время .CreateDatabase() как необработанное выражение SQL, представляющее выражение вычисляемого столбца.
IsVersion Логическое значение Неверно Указывает, что это поле представляет поле TIMESTAMP в SQL Server, которое автоматически обновляется при каждом изменении строки. Затем это поле можно использовать для более эффективного обнаружения конфликтов оптимистического параллелизма.
IsDelayLoaded Логическое значение Неверно Указывает, что этот столбец не должен загружаться сразу после материализации объекта, а только при первом обращении к соответствующему свойству. Это полезно для больших полей memo или двоичных данных в строке, которая не всегда требуется.
AutoSync AutoSync If (IsDbGenerated && IsPrimaryKey) OnInsert;

Else if (IsDbGenerated) Always

Иначе никогда

Указывает, синхронизируется ли столбец автоматически из значения, созданного базой данных. Допустимые значения для этого тега: OnInsert, Always и Never.

Взаимосвязь

Этот элемент представляет любой конец связи внешнего ключа. Для связей "один ко многим" это будет EntitySet<T> с одной стороны и EntityRef<T> на стороне многих. Для связей "один к одному" это будет EntityRef<T> с обеих сторон.

Обратите внимание, что не обязательно иметь запись "Связь " на обеих сторонах ассоциации. В этом случае свойство будет создано только на стороне с записью (формируя однонаправленную связь).

Атрибуты ассоциации

attribute Тип По умолчанию Описание
Имя Строка (требуется) Имя отношения (обычно это имя ограничения внешнего ключа). Технически это может быть необязательным, но всегда должен создаваться кодом, чтобы избежать неоднозначности при наличии нескольких связей между одной и той же двумя таблицами.
Член Строка имя; Имя свойства СРЕДЫ CLR, создаваемого на этой стороне связи.
Память Строка Если oneToMany и not IsForeignKey:

_OtherTable

Или:

_TypeName(OtherTable)

Имя частного резервного поля СРЕДЫ CLR, в которое будет храниться значение этого столбца.
AccessModifier AccessModifier Общедоступные Уровень доступности создаваемого свойства CLR. Допустимые значения: Public, Protected, Internal и Private.
ThisKey Строка Свойство IsIdentity в содержавом классе Разделенный запятыми список ключей на этой стороне связи.
OtherTable Строка См. описание. Таблица на другом конце связи. Обычно это может определяться средой выполнения LINQ to SQL путем сопоставления имен связей, но это невозможно для однонаправленных или анонимных связей.
OtherKey Строка Первичные ключи во внешнем классе Разделенный запятыми список ключей на другой стороне связи.
IsForeignKey Логическое значение Неверно Указывает, является ли это "дочерней" стороной связи, стороной числа "один ко многим".
RelationshipType RelationshipType OneToMany Указывает, подтверждает ли пользователь, что данные, связанные с этой связью, соответствуют критериям данных "один к одному" или соответствуют более общему варианту "один ко многим". В случае "один к одному" пользователь утверждает, что для каждой строки на стороне первичного ключа ("один") имеется только одна строка на стороне внешнего ключа ("многие"). Это приведет к созданию EntityRef<T> на стороне "один", а не EntitySet<T>. Допустимые значения: OneToOne и OneToMany.
DeleteRule Строка None Используется для добавления поведения удаления в эту связь. Например, cascade добавит ONDELETECASCADE в отношение FK. Если задано значение NULL, поведение удаления не добавляется.

Функция

Этот элемент представляет хранимую процедуру или функцию базы данных. Для каждого узла Функции в классе DataContext создается метод .

Атрибуты функций

attribute Тип По умолчанию Описание
Имя Строка (требуется) Имя хранимой процедуры в базе данных.
Метод Строка Метод Имя создаваемого метода CLR, которое позволяет вызывать хранимую процедуру. Имя метода по умолчанию имеет такие элементы, как [dbo]. Удалено имя.
AccessModifier AccessModifier Общедоступные Уровень доступности метода хранимой процедуры. Допустимые значения: Public, Protected, Internal и Private.
HasMultipleResults Логическое Число типов > 1 Указывает, возвращает ли хранимая процедура, представленная этим узлом функции , несколько наборов результатов. Каждый набор результатов — это табличная фигура, она может быть либо существующим типом , либо набором столбцов. В последнем случае для набора столбцов будет создан узел Type .
IsComposable Логическое значение Неверно Указывает, можно ли создавать функцию или хранимую процедуру в LINQ to SQL запросах. Можно создавать только функции базы данных, которые не возвращают void.

Атрибуты Sub-Element функции

Sub-Element Типы элементов Диапазон вхождов Описание
<Параметр> Параметр 0 без ограничений Представляет параметры in и out этой хранимой процедуры.
<ElementType> Тип 0 без ограничений Представляет табличные фигуры, которые может возвращать соответствующая хранимая процедура.
<Return> Возвращает 0—1 Возвращаемый скалярный тип этой функции базы данных или хранимой процедуры. Если значение Return равно NULL, функция возвращает void. Функция не может иметь значение Return и ElementType.

TableFunction

Этот элемент представляет функции переопределения CUD для таблиц. Конструктор LINQ to SQL позволяет создавать методы переопределения Insert, Update и Delete для LINQ TO SQL и сопоставлять имена свойств сущности с именами параметров хранимых процедур.

Имя метода для функций CUD исправлено, поэтому в DBML для элементов TableFunction отсутствует атрибут Method. Например, для таблицы Customer методы CUD называются InsertCustomer, UpdateCustomer и DeleteCustomer.

Табличная функция не может возвращать табличную фигуру, поэтому в элементе TableFunction отсутствует атрибут ElementType.

Атрибуты TableFunction

attribute Тип По умолчанию Описание
Имя Строка (требуется) Имя хранимой процедуры в базе данных.
AccessModifier AccessModifier Private Уровень доступности метода хранимой процедуры. Допустимые значения: Public, Protected, Internal и Private.
HasMultipleResults Логическое Число типов > 1 Указывает, возвращает ли хранимая процедура, представленная этим узлом функции, несколько наборов результатов. Каждый набор результатов — это табличная фигура, она может быть либо существующим типом , либо набором столбцов. В последнем случае для набора столбцов будет создан узел Type .
IsComposable Логическое значение Неверно Указывает, можно ли создавать функцию или хранимую процедуру в LINQ to SQL запросах. Можно создавать только функции базы данных, которые не возвращают void.

Атрибуты Sub-Element TableFunction

Sub-Elements Тип элемента Диапазон вхождов Описание
<Параметр> TableFunctionParameter 0 без ограничений Представляет параметры in и out этой табличной функции.
<Return> TableFunctionReturn 0—1 Возвращаемый скалярный тип этой табличной функции. Если значение Return равно NULL, функция возвращает void.

Параметр

Этот элемент представляет хранимую процедуру или параметр функции. Параметры могут передавать и передавать данные.

Атрибуты параметра

attribute Тип По умолчанию Описания
Имя Строка (требуется) Имя базы данных хранимого параметра proc/function.
Параметр Строка имя; Имя параметра метода в среде CLR.
  Строка (требуется) Имя параметра метода в среде CLR.
DbType Строка None Тип базы данных хранимого параметра proc/function.
Направление ParameterDirection Где Направление, в которое передается параметр. Может иметь значение In, Out и InOut.

Возвращает

Этот элемент представляет тип возвращаемого значения хранимой процедуры или функции.

Атрибуты возврата

attribute Тип По умолчанию Описание
Тип Строка (требуется) Тип CLR хранимого результата proc/function.
DbType Строка None Тип базы данных сохраненного результата proc/function.

TableFunctionParameter

Этот элемент представляет параметр функции CUD. Параметры могут передавать и передавать данные. Каждый параметр сопоставляется со столбцом таблицы , которому принадлежит эта функция CUD. В этом элементе нет атрибутов Type или DbType , так как сведения о типе можно получить из столбца, с которым сопоставляется параметр.

Атрибуты TableFunctionParameter

attribute Тип По умолчанию Описание
Имя Строка (требуется) Имя базы данных параметра функции CUD.
Параметр Строка имя; Имя параметра метода в среде CLR.
Столбец Строка имя; Имя столбца, с который сопоставляется этот параметр.
Направление ParameterDirection Где Направление, в которое передается параметр. Может иметь значение In, Out или InOut.
Версия Версия Текущие Указывает, ссылается ли PropertyName на текущую или исходную версию заданного столбца. Применимо только во время переопределения обновления . Может иметь значение Current или Original.

TableFunctionReturn

Этот элемент представляет тип возвращаемого значения функции CUD. Фактически он содержит только имя столбца, сопоставленное с результатом функции CUD. Сведения о типе возвращаемого значения можно получить из столбца .

Атрибут TableFunctionReturn

Аттробит Тип По умолчанию Описание
Столбец Строка None Имя столбца, с которым сопоставляется возвращаемый объект.

Подключение

Этот элемент представляет параметры подключения к базе данных по умолчанию. Это позволяет создать конструктор по умолчанию для типа DataContext , который уже знает, как подключиться к базе данных.

Существует два типа подключений по умолчанию: один с прямым ConnectionString, а второй — считывание из App.Settings.

Атрибуты подключения

attribute Тип По умолчанию Описание
UseApplicationSettings Логическое значение Неверно Определяет, следует ли использовать файл App.Settings или получить параметрыприложения из прямого объекта ConnectionString.
ConnectionString Строка None Строка подключения, отправляемая поставщику данных SQL.
SettingsObjectName Строка Параметры Объект App.Settings, из которого извлекаются свойства.
SettingsPropertyName Строка ConnectionString Свойство App.Settings , содержащее ConnectionString.

Многоуровневые сущности

В двухуровневых приложениях один dataContext обрабатывает запросы и обновления. Однако для приложений с дополнительными уровнями часто требуется использовать отдельные экземпляры DataContext для запросов и обновлений. Например, в случае ASP.NET приложений запросы и обновления выполняются для отдельных запросов к веб-серверу. Поэтому нецелесообразно использовать один и тот же экземпляр DataContext в нескольких запросах. В таких случаях экземпляр DataContext должен иметь возможность обновлять объекты, которые он не извлекает. Поддержка многоуровневых сущностей в LINQ to SQL предоставляет такую возможность с помощью метода Attach().

Ниже приведен пример изменения объекта Customer с помощью другого экземпляра DataContext :

C#

// Customer entity changed on another tier – for example, through a browser
// Back on the mid-tier, a new context needs to be used
Northwind db2 = new Northwind(…);

// Create a new entity for applying changes
Customer C2 = new Customer();
C2.CustomerID ="NewCustID";

// Set other properties needed for optimistic concurrency check
C2.CompanyName = "New Company Name Co.";

...

// Tell LINQ to SQL to track this object for an update; that is, not for insertion
db2.Customers.Attach(C2);

// Now apply the changes
C2.ContactName = "Mary Anders";

// DataContext now knows how to update the customer
db2.SubmitChanges();

Visual Basic

' Customer entity changed on another tier – for example, through a browser
' Back on the mid-tier, a new context needs to be used
Dim db2 As Northwind = New Northwind(…)

' Create a new entity for applying changes
Dim C2 As New Customer()
C2.CustomerID =”NewCustID”

' Set other properties needed for optimistic concurrency check
C2.CompanyName = ”New Company Name Co.”

...

' Tell LINQ to SQL to track this object for an update; that is, not for insertion
db2.Customers.Attach(C2)

' Now apply the changes
C2.ContactName = "Mary Anders"

' DataContext now knows how to update the customer
db2.SubmitChanges()

В многоуровневых приложениях вся сущность часто не отправляется между уровнями для простоты, взаимодействия или конфиденциальности. Например, поставщик может определить контракт данных для веб-службы, который отличается от сущности Order , используемой на среднем уровне. Аналогичным образом на веб-странице может отображаться только подмножество членов сущности Employee. Таким образом, многоуровневая поддержка предназначена для таких случаев. Только члены, относящиеся к одной или нескольким из следующих категорий, необходимо перенести между уровнями и задать перед вызовом Attach().

  1. Члены, которые являются частью удостоверения сущности.
  2. Измененные члены.
  3. Члены, участвующие в проверка оптимистического параллелизма.

Если для проверка оптимистичного параллелизма используется столбец метки времени или номера версии, то перед вызовом Attach() необходимо задать соответствующий элемент. Перед вызовом Attach() не нужно задавать значения для других членов. LINQ to SQL использует минимальные обновления с оптимистическими проверками параллелизма, то есть элемент, который не задан или не проверен на оптимистичный параллелизм, игнорируется.

Исходные значения, необходимые для оптимистических проверок параллелизма, могут храниться с помощью различных механизмов, не входящих в область API LINQ to SQL. Приложение ASP.NET может использовать состояние представления (или элемент управления, использующий состояние представления). Веб-служба может использовать DataContract для метода обновления, чтобы обеспечить доступность исходных значений для обработки обновлений. В интересах взаимодействия и универсальности LINQ to SQL не определяет форму данных, обмениваемых между уровнями, или механизмы, используемые для кругового пути исходных значений.

Сущностям для вставки и удаления не требуется метод Attach( ). Для вставки и удаления можно использовать методы, используемые для двухуровневых приложений — Table.Add() и Table.Remove( ). Как и в случае двухуровневых обновлений, пользователь отвечает за обработку ограничений внешнего ключа. Клиент с заказами не может быть просто удален без обработки его заказов, если в базе данных есть ограничение внешнего ключа, препятствующее удалению клиента с заказами.

LINQ to SQL также обрабатывает вложение сущностей для обновлений транзитивно. По сути, пользователь создает граф объектов перед обновлением по своему усмотрению и вызывает Attach(). Затем все изменения можно воспроизвести на подключенном графе, чтобы выполнить необходимые обновления, как показано ниже.

C#

Northwind db1 = new Northwind(…);
// Assume Customer c1 and related Orders o1, o2 are retrieved

// Back on the mid-tier, a new context needs to be used
Northwind db2 = new Northwind(…);

// Create new entities for applying changes
Customer c2 = new Customer();
c2.CustomerID = c.CustomerID;
Order o2 = new Order();
o2.OrderID = ...;

c2.Orders.Add(o2);

// Add other related objects needed for updates

// Set properties needed for optimistic concurrency check
...
// Order o1 to be deleted
Order o1 = new Order();
o1.OrderID = ...;

// Tell LINQ to SQL to track the graph transitively
db2.Customers.Attach(c2);
// Now "replay" all the changes

// Updates
c2.ContactName = ...;
o2.ShipAddress = ...;

// New object for insertion
Order o3 = new Order();
o3.OrderID = ...;
c2.Orders.Add(o3);

// Remove order o1
db2.Orders.Remove(o1);

// DataContext now knows how to do update/insert/delete
db2.SubmitChanges();

Visual Basic

Dim db1 As Northwind = New Northwind(…)
' Assume Customer c1 and related Orders o1, o2 are retrieved

' Back on the mid-tier, a new context needs to be used
Dim db2 As Northwind = New Northwind(…)

' Create new entities for applying changes
Customer c2 = new Customer()
c2.CustomerID = c.CustomerID
Dim o2 As Order = New Order()
o2.OrderID = ...

c2.Orders.Add(o2)

' Add other related objects needed for updates

' Set properties needed for optimistic concurrency check
...
' Order o1 to be deleted
Dim o1 As Order = New Order()
o1.OrderID = ...

' Tell LINQ to SQL to track the graph transitively
db2.Customers.Attach(c2)
' Now "replay" all the changes

' Updates
c2.ContactName = ...
o2.ShipAddress = ...

' New object for insertion
Dim o3 As Order = New Order()
o3.OrderID = ...
c2.Orders.Add(o3)

' Remove order o1
db2.Orders.Remove(o1)

' DataContext now knows how to do update/insert/delete
db2.SubmitChanges()

Внешнее сопоставление

Помимо сопоставления на основе атрибутов, LINQ to SQL также поддерживает внешнее сопоставление. Наиболее распространенной формой внешнего сопоставления является XML-файл. Файлы сопоставления позволяют использовать дополнительные сценарии, в которых желательно отделить сопоставление от кода.

DataContext предоставляет дополнительный конструктор для предоставления MappingSource. Одной из форм MappingSource является XmlMappingSource , который можно создать из XML-файла сопоставления.

Ниже приведен пример использования файла сопоставления.

C#

String path = @"C:\Mapping\NorthwindMapping.xml";
XmlMappingSource prodMapping = 
   XmlMappingSource.FromXml(File.ReadAllText(path));
Northwind db = new Northwind(
   @"Server=.\SQLExpress;Database=c:\Northwind\Northwnd.mdf",
   prodMapping
   );

Visual Basic

Dim path As String = "C:\Mapping\NorthwindMapping.xml"
Dim prodMapping As XmlMappingSource = _
   XmlMappingSource.FromXml(File.ReadAllText(path))
Dim db As Northwind = New Northwind( _
   "Server=.\SQLExpress;Database=c:\Northwind\Northwnd.mdf", _
   prodMapping   )

Ниже приведен соответствующий фрагмент из файла сопоставления, показывающий сопоставление для класса Product . В нем показан класс Product в пространстве имен Сопоставление пространства имен, сопоставленный с таблицей Products в базе данных Northwind . Элементы и атрибуты согласованы с именами и параметрами атрибутов.

<?xml version="1.0" encoding="utf-8"?>
<Database xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="Northwind"
   ProviderType="System.Data.Linq.SqlClient.Sql2005Provider">
   <Table Name="Products">
      <Type Name="Mappings.FunctionMapping.Product">
         <Column Name="ProductID" Member="ProductID" Storage="_ProductID"
            DbType="Int NOT NULL IDENTITY" IsPrimaryKey="True"
            IsDBGenerated="True" AutoSync="OnInsert" />
         <Column Name="ProductName" Member="ProductName" Storage="_ProductName"
            DbType="NVarChar(40) NOT NULL" CanBeNull="False" />
         <Column Name="SupplierID" Member="SupplierID" Storage="_SupplierID"
            DbType="Int" />
         <Column Name="CategoryID" Member="CategoryID" Storage="_CategoryID"
            DbType="Int" />
         <Column Name="QuantityPerUnit" Member="QuantityPerUnit"
            Storage="_QuantityPerUnit" DbType="NVarChar(20)" />
         <Column Name="UnitPrice" Member="UnitPrice" Storage="_UnitPrice"
            DbType="Money" />
         <Column Name="UnitsInStock" Member="UnitsInStock" Storage="_UnitsInStock"
            DbType="SmallInt" />
         <Column Name="UnitsOnOrder" Member="UnitsOnOrder" Storage="_UnitsOnOrder"
            DbType="SmallInt" />
         <Column Name="ReorderLevel" Member="ReorderLevel" Storage="_ReorderLevel"
            DbType="SmallInt" />
         <Column Name="Discontinued" Member="Discontinued" Storage="_Discontinued"
            DbType="Bit NOT NULL" />
         <Association Name="FK_Order_Details_Products" Member="OrderDetails"
            Storage="_OrderDetails" ThisKey="ProductID" OtherTable="Order Details"
            OtherKey="ProductID" DeleteRule="NO ACTION" />
         <Association Name="FK_Products_Categories" Member="Category"
            Storage="_Category" ThisKey="CategoryID" OtherTable="Categories"
            OtherKey="CategoryID" IsForeignKey="True" />
         <Association Name="FK_Products_Suppliers" Member="Supplier"
            Storage="_Supplier" ThisKey="SupplierID" OtherTable="Suppliers"
            OtherKey="SupplierID" IsForeignKey="True" />
      </Type>
   </Table>

</Database>

Примечания о поддержке функций NET Framework

В следующих абзацах приведены основные сведения о поддержке типов LINQ to SQL и отличиях от платформа .NET Framework.

Примитивные типы

Реализовано

  • Арифметические операторы и операторы сравнения
  • Операторы shift: << и >>
  • Преобразование между символами char и numeric выполняется с помощью NCHAR в ЮНИКОДе/; в противном случае используется преобразование SQL.

Не реализовано

  • <Введите>. Анализа
  • Перечисления можно использовать и сопоставлять с целыми числами и строками в таблице. Для последнего используются методы Parse и ToString( ).

Отличие от .NET

  • Выходные данные ToString для double используют convert(NVARCHAR(30), @x, 2) в SQL, где всегда используется 16 цифр и "Научная нотация". Например: "0,000000000000000e+000" для 0, поэтому он не дает ту же строку, что и . Net's Convert.ToString().

System.String

Реализовано

  • Нестатические методы:

    • Length, Substring, Contains, StartsWith, EndsWith, IndexOf, Insert, Remove, Replace, Trim, ToLower, ToUpper, LastIndexOf, PadRight, PadLeft, Equals, CompareTo. Поддерживаются все подписи, за исключением случаев, когда они принимают параметр StringComparison и т. д., как описано ниже.
  • Статические методы:

       Concat(...)               all signatures
       Compare(String, String)
       String (indexer) 
       Equals(String, String)
    
  • Конструктор:

        String(Char, Int32)
    
  • Операторов:

      +, ==, != (+, =, and <> in Visual Basic)
    

Не реализовано

  • Методы, которые принимают или создают массив char.

  • Методы, принимаюющие объект CultureInfo/StringComparison/IFormatProvider.

  • Static (Shared in Visual Basic):

       Copy(String str)
       Compare(String, String, Boolean)
       Compare(String, String, StringComparison)
       Compare(String, String, Boolean, CultureInfo) 
       Compare(String, Int32, String, Int32, Int32)
       Compare(String, Int32, String, Int32, Int32,   Boolean)
       Compare(String, Int32, String, Int32, Int32, StringComparison)
       Compare(String, Int32, String, Int32, Int32, Boolean, CultureInfo)
       CompareOrdinal(String, String)
       CompareOrdinal(String, Int32, String, Int32, Int32)
       Join(String, ArrayOf String [,...]) All Join version with first three args
    
  • Instance:

       ToUpperInvariant()
       Format(String, Object)      + overloads
       IndexOf(String, Int32, StringComparison)
       IndexOfAny(ArrayOf Char)
       Normalize()
       Normalize(NormalizationForm)
       IsNormalized()
       Split(...)
       StartsWith(String, StringComparison)
       ToCharArray()
       ToUpper(CultureInfo)
       TrimEnd(ParamArray Char)
       TrimStart(ParamArray Char)
    

Ограничения и отличия от .NET

SQL использует параметры сортировки для определения равенства и упорядочения строк. Их можно указать в экземпляре SQL Server, базе данных, столбце таблицы или выражении.

Переводы функций, реализованных до сих пор, не изменяют параметры сортировки и не указывают другие параметры сортировки в переведенных выражениях. Таким образом, если параметры сортировки по умолчанию не учитывают регистр, такие функции, как CompareTo или IndexOf , могут давать результаты, отличные от результатов, которые будут давать функции .NET (с учетом регистра).

Методы StartsWith(str)/EndsWith(str) предполагают, что аргумент str является константой или выражением, которое вычисляется на клиенте. То есть в настоящее время невозможно использовать столбец для str.

System.Math

Реализованы статические методы

  • Все подписи:
    • Abs, Acos, Asin, Atan, Atan2, BigMul, Ceiling, Cos,Exp, Floor, Log, Log10, Max, Min, Pow, Sign, Sinh, Sqrt, Tan, Tanh или Truncate.

Не реализовано

  • IEEERemainder.
  • DivRem имеет параметр out, поэтому его нельзя использовать в выражении. Константы Math.PI и Math.E вычисляются на клиенте, поэтому перевод не требуется.

Отличие от .NET

Перевод функции .NET Math.Round является функцией SQL ROUND. Преобразование поддерживается, только если указана перегрузка, указывающая значение перечисления MidpointRounding . MidpointRounding.AwayFromZero является поведением SQL, а MidpointRounding.ToEven указывает на поведение СРЕДЫ CLR.

System.Convert

Реализовано

  • Методы формы To<Type1>(<Type2> x), где Type1, Type2 является одним из следующих:
    • bool, byte, char, DateTime, decimal, double, float, Int16, Int32, Int64 или string.
  • Поведение такое же, как приведение:
    • Для ToString(Double) существует специальный код для получения полной точности.
    • Для преобразования символов Int32/ LINQ to SQL использует функциюNCHAR в КОДИРОВКе SQL/.
    • В противном случае перевод будет преобразовывая.

Не реализовано

  • ToSByte, UInt16, 32, 64: эти типы не существуют в SQL.

    To<integer type>(String, Int32) 
    ToString(..., Int32)       any overload ending with an Int32 toBase
    IsDBNull(Object)
    GetTypeCode(Object)
    ChangeType(...)
    
  • Версии с параметром IFormatProvider .

  • Методы, включающие массив (To/FromBase64CharArray, To/FromBase64String).

System.TimeSpan

Реализовано

  • Конструкторы:

       TimeSpan(Long)
       TimeSpan (year, month, day)
       TimeSpan (year, month, day, hour, minutes, seconds)
       TimeSpan (year, month, day, hour, minutes, seconds, milliseconds)
    
  • Операторов:

       Comparison operators: <,==, and so on in C#; <, =, and so on in Visual Basic
    
       +, -
    
  • Статические (общие в Visual Basic) методы:

       Compare(t1,t2)
    
  • Нестатические методы (экземпляр) / свойства:

       Ticks, Milliseconds, Seconds, Hours, Days
       TotalMilliseconds, TotalSeconds, TotalMinutes, TotalHours, TotalDays,
       Equals, CompareTo(TimeSpan)
       Add(TimeSpan), Subtract(TimeSpan)
       Duration() [= ABS], Negate()
    

Не реализовано

   ToString()
   TimeSpan FromDay(Double), FromHours,   all From Variants
   TimeSpan Parse(String)

System.DateTime

Реализовано

  • Конструкторы:

       DateTime(year, month, day)
       DateTime(year, month, day, hour, minutes, seconds)
       DateTime(year, month, day, hour, minutes, seconds, milliseconds)
    
  • Операторов:

       Comparisons
       DateTime – DateTime (gives TimeSpan)
       DateTime + TimeSpan (gives DateTime)
       DateTime – TimeSpan (gives DateTime)
    
  • Статические (общие) методы:

       Add(TimeSpan), AddTicks(Long),
       AddDays/Hours/Milliseconds/Minutes (Double)
       AddMonths/Years(Int32)
       Equals
    
  • Нестатические (экземплярные) методы и свойства:

       Day, Month, Year, Hour, Minute, Second, Millisecond, DayOfWeek
       CompareTo(DateTime)
       TimeOfDay()
       Equals
       ToString()
    

Отличие от .NET

Значения даты и времени SQL округляются до 0,000, 003 или 007 секунд, поэтому они менее точны, чем значения .NET.

Диапазон даты и времени SQL начинается с 1 января 1753 г.

SQL не имеет встроенного типа для TimeSpan. В нем используются различные методы DATEDIFF , возвращающие 32-разрядные целые числа. Один из них — DATEDIFF(DAY,...), который определяет количество дней; другой — DATEDIFF(MILLISECOND,...), который задает количество миллисекундах. Если значения DateTimes отличаются друг от друга более чем на 24 дня, возникает ошибка. В отличие от этого, .NET использует 64-разрядные целые числа и измеряет TimeSpans в тактах.

Чтобы максимально приблизиться к семантике .NET в SQL, LINQ to SQL преобразует TimeSpans в 64-разрядные целые числа и использует два указанных выше метода DATEDIFF для вычисления числа тактов между двумя датами.

Datetime UtcNow вычисляется на клиенте при переводе запроса (как и любое выражение, не использующее данные базы данных).

Не реализовано

   IsDaylightSavingTime()
   IsLeapYear(Int32)
   DaysInMonth(Int32, Int32)
   ToBinary()
   ToFileTime()
   ToFileTimeUtc()
   ToLongDateString()
   ToLongTimeString()
   ToOADate()
   ToShortDateString()
   ToShortTimeString()
   ToUniversalTime()
   FromBinary(Long), FileTime, FileTimeUtc, OADate
   GetDateTimeFormats(...)
   constructor DateTime(Long)
   Parse(String)
   DayOfYear

Поддержка отладки

DataContext предоставляет методы и свойства для получения SQL, созданного для запросов и обработки изменений. Эти методы могут быть полезны для понимания функций LINQ to SQL и для отладки конкретных проблем.

Методы DataContext для получения созданного SQL

Участник Назначение
Журнал Печатает SQL перед выполнением. Охватывает команды запроса, вставки, обновления и удаления. Использование:

C#

db.Log = Console.Out;

Visual Basic

db.Log = Console.Out

GetQueryText(query) Возвращает текст запроса без его выполнения. Использование:

C#

Console.WriteLine(db.GetQueryText(db.Customers));

Visual Basic

Console.WriteLine(db.GetQueryTest(db.Customers))

GetChangeText() Возвращает текст команд SQL для вставки, обновления и удаления без их выполнения. Использование:

C#

Console.WriteLine(db.GetChangeText());

Visual Basic

Console.WriteLine(db.GetChangeText())