Recuperación de datos y operaciones CUD en aplicaciones de n niveles (LINQ to SQL)

Al serializar objetos entidad, como Clientes o Pedidos, con destino a un cliente a través de una red, esas entidades se desasocian de su contexto de datos. El contexto de datos ya no realiza un seguimiento de sus cambios o sus asociaciones con otros objetos. Esto no constituye un problema mientras los clientes solo estén leyendo los datos. También es relativamente sencillo permitir a los clientes agregar nuevas filas a una base de datos. Sin embargo, si su aplicación requiere que los clientes pueden actualizar o eliminar datos, deberá asociar las entidades a un nuevo contexto de datos antes de llamar a DataContext.SubmitChanges. Además, si está utilizando una comprobación de simultaneidad optimista con valores originales, también necesitará un medio para proporcionar a la base de datos la entidad original y la entidad modificada. Los métodos Attach se utilizan para colocar las entidades en un nuevo contexto de datos después de haber sido desasociadas.

Aun cuando esté serializando objetos proxy en lugar de entidades LINQ to SQL, todavía deberá construir una entidad en la capa de acceso a datos (DAL) y asociarla a un nuevo System.Data.Linq.DataContext para enviar los datos a la base de datos.

A LINQ to SQL le resulta completamente indiferente cómo se serializan las entidades. Para obtener más información sobre cómo utilizar las herramientas de Object Relational Designer y SQLMetal para generar clases serializables mediante Windows Communication Foundation (WCF), vea Cómo: Serialización de entidades.

Nota

Solo llame a los métodos Attach para entidades nuevas o deserializadas. La única manera de desasociar una entidad de su contexto de datos original es deserializarla. Si intenta asociar una entidad no desasociada a un nuevo contexto de datos, y esa entidad todavía tiene cargadores diferidos procedentes de su contexto de datos anterior, LINQ to SQL producirá una excepción. Una entidad con cargadores diferidos de dos contextos de datos diferentes podría producir resultados no deseados al realizar operaciones de inserción, actualización y eliminación sobre esa entidad. Para obtener más información sobre los cargadores diferidos, vea Carga diferida frente a carga inmediata.

Recuperación de datos

Llamada a método del cliente

Los ejemplos siguientes muestran un ejemplo de llamada a método en la capa DAL desde un cliente de Windows Forms. En este ejemplo, la capa DAL se implementa como una Biblioteca de servicios de 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.  
    }  

Implementación del nivel intermedio

El ejemplo siguiente muestra una implementación del método de interfaz en el nivel intermedio. Deberá tener en cuenta los dos puntos principales siguientes:

  • El objeto DataContext se declara en el ámbito del método.

  • El método devuelve una colección IEnumerable de los resultados reales. El serializador ejecutará la consulta para devolver los resultados al nivel de presentación o cliente. Para obtener acceso localmente a los resultados de la consulta en el nivel intermedio, puede forzar la ejecución llamando a ToList o ToArray para la variable de consulta. A continuación, puede devolver esa lista o matriz como un 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();
}  

Una instancia de un contexto de datos debe tener una duración de una "unidad de trabajo". En un entorno de acoplamiento flexible, una unidad de trabajo suele ser pequeña, quizás una transacción optimista, incluida una sola llamada a SubmitChanges. Por consiguiente, el contexto de los datos se crea y se deshace en el ámbito del método. Si la unidad de trabajo incluye llamadas a la lógica de reglas de empresa, entonces generalmente deseará mantener la instancia de DataContext para esa operación completa. En cualquier caso, las instancias de DataContext no deberían mantenerse activas durante largos períodos de tiempo a lo largo de un número arbitrario de transacciones.

Este método devolverá objetos Product, pero no la colección de los objetos Order_Detail asociados a cada objeto Product. Use el objeto DataLoadOptions para cambiar este comportamiento predeterminado. Para obtener más información, consulte Cómo: Controlar la cantidad de datos relacionados que se recupera.

Insertar datos

Para insertar un nuevo objeto, el nivel de presentación simplemente llama al método pertinente en la interfaz de nivel intermedio y pasa el nuevo objeto que se va a insertar. En algunos casos, puede ser más eficiente para el cliente pasar solo algunos valores y hacer que el nivel intermedio construya el objeto completo.

Implementación del nivel intermedio

En el nivel intermedio, se crea un nuevo DataContext, se asocia el objeto al DataContext mediante el método InsertOnSubmit, y se inserta el objeto al llamar a SubmitChanges. Se pueden administrar excepciones, devoluciones de llamada y condiciones de error al igual que en cualquier otro escenario de servicio Web.

' 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();  
    }  

Eliminar datos

Para eliminar un objeto existente de la base de datos, el nivel de presentación llama al método pertinente en la interfaz de nivel intermedio y pasa su copia que incluye los valores originales del objeto que se va a eliminar.

Las operaciones de eliminación implican comprobaciones de simultaneidad optimista, y el objeto que se va a eliminar debe estar primero asociado al nuevo contexto de datos. En este ejemplo, el parámetro Boolean está establecido en false para indicar que el objeto no tiene marca de tiempo (RowVersion). Si la tabla de la base de datos genera marcas de tiempo para cada registro, entonces las comprobaciones de simultaneidad son mucho más simples, sobre todo para el cliente. Simplemente pase el objeto original o el objeto modificado y establezca el parámetro Boolean en true. En cualquier caso, en el nivel intermedio es necesario generalmente capturar la excepción ChangeConflictException. Para obtener más información sobre cómo administrar los conflictos de simultaneidad optimista, vea Simultaneidad optimista: información general.

Al eliminar entidades que tienen restricciones de clave externa sobre tablas asociadas, deberá eliminar primero todos los objetos en sus colecciones 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).  
    }  
}  

Actualización de datos

LINQ to SQL admite actualizaciones en estos escenarios que presentan simultaneidad optimista:

  • Simultaneidad optimista basada en marcas de tiempo o números RowVersion.

  • Simultaneidad optimista basada en valores originales de un subconjunto de propiedades de entidad.

  • Simultaneidad optimista basada en las entidades completas originales y modificadas.

Puede también realizar actualizaciones o eliminaciones conjuntas sobre una entidad y sus relaciones; por ejemplo, un Cliente y una colección de sus objetos Pedido asociados. Cuando realiza modificaciones sobre el cliente en un grafo de objetos entidad y sus colecciones (EntitySet) secundarias, y las comprobaciones de simultaneidad optimista requieren valores originales, el cliente debe proporcionar esos valores originales para cada entidad y cada objeto EntitySet<TEntity>. Si desea permitir a los clientes realizar, en una única llamada al método, un conjunto de actualizaciones, eliminaciones e inserciones relacionadas, deberá proporcionar al cliente una manera de indicar qué tipo de operación va a realizar sobre cada entidad. En el nivel intermedio, deberá llamar al método Attach apropiado y, a continuación, a InsertOnSubmit, DeleteAllOnSubmit o InsertOnSubmit (sin Attach, para las inserciones) para cada entidad antes de llamar a SubmitChanges. No recupere datos de la base de datos como una manera de obtener valores originales antes de probar las actualizaciones.

Para obtener más información sobre simultaneidad optimista, vea Simultaneidad optimista: información general. Para obtener información detallada sobre cómo resolver conflictos de cambio de simultaneidad optimista, vea Cómo: Administrar conflictos de cambios.

Los ejemplos siguientes explican los distintos escenarios:

Simultaneidad optimista con marcas de tiempo

' 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).  
}  

Con subconjunto de valores originales

En este enfoque, el cliente devuelve el objeto completo serializado, junto con los valores que se van a modificar.

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).  
        }  
    }  
}  

Con entidades completas

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)/  
        }
    }  
}  

Para actualizar una colección, llame a AttachAll en lugar de a Attach.

Miembros de entidad esperados

Como ya se ha dicho anteriormente, solo es necesario establecer ciertos miembros del objeto entidad antes de llamar a los métodos Attach. Los miembros de entidad que es necesario establecer deben cumplir los siguientes criterios:

  • Formar parte de la identidad de la entidad.

  • Se debe esperar que sean modificados.

  • Ser una marca de tiempo o tener su atributo UpdateCheck establecido en un valor distinto de Never.

Si una tabla utiliza una marca de tiempo o un número de versión para una comprobación de simultaneidad optimista, se deberán establecer esos miembros antes de llamar a Attach. Un miembro está designado para la comprobación de simultaneidad optimista cuando la propiedad IsVersion está establecida como verdadera (true) para ese atributo de columna. Cualquier actualización solicitada solo se enviará si los valores de número de versión o marca de tiempo son los mismos en la base de datos.

Un miembro también se utiliza en la comprobación de simultaneidad optimista siempre que el miembro no tenga la propiedad UpdateCheck establecida con el valor Never. El valor predeterminado es Always si no se especifica ningún otro valor.

Si falta cualquiera de estos miembros necesarios, se producirá una excepción ChangeConflictException durante SubmitChanges ("Fila no encontrada o cambiada").

State

Una vez que un objeto entidad se asocia a la instancia DataContext, el objeto se considerará en el estado PossiblyModified. Existen tres maneras de obligar a que un objeto asociado se considere Modified.

  1. Asociarlo como no modificado y, a continuación, modificar directamente los campos.

  2. Asociarlo mediante la sobrecarga Attach que acepta instancias de objeto actuales y originales. Esto proporciona al seguidor de cambios valores antiguos y nuevos para que sepa automáticamente qué campos han cambiado.

  3. Asociarlo mediante la sobrecarga Attach que acepta un segundo parámetro booleano (con el valor true). Esto indicará al seguidor de cambios que considere el objeto modificado sin tener que proporcionar ningún valor original. En este enfoque, el objeto debe tener un campo de versión o marca de tiempo.

Para obtener más información, vea Estados de objeto y Seguimiento de cambios.

Si ya existe un objeto entidad en la caché de identificadores con la misma identidad que el objeto que se va a asociar, se producirá la excepción DuplicateKeyException.

Al asociar con un conjunto de objetos IEnumerable, se producirá una excepción DuplicateKeyException cuando esté presente una clave existente. Los objetos restantes no se asociarán.

Consulte también