Извлечение данных и операции создания, обновления и удаления в 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 ("Строка не найдена или изменена").

State

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

  1. Присоединить объект как неизмененный и затем изменить непосредственно поля.

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

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

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

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

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

См. также