Control de la simultaneidad con Entity Framework 4.0 en una aplicación web de ASP.NET 4

Por Tom Dykstra

Esta serie de tutoriales se basa en la aplicación web Contoso University, creada por la serie de tutoriales Introducción a Entity Framework 4.0. Si no completó los tutoriales anteriores, como punto de partida para este tutorial, puede descargar la aplicación que habría creado. También puede descargar la aplicación creada por la serie de tutoriales completa. Si tiene preguntas sobre los tutoriales, puede publicarlas en el foro de ASP.NET Entity Framework.

En el tutorial anterior ha aprendido a ordenar y filtrar datos usando el control ObjectDataSource y Entity Framework. En este tutorial se muestran las opciones para controlar la simultaneidad en una aplicación web de ASP.NET que usa Entity Framework. Creará una nueva página web dedicada a actualizar las asignaciones de oficina de instructor. Controlará los problemas de simultaneidad en esa página y en la página Departamentos que creó anteriormente.

Image06

Image01

Conflictos de simultaneidad

Un conflicto de simultaneidad se produce cuando un usuario edita un registro y otro usuario edita el mismo registro antes de que se escriba el primer cambio del usuario en la base de datos. Si no configura Entity Framework para detectar estos conflictos, quien actualice la base de datos por última vez sobrescribirá los cambios del otro usuario. En muchas aplicaciones, este riesgo es aceptable y no es necesario configurar la aplicación para controlar posibles conflictos de simultaneidad (si hay pocos usuarios o pocas actualizaciones, o si no es realmente importante si se sobrescriben algunos cambios, el costo de programación para la simultaneidad puede superar el beneficio obtenido). Si no necesita preocuparse por los conflictos de simultaneidad, puede omitir este tutorial; los dos tutoriales restantes de la serie no dependen de nada de lo que crea en este.

Simultaneidad pesimista (bloqueo)

Si la aplicación necesita evitar la pérdida accidental de datos en casos de simultaneidad, una manera de hacerlo es usar los bloqueos de base de datos. Esto se denomina simultaneidad pesimista. Por ejemplo, antes de leer una fila de una base de datos, solicita un bloqueo de solo lectura o para acceso de actualización. Si bloquea una fila para acceso de actualización, no se permite que ningún otro usuario bloquee la fila como solo lectura o para acceso de actualización, porque recibirían una copia de los datos que se están modificando. Si bloquea una fila para acceso de solo lectura, otras personas también pueden bloquearla para acceso de solo lectura pero no para actualización.

La administración de bloqueos tiene algunas desventajas. Puede ser bastante complicado de programar. Se necesita un número significativo de recursos de administración de base de datos, y puede provocar problemas de rendimiento a medida que aumenta el número de usuarios de una aplicación (es decir, no se escala bien). Por estos motivos, no todos los sistemas de administración de bases de datos admiten la simultaneidad pesimista. Entity Framework no proporciona ninguna compatibilidad integrada para ello y en este tutorial no se muestra cómo implementarla.

Simultaneidad optimista

La alternativa a la simultaneidad pesimista es la simultaneidad optimista. La simultaneidad optimista implica permitir que se produzcan conflictos de simultaneidad y reaccionar correctamente si ocurren. Por ejemplo, John ejecuta la página Department.aspx, hace clic en el vínculo Editar del departamento de Historia y reduce el importe de Presupuesto de 1 000 000,00 USD a 125 000,00 USD (John administra un departamento de la competencia y quiere liberar dinero para su propio departamento).

Image07

Antes de que John haga clic en Actualizar, Jane ejecuta la misma página, hace clic en el vínculo Editar del departamento de Historia y, a continuación, cambia el campo Fecha de inicio del 1/10/2011 al 1/1/1999 (Jane administra el departamento de Historia y quiere darle más antigüedad).

Image08

John hace clic en Actualizar primero y, a continuación, Jane hace clic en Actualizar. El explorador de Jane ahora muestra el importe de Presupuesto 1 000 000,00 USD, pero esto es incorrecto porque John ha cambiado la cantidad a 125 000,00 USD.

Algunas de las acciones que puede realizar en este escenario son las siguientes:

  • Puede realizar un seguimiento de la propiedad que ha modificado un usuario y actualizar solo las columnas correspondientes de la base de datos. En el escenario de ejemplo, no se perdería ningún dato porque los dos usuarios actualizaron diferentes propiedades. La próxima vez que alguien explore el departamento de Historia, verá 1/1/1999 y 125 000,00 USD.

    Este es el comportamiento predeterminado en Entity Framework y puede reducir considerablemente el número de conflictos que podrían provocar la pérdida de datos. Sin embargo, este comportamiento no evita la pérdida de datos si se realizan cambios en la misma propiedad de una entidad. Además, este comportamiento no siempre es posible; cuando asigna procedimientos almacenados a un tipo de entidad, todas las propiedades de una entidad se actualizan cuando se realizan cambios en la entidad en la base de datos.

  • Puede permitir que los cambios de John sobrescriban los de Jane. Después de que Jane haga clic en Actualizar, el importe de Presupuesto vuelve a ser 1 000 000,00 USD Esto se denomina un escenario de Prevalece el cliente o Prevalece el último. (los valores del cliente tienen prioridad sobre lo que aparece en el almacén de datos).

  • Puede impedir que el cambio de Jane se actualice en la base de datos. Normalmente, mostraría un mensaje de error, le mostraría el estado actual de los datos y le permitiría volver a escribir sus cambios si todavía quiere realizarlos. Podría automatizar aún más el proceso guardando su entrada y dándole la oportunidad de volver a aplicarlo sin tener que introducirlo de nuevo. Esto se denomina un escenario de Prevalece el almacén. (Los valores del almacén de datos tienen prioridad sobre los valores enviados por el cliente).

Detección de conflictos de simultaneidad

En Entity Framework puede resolver conflictos controlando excepciones OptimisticConcurrencyException que inicie Entity Framework. Para saber cuándo se producen dichas excepciones, Entity Framework debe ser capaz de detectar conflictos. Por lo tanto, debe configurar correctamente la base de datos y el modelo de datos. Algunas opciones para habilitar la detección de conflictos son las siguientes:

  • En la base de datos, incluya una columna de tabla que se pueda usar para determinar cuándo se ha cambiado una fila. Después, puede configurar Entity Framework para incluir esa columna en la cláusula Where de los comandos Update o Delete de SQL.

    Este es el propósito de la columna Timestamp de la tabla OfficeAssignment.

    Image09

    El tipo de datos de la columna Timestamp también se denomina Timestamp. Sin embargo, la columna no contiene realmente un valor de fecha u hora, sino que es un número secuencial que se incrementa cada vez que se actualiza la fila. En un comando Update o Delete, la cláusula Where incluye el valor Timestamp original. Si otro usuario ha cambiado la fila que se está actualizando, el valor de Timestamp es diferente del valor original, por lo que la cláusula Where no devuelve ninguna fila que actualizar. Cuando Entity Framework encuentra que no se ha actualizado ninguna fila mediante el comando Update o Delete actual (es decir, cuando el número de filas afectadas es cero), lo interpreta como un conflicto de simultaneidad.

  • Configure Entity Framework para que incluya los valores originales de cada columna de la tabla en la cláusula Where de los comandos Update y Delete.

    Como se muestra en la primera opción, si algo en la fila ha cambiado desde que se leyó por primera, la cláusula Where no devolverá una fila para actualizar, lo cual Entity Framework interpreta como un conflicto de simultaneidad. Este método es tan eficaz como el uso de un campo Timestamp, pero puede ser ineficaz. Para las tablas de base de datos que tienen muchas columnas, puede dar lugar a cláusulas Where muy grandes y, en una aplicación web, puede requerir mantener grandes cantidades de estado. Mantener grandes cantidades de estado puede afectar al rendimiento de la aplicación porque requiere recursos del servidor (por ejemplo, estado de sesión) o se deben incluir en la propia página web (por ejemplo, estado de vista).

En este tutorial agregará control de errores para conflictos de simultaneidad optimista para una entidad que no tiene una propiedad de seguimiento (la entidad Department) y para una entidad que tiene una propiedad de seguimiento (la entidad OfficeAssignment).

Control de la simultaneidad optimista sin una propiedad de seguimiento

Para implementar la simultaneidad optimista para la entidad Department, que no tiene ninguna propiedad de seguimiento (Timestamp), completará las siguientes tareas:

  • Cambie el modelo de datos para habilitar el seguimiento de simultaneidad para las entidades Department.
  • En la clase SchoolRepository, controle las excepciones de simultaneidad en el método SaveChanges.
  • En la página Departments.aspx, controle las excepciones de simultaneidad mostrando un mensaje a la advertencia del usuario de que los cambios intentados no se realizaron correctamente. Después, el usuario puede ver los valores actuales y volver a intentar los cambios si aún son necesarios.

Habilitación del seguimiento de simultaneidad en el modelo de datos

En Visual Studio, abra la aplicación web Contoso University con la que estaba trabajando en el tutorial anterior de esta serie.

Abra SchoolModel.edmx y, en el diseñador de modelos de datos, haga clic con el botón derecho en la propiedad Name de la entidad Department y, a continuación, haga clic en Propiedades. En la ventana Propiedades, cambie la propiedad ConcurrencyMode a Fixed.

Image16

Haga lo mismo para las otras propiedades escalares que no son de clave principal (Budget, StartDate y Administrator). No puede hacerlo para las propiedades de navegación. Esto especifica que cada vez que Entity Framework genera un comando SQL Update o Delete para actualizar la entidad Department en la base de datos, estas columnas (con valores originales) deben incluirse en la cláusula Where. Si no se encuentra ninguna fila cuando se ejecute el comando Update o Delete, Entity Framework iniciará una excepción de simultaneidad optimista.

Guarde y cierre el modelo de datos.

Control de excepciones de simultaneidad en DAL

Abra SchoolRepository.cs y agregue la siguiente instrucción using para el espacio de nombres System.Data:

using System.Data;

Agregue el siguiente método SaveChanges NUEVO, que controla las excepciones de simultaneidad optimista:

public void SaveChanges()
{
    try
    {
        context.SaveChanges();
    }
    catch (OptimisticConcurrencyException ocex)
    {
        context.Refresh(RefreshMode.StoreWins, ocex.StateEntries[0].Entity);
        throw ocex;
    }
}

Si se produce un error de simultaneidad cuando se llama a este método, los valores de propiedad de la entidad en memoria se reemplazan por los valores que se encuentran actualmente en la base de datos. La excepción de simultaneidad se vuelve a iniciar para que la página web pueda controlarla.

En los métodos DeleteDepartment y UpdateDepartment, reemplace la llamada existente a context.SaveChanges() por una llamada a SaveChanges() para invocar el nuevo método.

Control de excepciones de simultaneidad en la capa de presentación

Abra Departments.aspx y agregue un atributo OnDeleted="DepartmentsObjectDataSource_Deleted" al control DepartmentsObjectDataSource. La etiqueta de apertura del control se parecerá al ejemplo siguiente.

<asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server" 
        TypeName="ContosoUniversity.BLL.SchoolBL" DataObjectTypeName="ContosoUniversity.DAL.Department" 
        SelectMethod="GetDepartmentsByName" DeleteMethod="DeleteDepartment" UpdateMethod="UpdateDepartment"
        ConflictDetection="CompareAllValues" OldValuesParameterFormatString="orig{0}" 
        OnUpdated="DepartmentsObjectDataSource_Updated" SortParameterName="sortExpression" 
        OnDeleted="DepartmentsObjectDataSource_Deleted" >

En el control DepartmentsGridView, especifique todas las columnas de tabla del atributo DataKeyNames, como se muestra en el ejemplo siguiente. Tenga en cuenta que esto creará campos de estado de vista muy grandes, que es una razón por la que el uso de un campo de seguimiento suele ser la manera preferida de realizar un seguimiento de los conflictos de simultaneidad.

<asp:GridView ID="DepartmentsGridView" runat="server" AutoGenerateColumns="False"
        DataSourceID="DepartmentsObjectDataSource" 
        DataKeyNames="DepartmentID,Name,Budget,StartDate,Administrator" 
        OnRowUpdating="DepartmentsGridView_RowUpdating"
        OnRowDataBound="DepartmentsGridView_RowDataBound"
        AllowSorting="True" >

Abra Departments.aspx.cs y agregue la siguiente instrucción using para el espacio de nombres System.Data:

using System.Data;

Agregue el siguiente método nuevo, al que llamará desde los controladores de eventos Updated y Deleted del control de origen de datos para controlar las excepciones de simultaneidad:

private void CheckForOptimisticConcurrencyException(ObjectDataSourceStatusEventArgs e, string function)
{
    if (e.Exception.InnerException is OptimisticConcurrencyException)
    {
        var concurrencyExceptionValidator = new CustomValidator();
        concurrencyExceptionValidator.IsValid = false;
        concurrencyExceptionValidator.ErrorMessage = 
            "The record you attempted to edit or delete was modified by another " +
            "user after you got the original value. The edit or delete operation was canceled " +
            "and the other user's values have been displayed so you can " +
            "determine whether you still want to edit or delete this record.";
        Page.Validators.Add(concurrencyExceptionValidator);
        e.ExceptionHandled = true;
    }
}

Este código comprueba el tipo de excepción y, si es una excepción de simultaneidad, el código crea dinámicamente un control CustomValidator que, a su vez, muestra un mensaje en el control ValidationSummary.

Llame al nuevo método desde el controlador de eventos Updated que agregó anteriormente. Cree también un controlador de eventos Deleted que llame al mismo método (pero que no haga nada más):

protected void DepartmentsObjectDataSource_Updated(object sender, ObjectDataSourceStatusEventArgs e)
{
    if (e.Exception != null)
    {
        CheckForOptimisticConcurrencyException(e, "update");
        // ...
    }
}

protected void DepartmentsObjectDataSource_Deleted(object sender, ObjectDataSourceStatusEventArgs e)
{
    if (e.Exception != null)
    {
        CheckForOptimisticConcurrencyException(e, "delete");
    }
}

Prueba de la simultaneidad optimista en la página Departamentos

Ejecute la página Departments.aspx.

A screenshot that shows the Departments page.

Haga clic en Editar en una fila y cambie el valor de la columna Presupuesto (recuerde que solo puede editar los registros que ha creado para este tutorial, ya que los registros de base de datos School existentes contienen algunos datos no válidos; el registro del departamento Economía es seguro para experimentar con él).

Image18

Abra una nueva ventana del explorador y vuelva a ejecutar la página (copie la dirección URL del primer cuadro de dirección de la ventana del explorador en la segunda ventana del explorador).

A screenshot that shows a new browser window ready for input.

Haga clic en Editar en la misma fila que editó anteriormente y cambie el valor de Presupuesto por otro distinto.

Image19

En la segunda ventana del explorador, haga clic en Actualizar. El importe de Presupuesto se cambia correctamente a este nuevo valor.

Image20

En la primera ventana del explorador, haga clic en Actualizar. Se produce un error en la actualización. El importe de Presupuesto se vuelve a mostrar con el valor que estableció en la segunda ventana del explorador y verá un mensaje de error.

Image21

Control de la simultaneidad optimista mediante una propiedad de seguimiento

Para controlar la simultaneidad optimista de una entidad que tiene una propiedad de seguimiento, completará las siguientes tareas:

  • Agregue procedimientos almacenados al modelo de datos para administrar entidades OfficeAssignment (las propiedades de seguimiento y los procedimientos almacenados no tienen que usarse juntos; solo se agrupan aquí a título ilustrativo).
  • Agregue métodos CRUD a DAL y BLL para las entidades OfficeAssignment, incluido el código para controlar las excepciones de simultaneidad optimista en DAL.
  • Cree una página web office-assignments.
  • Pruebe la simultaneidad optimista en la nueva página web.

Cómo agregar procedimientos almacenados OfficeAssignment al modelo de datos

Abra el archivo SchoolModel.edmx en el diseñador de modelos, haga clic con el botón derecho en la superficie de diseño y haga clic en Actualizar modelo desde la base de datos. En la pestaña Agregar del cuadro de diálogo Elija los objetos de base de datos, expanda Procedimientos almacenados y seleccione los tres procedimientos almacenados OfficeAssignment (vea la captura de pantalla siguiente) y, a continuación, haga clic en Finalizar (estos procedimientos almacenados ya estaban en la base de datos al descargarlo o crearlo mediante un script).

Image02

Haga clic con el botón derecho en la entidad OfficeAssignment y seleccione Asignación de procedimientos almacenados.

Image03

Establezca las funciones Insertar, Actualizar y Eliminar para usar sus procedimientos almacenados correspondientes. Para el parámetro OrigTimestamp de la función Update, establezca la propiedad en Timestamp y seleccione la opción Usar valor original.

Image04

Cuando Entity Framework llame al procedimiento almacenado UpdateOfficeAssignment, pasará el valor original de la columna Timestamp al parámetro OrigTimestamp. El procedimiento almacenado usa este parámetro en su cláusula Where:

ALTER PROCEDURE [dbo].[UpdateOfficeAssignment]
    @InstructorID int,
    @Location nvarchar(50),
    @OrigTimestamp timestamp
    AS
    UPDATE OfficeAssignment SET Location=@Location 
    WHERE InstructorID=@InstructorID AND [Timestamp]=@OrigTimestamp;
    IF @@ROWCOUNT > 0
    BEGIN
        SELECT [Timestamp] FROM OfficeAssignment 
            WHERE InstructorID=@InstructorID;
    END

El procedimiento almacenado también selecciona el nuevo valor de la columna Timestamp después de la actualización para que Entity Framework pueda mantener la entidad OfficeAssignment en la memoria sincronizada con la fila de base de datos correspondiente

(tenga en cuenta que el procedimiento almacenado para eliminar una asignación de oficina no tiene ningún parámetro OrigTimestamp; por esto, Entity Framework no puede comprobar que una entidad no se ha modificado antes de eliminarla).

Guarde y cierre el modelo de datos.

Cómo agregar métodos OfficeAssignment a DAL

Abra ISchoolRepository.cs y agregue los siguientes métodos CRUD para el conjunto de entidades OfficeAssignment:

IEnumerable<OfficeAssignment> GetOfficeAssignments(string sortExpression);
void InsertOfficeAssignment(OfficeAssignment OfficeAssignment);
void DeleteOfficeAssignment(OfficeAssignment OfficeAssignment);
void UpdateOfficeAssignment(OfficeAssignment OfficeAssignment, OfficeAssignment origOfficeAssignment);

Agregue los siguientes métodos nuevos a SchoolRepository.cs. En el método UpdateOfficeAssignment, llame al método local SaveChanges en lugar de llamar a context.SaveChanges.

public IEnumerable<OfficeAssignment> GetOfficeAssignments(string sortExpression)
{
    return new ObjectQuery<OfficeAssignment>("SELECT VALUE o FROM OfficeAssignments AS o", context).Include("Person").OrderBy("it." + sortExpression).ToList();
}

public void InsertOfficeAssignment(OfficeAssignment officeAssignment)
{
    context.OfficeAssignments.AddObject(officeAssignment);
    context.SaveChanges();
}

public void DeleteOfficeAssignment(OfficeAssignment officeAssignment)
{
    context.OfficeAssignments.Attach(officeAssignment);
    context.OfficeAssignments.DeleteObject(officeAssignment);
    context.SaveChanges();
}

public void UpdateOfficeAssignment(OfficeAssignment officeAssignment, OfficeAssignment origOfficeAssignment)
{
    context.OfficeAssignments.Attach(origOfficeAssignment);
    context.ApplyCurrentValues("OfficeAssignments", officeAssignment);
    SaveChanges();
}

En el proyecto de prueba, abra MockSchoolRepository.cs y agregue los siguientes métodos OfficeAssignment CRUD y de colección (el repositorio ficticio debe implementar la interfaz del repositorio o la solución no se compilará).

List<OfficeAssignment> officeAssignments = new List<OfficeAssignment>();
        
public IEnumerable<OfficeAssignment> GetOfficeAssignments(string sortExpression)
{
    return officeAssignments;
}

public void InsertOfficeAssignment(OfficeAssignment officeAssignment)
{
    officeAssignments.Add(officeAssignment);
}

public void DeleteOfficeAssignment(OfficeAssignment officeAssignment)
{
    officeAssignments.Remove(officeAssignment);
}

public void UpdateOfficeAssignment(OfficeAssignment officeAssignment, OfficeAssignment origOfficeAssignment)
{
    officeAssignments.Remove(origOfficeAssignment);
    officeAssignments.Add(officeAssignment);
}

Cómo agregar métodos OfficeAssignment a BLL

En el proyecto principal, abra SchoolBL.cs y agréguele los siguientes métodos CRUD para el conjunto de entidades OfficeAssignment:

public IEnumerable<OfficeAssignment> GetOfficeAssignments(string sortExpression)
{
    if (string.IsNullOrEmpty(sortExpression)) sortExpression = "Person.LastName";
    return schoolRepository.GetOfficeAssignments(sortExpression);
}

public void InsertOfficeAssignment(OfficeAssignment officeAssignment)
{
    try
    {
        schoolRepository.InsertOfficeAssignment(officeAssignment);
    }
    catch (Exception ex)
    {
        //Include catch blocks for specific exceptions first,
        //and handle or log the error as appropriate in each.
        //Include a generic catch block like this one last.
        throw ex;
    }
}

public void DeleteOfficeAssignment(OfficeAssignment officeAssignment)
{
    try
    {
        schoolRepository.DeleteOfficeAssignment(officeAssignment);
    }
    catch (Exception ex)
    {
        //Include catch blocks for specific exceptions first,
        //and handle or log the error as appropriate in each.
        //Include a generic catch block like this one last.
        throw ex;
    }
}

public void UpdateOfficeAssignment(OfficeAssignment officeAssignment, OfficeAssignment origOfficeAssignment)
{
    try
    {
        schoolRepository.UpdateOfficeAssignment(officeAssignment, origOfficeAssignment);
    }
    catch (Exception ex)
    {
        //Include catch blocks for specific exceptions first,
        //and handle or log the error as appropriate in each.
        //Include a generic catch block like this one last.
        throw ex;
    }
}

Creación de una página web OfficeAssignments

Cree una página web que use la página maestra Site.Master y asígnele el nombre OfficeAssignments.aspx. Agregue el marcado siguiente al control Content denominado Content2:

<h2>Office Assignments</h2>
    <asp:ObjectDataSource ID="OfficeAssignmentsObjectDataSource" runat="server" TypeName="ContosoUniversity.BLL.SchoolBL"
        DataObjectTypeName="ContosoUniversity.DAL.OfficeAssignment" SelectMethod="GetOfficeAssignments"
        DeleteMethod="DeleteOfficeAssignment" UpdateMethod="UpdateOfficeAssignment" ConflictDetection="CompareAllValues"
        OldValuesParameterFormatString="orig{0}"
        SortParameterName="sortExpression"  OnUpdated="OfficeAssignmentsObjectDataSource_Updated">
    </asp:ObjectDataSource>
    <asp:ValidationSummary ID="OfficeAssignmentsValidationSummary" runat="server" ShowSummary="true"
        DisplayMode="BulletList" Style="color: Red; width: 40em;" />
    <asp:GridView ID="OfficeAssignmentsGridView" runat="server" AutoGenerateColumns="False"
        DataSourceID="OfficeAssignmentsObjectDataSource" DataKeyNames="InstructorID,Timestamp"
        AllowSorting="True">
        <Columns>
            <asp:CommandField ShowEditButton="True" ShowDeleteButton="True" ItemStyle-VerticalAlign="Top">
                <ItemStyle VerticalAlign="Top"></ItemStyle>
            </asp:CommandField>
            <asp:TemplateField HeaderText="Instructor" SortExpression="Person.LastName">
                <ItemTemplate>
                    <asp:Label ID="InstructorLastNameLabel" runat="server" Text='<%# Eval("Person.LastName") %>'></asp:Label>,
                    <asp:Label ID="InstructorFirstNameLabel" runat="server" Text='<%# Eval("Person.FirstMidName") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:DynamicField DataField="Location" HeaderText="Location" SortExpression="Location"/>
        </Columns>
        <SelectedRowStyle BackColor="LightGray"></SelectedRowStyle>
    </asp:GridView>

Observe que en el atributo DataKeyNames, el marcado especifica la propiedad Timestamp, así como la clave de registro (InstructorID). La especificación de propiedades en el atributo DataKeyNames hace que el control los guarde en estado de control (que es similar al estado de vista) para que los valores originales estén disponibles durante el procesamiento de postback.

Si no guardara el valor Timestamp, Entity Framework no lo tendría para la cláusula Where del comando SQL Update. Por lo tanto, no se encontraría nada para actualizar. Como resultado, Entity Framework produciría una excepción de simultaneidad optimista cada vez que se actualizase una entidad OfficeAssignment.

Abra OfficeAssignments.aspx.cs y agregue la siguiente instrucción using para la capa de acceso a datos:

using ContosoUniversity.DAL;

Agregue el siguiente método Page_Init, que habilita la funcionalidad Datos dinámicos. Agregue también el siguiente controlador para el evento Updated del control ObjectDataSource para comprobar si hay errores de simultaneidad:

protected void Page_Init(object sender, EventArgs e)
{
    OfficeAssignmentsGridView.EnableDynamicData(typeof(OfficeAssignment));
}

protected void OfficeAssignmentsObjectDataSource_Updated(object sender, ObjectDataSourceStatusEventArgs e)
{
    if (e.Exception != null)
    {
        var concurrencyExceptionValidator = new CustomValidator();
        concurrencyExceptionValidator.IsValid = false;
        concurrencyExceptionValidator.ErrorMessage = "The record you attempted to " +
            "update has been modified by another user since you last visited this page. " +
            "Your update was canceled to allow you to review the other user's " +
            "changes and determine if you still want to update this record.";
        Page.Validators.Add(concurrencyExceptionValidator);
        e.ExceptionHandled = true;
    }
}

Prueba de la simultaneidad optimista en la página OfficeAssignments

Ejecute la página OfficeAssignments.aspx.

A screenshot that shows the Office Assignments page.

Haga clic en Editar en una fila y cambie el valor de la columna Ubicación.

Image11

Abra una nueva ventana del explorador y vuelva a ejecutar la página (copie la dirección URL de la primera ventana del explorador a la segunda ventana del explorador).

A screenshot that shows a new browser window.

Haga clic en Editar en la misma fila que editó anteriormente y cambie el valor de Ubicación por otro distinto.

Image12

En la segunda ventana del explorador, haga clic en Actualizar.

Image13

Cambie a la primera ventana del explorador y haga clic en Actualizar.

Image15

Verá un mensaje de error y el valor de Ubicación se ha actualizado para mostrar el valor al que cambió en la segunda ventana del explorador.

Control de simultaneidad con el control EntityDataSource

El control EntityDataSource incluye una lógica integrada que reconoce la configuración de simultaneidad en el modelo de datos y controla las operaciones de actualización y eliminación en consecuencia. Sin embargo, al igual que con todas las excepciones, debe controlar las excepciones OptimisticConcurrencyException usted mismo para proporcionar un mensaje de error descriptivo.

A continuación, configurará la página Courses.aspx (que usa un control EntityDataSource) para permitir operaciones de actualización y eliminación y mostrar un mensaje de error si se produce un conflicto de simultaneidad. La entidad Course no tiene ninguna columna de seguimiento de simultaneidad, por lo que usará el mismo método que hizo con la entidad Department: realizar un seguimiento de los valores de todas las propiedades que no sean clave.

Abra el archivo SchoolModel.edmx. Para las propiedades que no sean clave de la entidad Course (Title, Credits y DepartmentID), establezca la propiedad Modo de simultaneidad en Fixed. A continuación, guarde y cierre el modelo de datos.

Abra la página Courses.aspx y realice los cambios siguientes:

  • En el control CoursesEntityDataSource, agregue atributos EnableUpdate="true" y EnableDelete="true". La etiqueta de apertura de ese control ahora es similar al ejemplo siguiente:

    <asp:EntityDataSource ID="CoursesEntityDataSource" runat="server" 
            ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="false" 
            AutoGenerateWhereClause="True" EntitySetName="Courses"
            EnableUpdate="true" EnableDelete="true">
    
  • En el control CoursesGridView, cambie el valor del atributo DataKeyNames a "CourseID,Title,Credits,DepartmentID". A continuación, agregue un elemento CommandField al elemento Columns que muestre los botones Editar y Eliminar (<asp:CommandField ShowEditButton="True" ShowDeleteButton="True" />). El control GridView ahora es similar al ejemplo siguiente:

    <asp:GridView ID="CoursesGridView" runat="server" AutoGenerateColumns="False" 
            DataKeyNames="CourseID,Title,Credits,DepartmentID"
            DataSourceID="CoursesEntityDataSource" >
            <Columns>
                <asp:CommandField ShowEditButton="True" ShowDeleteButton="True" />
                <asp:BoundField DataField="CourseID" HeaderText="CourseID" ReadOnly="True" SortExpression="CourseID" />
                <asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" />
                <asp:BoundField DataField="Credits" HeaderText="Credits" SortExpression="Credits" />
            </Columns>
        </asp:GridView>
    

Ejecute la página y cree una situación de conflicto como hizo antes en la página Departamentos. Ejecute la página en dos ventanas del explorador, haga clic en Editar en la misma línea de cada ventana y realice un cambio diferente en cada una. Haga clic en Actualizar en una ventana y, a continuación, haga clic en Actualizar en la otra ventana. Al hacer clic en Actualizar la segunda vez, verá la página de error resultante de una excepción de simultaneidad no controlada.

Image22

Este error se controla de forma muy similar a cómo lo hizo para el control ObjectDataSource. Abra la página Courses.aspx y, en el control CoursesEntityDataSource, especifique controladores para los eventos Deleted y Updated. La etiqueta de apertura del control ahora es similar al ejemplo siguiente:

<asp:EntityDataSource ID="CoursesEntityDataSource" runat="server" 
        ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="false"
        AutoGenerateWhereClause="true" EntitySetName="Courses" 
        EnableUpdate="true" EnableDelete="true" 
        OnDeleted="CoursesEntityDataSource_Deleted" 
        OnUpdated="CoursesEntityDataSource_Updated">

Antes del control CoursesGridView, agregue el siguiente control ValidationSummary:

<asp:ValidationSummary ID="CoursesValidationSummary" runat="server" 
        ShowSummary="true" DisplayMode="BulletList"  />

En Courses.aspx.cs, agregue una instrucción using para el espacio de nombres System.Data, agregue un método que compruebe si hay excepciones de simultaneidad y agregue controladores para los controladores Updated y Deleted del control EntityDataSource. El código tendrá un aspecto similar al siguiente:

using System.Data;
protected void CoursesEntityDataSource_Updated(object sender, EntityDataSourceChangedEventArgs e)
{
    CheckForOptimisticConcurrencyException(e, "update");
}

protected void CoursesEntityDataSource_Deleted(object sender, EntityDataSourceChangedEventArgs e)
{
    CheckForOptimisticConcurrencyException(e, "delete");
}

private void CheckForOptimisticConcurrencyException(EntityDataSourceChangedEventArgs e, string function)
{
    if (e.Exception != null && e.Exception is OptimisticConcurrencyException)
    {
        var concurrencyExceptionValidator = new CustomValidator();
        concurrencyExceptionValidator.IsValid = false;
        concurrencyExceptionValidator.ErrorMessage = 
            "The record you attempted to edit or delete was modified by another " +
            "user after you got the original value. The edit or delete operation was canceled " +
            "and the other user's values have been displayed so you can " +
            "determine whether you still want to edit or delete this record.";
        Page.Validators.Add(concurrencyExceptionValidator);
        e.ExceptionHandled = true;
    }
}

La única diferencia entre este código y lo que hizo para el control ObjectDataSource es que, en este caso, la excepción de simultaneidad se encuentra en la propiedad Exception del objeto arguments del evento, en lugar de en la propiedad InnerException de esa excepción.

Ejecute la página y vuelva a crear un conflicto de simultaneidad. Esta vez verá un mensaje de error:

Image23

Con esto finaliza la introducción para el control de los conflictos de simultaneidad. En el siguiente tutorial se proporcionan instrucciones sobre cómo mejorar el rendimiento en una aplicación web que usa Entity Framework.