Операции извлечения данных и CUD в приложениях n-уровней (LINQ to SQL)

При сериализации сущностей, например, "Customers" или "Orders", клиенту по сети эти сущности отсоединяются от своего контекста данных. Контекст данных более не отслеживает их изменения или их связи с другими объектами. Это не вызывает проблемы, если клиент осуществляет только чтение данных. Добавление клиентам возможности добавлять новые строки в базу данных также является относительно простым. Однако если приложению требуется, чтобы клиенты имели возможность обновлять или удалять данные, то перед вызовом метода DataContext.SubmitChanges необходимо присоединить сущности к новому контексту данных. Кроме того, если вы используете оптимистическую проверку параллелизма с исходными значениями, вам также потребуется способ предоставить базе данных как исходную, так и изменённую сущность. Эти Attach методы предоставляются для включения сущностей в новый контекст данных после их отсоединения.

Даже если вы сериализируете прокси-объекты вместо сущностей LINQ to SQL, необходимо создать сущность на уровне доступа к данным (DAL) и подключить его к новой System.Data.Linq.DataContext, чтобы отправить данные в базу данных.

LINQ to SQL полностью равнодушен к сериализации сущностей. Дополнительные сведения об использовании средств реляционный конструктор объектов и SQLMetal для создания классов, сериализуемых с помощью Windows Communication Foundation (WCF), см. в статье "Практическое руководство. Создание сериализуемых сущностей".

Примечание.

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

Получить данные

Вызов клиентского метода

В следующих примерах показан метод вызова компонента DAL из клиента Windows Forms. В данном примере компонент DAL реализован в качестве библиотеки служб Windows.

Private Function GetProdsByCat_Click(ByVal sender As Object, ByVal e _
    As EventArgs)

    ' Create the WCF client proxy.
    Dim proxy As New NorthwindServiceReference.Service1Client

    ' Call the method on the service.
    Dim products As NorthwindServiceReference.Product() = _
        proxy.GetProductsByCategory(1)

    ' If the database uses original values for concurrency checks,
    ' the client needs to store them and pass them back to the
    ' middle tier along with the new values when updating data.

    For Each v As NorthwindClient1.NorthwindServiceReference.Product _
        In products
        ' Persist to a List(Of Product) declared at class scope.
        ' Additional change-tracking logic is the responsibility
        ' of the presentation tier and/or middle tier.
        originalProducts.Add(v)
    Next

    ' (Not shown) Bind the products list to a control
    ' and/or perform whatever processing is necessary.
End Function
private void GetProdsByCat_Click(object sender, EventArgs e)
{
    // Create the WCF client proxy.
    NorthwindServiceReference.Service1Client proxy =
    new NorthwindClient.NorthwindServiceReference.Service1Client();

    // Call the method on the service.
    NorthwindServiceReference.Product[] products =
    proxy.GetProductsByCategory(1);

    // If the database uses original values for concurrency checks,
    // the client needs to store them and pass them back to the
    // middle tier along with the new values when updating data.
    foreach (var v in products)
    {
        // Persist to a list<Product> declared at class scope.
        // Additional change-tracking logic is the responsibility
        // of the presentation tier and/or middle tier.
        originalProducts.Add(v);
    }

    // (Not shown) Bind the products list to a control
    // and/or perform whatever processing is necessary.
    }

Реализация промежуточного уровня

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

  • Класс DataContext объявляется в области действия метода.
  • Метод возвращает коллекцию IEnumerable, содержащую фактические результаты. Для отправки результатов обратно на клиентский уровень или уровень представления данных сериализатор выполняет запрос. Для получения доступа к результатам запроса локально на среднем уровне можно принудительно выполнить запрос, вызвав метод ToList или ToArray для переменной запроса. После этого можно возвратить этот список или массив в виде IEnumerable.
Public Function GetProductsByCategory(ByVal categoryID As Integer) _
    As IEnumerable(Of Product)

    Dim db As New NorthwindClasses1DataContext(connectionString)
    Dim productQuery = _
    From prod In db.Products _
    Where prod.CategoryID = categoryID _
    Select prod

    Return productQuery.AsEnumerable()

End Function
public IEnumerable<Product> GetProductsByCategory(int categoryID)
{
    NorthwindClasses1DataContext db =
    new NorthwindClasses1DataContext(connectionString);

    IEnumerable<Product> productQuery =
    from prod in db.Products
    where prod.CategoryID == categoryID
    select prod;

    return productQuery.AsEnumerable();
}

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

Данный метод возвращает объекты "Product", а не коллекцию объектов "Order_Detail", связанных с каждым объектом "Product". Для изменения данного поведения, установленного по умолчанию, используйте объект DataLoadOptions. Дополнительные сведения см. в разделе "Практическое руководство. Управление получением связанных данных".

Вставка данных

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

Реализация промежуточного уровня

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

' No call to Attach is necessary for inserts.
Public Sub InsertOrder(ByVal o As Order)

    Dim db As New NorthwindClasses1DataContext(connectionString)
    db.Orders.InsertOnSubmit(o)

    ' Exception handling not shown.
    db.SubmitChanges()

End Sub
// No call to Attach is necessary for inserts.
    public void InsertOrder(Order o)
    {
        NorthwindClasses1DataContext db = new NorthwindClasses1DataContext(connectionString);
        db.Orders.InsertOnSubmit(o);

        // Exception handling not shown.
        db.SubmitChanges();
    }

Удаление данных

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

В состав операций удаления входят проверки оптимистического параллелизма, а удаляемый объект должен сначала быть присоединен к новому контексту данных. В данном примере для параметра Boolean устанавливается значение false, которое указывает, что объект не имеет отметки времени (столбца "RowVersion"). Если таблица базы данных создает отметки времени для каждой записи, проверки параллелизма выполняются гораздо проще, особенно для клиента. Для этого достаточно передать исходный или измененный объект и установить для параметра Boolean значение true. В любом случае на среднем уровне, как правило, требуется перехватывать ChangeConflictException. Дополнительные сведения о том, как обрабатывать конфликты оптимистического параллелизма, см. в разделе "Обзор оптимистического параллелизма".

При удалении сущностей, которые имеют ограничения внешних ключей на связанных таблицах, необходимо сначала удалить все объекты в их коллекциях EntitySet<TEntity>.

' Attach is necessary for deletes.
Public Sub DeleteOrder(ByVal order As Order)
    Dim db As New NorthwindClasses1DataContext(connectionString)

    db.Orders.Attach(order, False)
    ' This will throw an exception if the order has order details.
    db.Orders.DeleteOnSubmit(order)

    Try
        ' ConflictMode is an optional parameter.
        db.SubmitChanges(ConflictMode.ContinueOnConflict)

    Catch ex As ChangeConflictException
        ' Get conflict information, and take actions
        ' that are appropriate for your application.
        ' See MSDN Article "How to: Manage Change
        ' Conflicts (LINQ to SQL).

    End Try
End Sub
// Attach is necessary for deletes.
public void DeleteOrder(Order order)
{
    NorthwindClasses1DataContext db = new NorthwindClasses1DataContext(connectionString);

    db.Orders.Attach(order, false);
    // This will throw an exception if the order has order details.
    db.Orders.DeleteOnSubmit(order);
    try
    {
        // ConflictMode is an optional parameter.
        db.SubmitChanges(ConflictMode.ContinueOnConflict);
    }
    catch (ChangeConflictException e)
    {
       // Get conflict information, and take actions
       // that are appropriate for your application.
       // See MSDN Article How to: Manage Change Conflicts (LINQ to SQL).
    }
}

Обновление данных

LINQ to SQL поддерживает обновления в сценариях с использованием оптимистического параллелизма:

  • Оптимистический параллелизм, основанный на метках времени (или значениях столбца "RowVersion").
  • Оптимистичная конкурентность, основанная на исходных значениях подмножества свойств сущности.
  • Оптимистический параллелизм, основанный на полных исходных и измененных сущностях.

Можно также выполнять операции обновления или удаления сущности вместе с ее связями, например обновить или удалить объект "Customer" и коллекцию связанных с ним объектов "Order". Если при внесении изменений в граф объектов сущностей и их дочерних коллекций (EntitySet) на клиенте для проверок оптимистического параллелизма требуются исходные значения, то клиенту необходимо предоставить эти исходные значения для каждой сущности и объекта EntitySet<TEntity>. Если необходимо предоставить клиенту возможность выполнить набор связанных операций удаления, обновления и вставки в одном методе, следует предоставить клиенту способ указания типа операции, которую необходимо выполнить для каждой сущности. На среднем уровне необходимо вызвать соответствующий метод Attach, а затем для каждой сущности вызвать InsertOnSubmit, DeleteAllOnSubmit или InsertOnSubmit (без Attach для операций вставки), перед тем как вызвать SubmitChanges. Не используйте извлечение данных из базы данных в качестве способа получения исходных значений перед обновлением.

Дополнительные сведения об оптимистическом параллелизме см. в разделе "Оптимистический параллелизм". Подробные сведения об устранении конфликтов изменений оптимистического параллелизма см. в разделе «Инструкция по управлению конфликтами изменений».

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

Оптимистический параллелизм на основе меток времени

' Assume that "customer" has been sent by client.
' Attach with "true" to say this is a modified entity
' and it can be checked for optimistic concurrency
' because it has a column that is marked with the
' "RowVersion" attribute.

db.Customers.Attach(customer, True)

Try
    ' Optional: Specify a ConflictMode value
    ' in call to SubmitChanges.
    db.SubmitChanges()
Catch ex As ChangeConflictException
    ' Handle conflict based on options provided.
    ' See MSDN article "How to: Manage Change
    ' Conflicts (LINQ to SQL)".
End Try
// Assume that "customer" has been sent by client.
// Attach with "true" to say this is a modified entity
// and it can be checked for optimistic concurrency because
//  it has a column that is marked with "RowVersion" attribute
db.Customers.Attach(customer, true)
try
{
    // Optional: Specify a ConflictMode value
    // in call to SubmitChanges.
    db.SubmitChanges();
}
catch(ChangeConflictException e)
{
    // Handle conflict based on options provided
    // See MSDN article How to: Manage Change Conflicts (LINQ to SQL).
}

С подмножеством исходных значений

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

Public Sub UpdateProductInventory(ByVal p As Product, ByVal _
    unitsInStock As Short?, ByVal unitsOnOrder As Short?)

    Using db As New NorthwindClasses1DataContext(connectionString)
        ' p is the original unmodified product
        ' that was obtained from the database.
        ' The client kept a copy and returns it now.
        db.Products.Attach(p, False)

        ' Now that the original values are in the data context,
        ' apply the changes.
        p.UnitsInStock = unitsInStock
        p.UnitsOnOrder = unitsOnOrder

        Try
            ' Optional: Specify a ConflictMode value
            ' in call to SubmitChanges.
            db.SubmitChanges()

        Catch ex As Exception
            ' Handle conflict based on options provided.
            ' See MSDN article "How to: Manage Change Conflicts
            ' (LINQ to SQL)".
        End Try
    End Using
End Sub
public void UpdateProductInventory(Product p, short? unitsInStock, short? unitsOnOrder)
{
    using (NorthwindClasses1DataContext db = new NorthwindClasses1DataContext(connectionString))
    {
        // p is the original unmodified product
        // that was obtained from the database.
        // The client kept a copy and returns it now.
        db.Products.Attach(p, false);

        // Now that the original values are in the data context, apply the changes.
        p.UnitsInStock = unitsInStock;
        p.UnitsOnOrder = unitsOnOrder;
        try
        {
             // Optional: Specify a ConflictMode value
             // in call to SubmitChanges.
             db.SubmitChanges();
        }
        catch (ChangeConflictException e)
        {
            // Handle conflict based on provided options.
            // See MSDN article How to: Manage Change Conflicts
            // (LINQ to SQL).
        }
    }
}

С полными сущностями

Public Sub UpdateProductInfo(ByVal newProd As Product, ByVal _
    originalProd As Product)

    Using db As New NorthwindClasses1DataContext(connectionString)
        db.Products.Attach(newProd, originalProd)

        Try
            ' Optional: Specify a ConflictMode value
            ' in call to SubmitChanges.
            db.SubmitChanges()

        Catch ex As Exception
            ' Handle potential change conflict in whatever way
            ' is appropriate for your application.
            ' For more information, see the MSDN article
            ' "How to: Manage Change Conflicts (LINQ to
            ' SQL)".
        End Try

    End Using
End Sub
public void UpdateProductInfo(Product newProd, Product originalProd)
{
     using (NorthwindClasses1DataContext db = new
        NorthwindClasses1DataContext(connectionString))
     {
         db.Products.Attach(newProd, originalProd);
         try
         {
               // Optional: Specify a ConflictMode value
               // in call to SubmitChanges.
               db.SubmitChanges();
         }
        catch (ChangeConflictException e)
        {
            // Handle potential change conflict in whatever way
            // is appropriate for your application.
            // For more information, see the MSDN article
            // How to: Manage Change Conflicts (LINQ to SQL)/
        }
    }
}

Для обновления коллекции вместо метода AttachAll вызовите метод Attach.

Ожидаемые члены сущности

Как уже указывалось выше, перед вызовом методов Attach требуется установить только некоторые члены объекта сущности. Критерии, которым должны соответствовать члены сущности, подлежащие установке, приведены ниже.

  • Станьте частью идентичности сущности.
  • Ожидается, что их изменят.
  • Они должны представлять собой метку времени или для их атрибута UpdateCheck должно быть установлено значение, отличное от Never.

Если для проверки оптимистического параллелизма в таблице используется метка времени или номер версии, то перед вызовом метода Attach необходимо установить значения этих членов. Член предназначен для проверки оптимистического параллелизма в том случае, если свойства IsVersion данного атрибута столбца установлено значение "true". Любые запрошенные обновления будут отправлены только в том случае, если значения номера версии или метки времени совпадают в базе данных.

Кроме того, член используется для проверки оптимистического параллелизма в том случае, если для свойства UpdateCheck данного члена не установлено значение Never. Значение по умолчанию — Always, если не указано иное значение.

Если один из этих обязательных членов отсутствует, то во время ChangeConflictException выбрасывается SubmitChanges ("Строка не найдена или изменена").

Штат

После того как объект сущности присоединен к экземпляру DataContext, состоянием объекта считается значение PossiblyModified. Существует три способа принудительно считать присоединённый объект как Modified.

  1. Присоедините в неизмененном виде, а затем непосредственно измените поля.

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

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

Дополнительные сведения см. в разделе "Состояния объектов" и "Отслеживание изменений".

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

Если при присоединении набора объектов IEnumerable уже существует совпадающий ключ, вызывается исключение DuplicateKeyException. Оставшиеся объекты не прикреплены.

См. также