Gestione della concorrenza con Entity Framework 4.0 in un'applicazione Web ASP.NET 4

di Tom Dykstra

Questa serie di esercitazioni si basa sull'applicazione Web Contoso University creata dalla Introduzione con la serie di esercitazioni di Entity Framework 4.0. Se non sono state completate le esercitazioni precedenti, come punto di partenza per questa esercitazione è possibile scaricare l'applicazione creata. È anche possibile scaricare l'applicazione creata dalla serie completa di esercitazioni. Se si hanno domande sulle esercitazioni, è possibile pubblicarli nel forum di Entity Framework ASP.NET.

Nell'esercitazione precedente si è appreso come ordinare e filtrare i dati usando il ObjectDataSource controllo e Entity Framework. Questa esercitazione illustra le opzioni per la gestione della concorrenza in un'applicazione Web ASP.NET che usa Entity Framework. Verrà creata una nuova pagina Web dedicata all'aggiornamento delle assegnazioni di ufficio dell'insegnante. Si gestiranno i problemi di concorrenza in tale pagina e nella pagina Reparti creati in precedenza.

Image06

Immagine01

Conflitti di concorrenza

Si verifica un conflitto di concorrenza quando un utente modifica un record e un altro utente modifica lo stesso record prima che la modifica del primo utente venga scritta nel database. Se non si configura Entity Framework per rilevare tali conflitti, chiunque aggiorni l'ultimo database sovrascrive le modifiche dell'altro utente. In molte applicazioni questo rischio è accettabile e non è necessario configurare l'applicazione per gestire possibili conflitti di concorrenza. Se sono presenti alcuni utenti o alcuni aggiornamenti o se non è davvero critico se alcune modifiche vengono sovrascritte, il costo della programmazione per la concorrenza potrebbe superare il vantaggio. Se non è necessario preoccuparsi dei conflitti di concorrenza, è possibile ignorare questa esercitazione; le due esercitazioni rimanenti nella serie non dipendono da nulla che si compila in questo.

Concorrenza pessimistica (blocco)

Se è importante che l'applicazione eviti la perdita accidentale di dati in scenari di concorrenza, un metodo per garantire che ciò accada è l'uso dei blocchi di database. Questo è chiamato concorrenza pessimistica. Ad esempio, prima di leggere una riga da un database si richiede un blocco per l'accesso di sola lettura o per l'accesso in modalità aggiornamento. Se si blocca una riga per l'accesso di aggiornamento, nessun altro utente può bloccare la riga per l'accesso di sola lettura o di aggiornamento, perché riceverebbe una copia di dati dei quali è in corso la modifica. Se si blocca una riga per l'accesso in sola lettura, anche altri utenti possono bloccarla per l'accesso in sola lettura, ma non per l'aggiornamento.

La gestione dei blocchi presenta alcuni svantaggi. La sua programmazione può risultare complicata. Richiede risorse di gestione del database significative e può causare problemi di prestazioni in quanto il numero di utenti di un'applicazione aumenta, ovvero non ridimensiona correttamente. Per questi motivi non tutti i sistemi di gestione di database supportano la concorrenza pessimistica. Entity Framework non fornisce alcun supporto predefinito e questa esercitazione non illustra come implementarla.

Concorrenza ottimistica

L'alternativa alla concorrenza pessimistica è la concorrenza ottimistica. Nella concorrenza ottimistica si consente che i conflitti di concorrenza si verifichino, quindi si reagisce con le modalità appropriate. Ad esempio, John esegue la pagina Department.aspx , fa clic sul collegamento Modifica per il reparto Cronologia e riduce l'importo budget compreso tra $1.000.000.00 e $125.000.000. (John amministra un reparto concorrente e vuole liberare denaro per il suo reparto).

Immagine07

Prima di fare clic su Aggiorna, Jane esegue la stessa pagina, fa clic sul collegamento Modifica per il reparto Cronologia e quindi modifica il campo Data di inizio da 1/10/2011 a 1/1/1999. Jane gestisce il reparto Cronologia e vuole dargli maggiore anzianità.

Immagine08

John fa clic su Aggiorna prima, quindi Jane fa clic su Aggiorna. Il browser di Jane elenca ora l'importo budget pari a $1.000.000.00, ma questo non è corretto perché l'importo è stato modificato da John a $125.000.00.

Alcune delle azioni che è possibile eseguire in questo scenario includono quanto segue:

  • È possibile tenere traccia della proprietà che un utente ha modificato e aggiornare solo le colonne corrispondenti nel database. Nello scenario dell'esempio non si perde nessun dato, perché i due utenti hanno aggiornato proprietà diverse. La prossima volta che qualcuno sfoglia il reparto Cronologia, vedrà 1/1/1999 e $125.000.00.

    Si tratta del comportamento predefinito in Entity Framework e può ridurre notevolmente il numero di conflitti che potrebbero causare la perdita di dati. Tuttavia, questo comportamento non evita la perdita di dati se le modifiche concorrenti vengono apportate alla stessa proprietà di un'entità. Inoltre, questo comportamento non è sempre possibile; quando si esegue il mapping delle stored procedure a un tipo di entità, tutte le proprietà di un'entità vengono aggiornate quando vengono apportate modifiche all'entità nel database.

  • È possibile riscrivere il cambiamento di John. Dopo che Jane fa clic su Aggiorna, l'importo del budget torna a $1.000.000.00. Questo scenario è detto Priorità client o Last in Wins (Priorità ultimo accesso). I valori del client hanno la precedenza su ciò che si trova nell'archivio dati.

  • È possibile impedire l'aggiornamento della modifica di Jane nel database. In genere, viene visualizzato un messaggio di errore, viene visualizzato lo stato corrente dei dati e si consente di immettere nuovamente le modifiche se si desidera ancora apportare tali modifiche. È possibile automatizzare ulteriormente il processo salvando l'input e dando la possibilità di riapplicarlo senza dover rimetterlo in ingresso. Questo scenario è detto Store Wins (Priorità archivio). I valori dell'archivio dati hanno la precedenza sui valori inoltrati dal client.

Rilevamento dei conflitti di concorrenza

In Entity Framework è possibile risolvere i conflitti gestendo OptimisticConcurrencyException le eccezioni generate da Entity Framework. Per determinare quando generare queste eccezioni, Entity Framework deve essere in grado di rilevare i conflitti. Pertanto è necessario configurare il database e il modello di dati in modo appropriato. Di seguito sono elencate alcune opzioni per abilitare il rilevamento dei conflitti:

  • Nel database includere una colonna di tabella che può essere usata per determinare quando è stata modificata una riga. È quindi possibile configurare Entity Framework per includere tale colonna nella Where clausola SQL Update o Delete comandi.

    Questo è lo scopo della Timestamp colonna nella OfficeAssignment tabella.

    Immagine09

    Il tipo di dati della Timestamp colonna viene chiamato Timestampanche . Tuttavia, la colonna non contiene effettivamente un valore di data o ora. Il valore è invece un numero sequenziale incrementato ogni volta che la riga viene aggiornata. In un Update comando o Delete la Where clausola include il valore originale Timestamp . Se la riga aggiornata è stata modificata da un altro utente, il valore in Timestamp è diverso dal valore originale, quindi la Where clausola non restituisce alcuna riga da aggiornare. Quando Entity Framework rileva che nessuna riga è stata aggiornata dal comando o Delete correnteUpdate, ovvero quando il numero di righe interessate è zero, viene interpretato come conflitto di concorrenza.

  • Configurare Entity Framework per includere i valori originali di ogni colonna nella tabella nella Where clausola di Update e Delete comandi.

    Come nella prima opzione, se qualsiasi elemento della riga è cambiato dopo la prima lettura della riga, la Where clausola non restituirà una riga da aggiornare, che Entity Framework interpreta come conflitto di concorrenza. Questo metodo è efficace come l'uso di un Timestamp campo, ma può essere inefficiente. Per le tabelle di database con molte colonne, può comportare clausole molto grandi Where e in un'applicazione Web può richiedere la gestione di grandi quantità di stato. La gestione di grandi quantità di stato può influire sulle prestazioni dell'applicazione perché richiede risorse server (ad esempio, stato sessione) o deve essere incluso nella pagina Web stessa (ad esempio, stato di visualizzazione).

In questa esercitazione si aggiungerà la gestione degli errori per conflitti di concorrenza ottimistica per un'entità che non dispone di una proprietà di rilevamento (entità) e per un'entità OfficeAssignment che dispone di una proprietà di rilevamento (Departmententità).

Gestione della concorrenza ottimistica senza una proprietà di rilevamento

Per implementare la concorrenza ottimistica per l'entità Department , che non dispone di una proprietà di rilevamento (Timestamp), verranno completate le attività seguenti:

  • Modificare il modello di dati per abilitare il rilevamento della concorrenza per Department le entità.
  • SchoolRepository Nella classe gestire le eccezioni di concorrenza nel SaveChanges metodo.
  • Nella pagina Departments.aspx gestire le eccezioni di concorrenza visualizzando un messaggio all'utente che indica che le modifiche tentate non sono riuscite. L'utente può quindi visualizzare i valori correnti e riprovare le modifiche se sono ancora necessarie.

Abilitazione del rilevamento della concorrenza nel modello di dati

In Visual Studio aprire l'applicazione Web Contoso University usata nell'esercitazione precedente in questa serie.

Aprire SchoolModel.edmx e nella finestra di progettazione modelli di dati fare clic con il pulsante destro del mouse sulla Name proprietà nell'entità Department e quindi scegliere Proprietà. Nella finestra Proprietà modificare la ConcurrencyMode proprietà su Fixed.

Immagine16

Eseguire la stessa operazione per le altre proprietà scalari non primarie (Budget, StartDatee Administrator.) Non è possibile eseguire questa operazione per le proprietà di spostamento. Questo specifica che ogni volta che Entity Framework genera un Update comando o Delete SQL per aggiornare l'entità Department nel database, queste colonne (con valori originali) devono essere incluse nella Where clausola . Se non viene trovata alcuna riga quando viene eseguito il Update comando o Delete , Entity Framework genererà un'eccezione di concorrenza ottimistica.

Salvare e chiudere il modello di dati.

Gestione delle eccezioni di concorrenza in DAL

Aprire SchoolRepository.cs e aggiungere l'istruzione seguente using per lo System.Data spazio dei nomi:

using System.Data;

Aggiungere il nuovo SaveChanges metodo seguente, che gestisce le eccezioni di concorrenza ottimistica:

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

Se si verifica un errore di concorrenza quando questo metodo viene chiamato, i valori delle proprietà dell'entità in memoria vengono sostituiti con i valori attualmente presenti nel database. L'eccezione di concorrenza è rethrown in modo che la pagina Web possa gestirla.

DeleteDepartment Nei metodi e UpdateDepartment sostituire la chiamata esistente a con una chiamata a context.SaveChanges()SaveChanges() per richiamare il nuovo metodo.

Gestione delle eccezioni di concorrenza nel livello presentazione

Aprire Departments.aspx e aggiungere un OnDeleted="DepartmentsObjectDataSource_Deleted" attributo al DepartmentsObjectDataSource controllo . Il tag di apertura per il controllo sarà ora simile all'esempio seguente.

<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" >

DepartmentsGridView Nel controllo specificare tutte le colonne della tabella nell'attributo DataKeyNames , come illustrato nell'esempio seguente. Si noti che in questo modo verranno creati campi di stato di visualizzazione molto grandi, motivo per cui l'uso di un campo di rilevamento è in genere il modo preferito per tenere traccia dei conflitti di concorrenza.

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

Aprire Departments.aspx.cs e aggiungere l'istruzione seguente using per lo spazio dei System.Data nomi :

using System.Data;

Aggiungere il nuovo metodo seguente, che verrà chiamato dai gestori eventi Updated e Deleted del controllo origine dati per la gestione delle eccezioni di concorrenza:

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;
    }
}

Questo codice controlla il tipo di eccezione e, se si tratta di un'eccezione di concorrenza, il codice crea dinamicamente un CustomValidator controllo che a sua volta visualizza un messaggio nel ValidationSummary controllo.

Chiamare il nuovo metodo dal Updated gestore eventi aggiunto in precedenza. Inoltre, creare un nuovo Deleted gestore eventi che chiama lo stesso metodo (ma non esegue altre operazioni):

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");
    }
}

Test della concorrenza ottimistica nella pagina Reparti

Eseguire la pagina Departments.aspx .

Screenshot che mostra la pagina Reparti.

Fare clic su Modifica in una riga e modificare il valore nella colonna Budget . Tenere presente che è possibile modificare solo i record creati per questa esercitazione, perché i record di database esistenti School contengono alcuni dati non validi. Il record per il dipartimento economia è uno sicuro con cui sperimentare.

Image18

Aprire una nuova finestra del browser ed eseguire di nuovo la pagina (copiare l'URL dalla casella dell'indirizzo della prima finestra del browser alla seconda finestra del browser).

Screenshot che mostra una nuova finestra del browser pronta per l'input.

Fare clic su Modifica nella stessa riga modificata in precedenza e modificare il valore Budget impostando un valore diverso.

Immagine 19

Nella seconda finestra del browser fare clic su Aggiorna. L'importo budget viene modificato correttamente in questo nuovo valore.

Immagine20

Nella prima finestra del browser fare clic su Aggiorna. L'aggiornamento non riesce. L'importo budget viene rieseguito usando il valore impostato nella seconda finestra del browser e viene visualizzato un messaggio di errore.

Immagine21

Gestione della concorrenza ottimistica tramite una proprietà di rilevamento

Per gestire la concorrenza ottimistica per un'entità con una proprietà di rilevamento, verranno completate le attività seguenti:

  • Aggiungere stored procedure al modello di dati per gestire OfficeAssignment le entità. Le proprietà di rilevamento e le stored procedure non devono essere usate insieme, ma sono raggruppate qui per l'illustrazione.
  • Aggiungere metodi CRUD a DAL e BLL per OfficeAssignment le entità, incluso il codice per gestire le eccezioni di concorrenza ottimistica in DAL.
  • Creare una pagina Web office-assignments.
  • Testare la concorrenza ottimistica nella nuova pagina Web.

Aggiunta di stored procedure officeAssignment al modello di dati

Aprire il file SchoolModel.edmx in Progettazione modelli, fare clic con il pulsante destro del mouse sull'area di progettazione e scegliere Aggiorna modello dal database. Nella scheda Aggiungi della finestra di dialogo Scegli oggetti di database espandere Stored procedure e selezionare le tre OfficeAssignment stored procedure (vedere lo screenshot seguente) e quindi fare clic su Fine. Queste stored procedure erano già presenti nel database quando è stato scaricato o creato usando uno script.

Immagine02

Fare clic con il pulsante destro del mouse sull'entità OfficeAssignment e scegliere Mapping stored procedure.

Immagine03

Impostare le funzioni Insert, Update e Delete per usare le stored procedure corrispondenti. Per il OrigTimestamp parametro della Update funzione, impostare proprietà suTimestamp e selezionare l'opzione Usa valore originale .

Immagine04

Quando Entity Framework chiama la UpdateOfficeAssignment stored procedure, passa il valore originale della Timestamp colonna nel OrigTimestamp parametro . La stored procedure usa questo parametro nella relativa Where clausola:

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

La stored procedure seleziona anche il nuovo valore della Timestamp colonna dopo l'aggiornamento in modo che Entity Framework possa mantenere l'entità OfficeAssignment in memoria sincronizzata con la riga del database corrispondente.

Si noti che la stored procedure per l'eliminazione di un'assegnazione di ufficio non ha un OrigTimestamp parametro. Per questo motivo, Entity Framework non è in grado di verificare che un'entità sia invariata prima di eliminarla.

Salvare e chiudere il modello di dati.

Aggiunta di metodi officeAssignment a DAL

Aprire ISchoolRepository.cs e aggiungere i metodi CRUD seguenti per il OfficeAssignment set di entità:

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

Aggiungere i nuovi metodi seguenti a SchoolRepository.cs. UpdateOfficeAssignment Nel metodo chiamare il metodo locale SaveChanges anziché 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();
}

Nel progetto di test aprire MockSchoolRepository.cs e aggiungervi la raccolta e i metodi CRUD seguenti OfficeAssignment . Il repository fittizio deve implementare l'interfaccia del repository o la soluzione non verrà compilata.

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

Aggiunta di metodi officeAssignment al BLL

Nel progetto principale aprire SchoolBL.cs e aggiungere i metodi CRUD seguenti per l'entità OfficeAssignment impostata su di esso:

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;
    }
}

Creazione di una pagina Web OfficeAssignments

Creare una nuova pagina Web che usa la pagina master Site.Master e denominarla OfficeAssignments.aspx. Aggiungere il markup seguente al Content controllo denominato 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>

Si noti che nell'attributo DataKeyNames il markup specifica la Timestamp proprietà e la chiave del record (InstructorID). Se si specificano proprietà nell'attributo DataKeyNames , il controllo li salva nello stato di controllo (simile allo stato di visualizzazione) in modo che i valori originali siano disponibili durante l'elaborazione postback.

Se il valore non è stato salvato Timestamp , Entity Framework non lo avrebbe per la Where clausola del comando SQL Update . Di conseguenza non viene trovato nulla da aggiornare. Di conseguenza, Entity Framework genera un'eccezione di concorrenza ottimistica ogni volta che un'entità OfficeAssignment viene aggiornata.

Aprire OfficeAssignments.aspx.cs e aggiungere l'istruzione seguente using per il livello di accesso ai dati:

using ContosoUniversity.DAL;

Aggiungere il metodo seguente Page_Init , che abilita la funzionalità Dynamic Data. Aggiungere anche il gestore seguente per l'evento ObjectDataSource del controllo per verificare la presenza di errori di Updated concorrenza:

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;
    }
}

Test della concorrenza ottimistica nella pagina OfficeAssignments

Eseguire la pagina OfficeAssignments.aspx .

Screenshot che mostra la pagina Assegnazioni di Office.

Fare clic su Modifica in una riga e modificare il valore nella colonna Posizione .

Immagine11

Aprire una nuova finestra del browser ed eseguire di nuovo la pagina (copiare l'URL dalla prima finestra del browser alla seconda finestra del browser).

Screenshot che mostra una nuova finestra del browser.

Fare clic su Modifica nella stessa riga modificata in precedenza e modificare il valore Location impostando un valore diverso.

Immagine12

Nella seconda finestra del browser fare clic su Aggiorna.

Immagine13

Passare alla prima finestra del browser e fare clic su Aggiorna.

Immagine15

Viene visualizzato un messaggio di errore e il valore Location è stato aggiornato per visualizzare il valore che è stato modificato in nella seconda finestra del browser.

Gestione della concorrenza con il controllo EntityDataSource

Il EntityDataSource controllo include la logica predefinita che riconosce le impostazioni di concorrenza nel modello di dati e gestisce le operazioni di aggiornamento ed eliminazione di conseguenza. Tuttavia, come per tutte le eccezioni, è necessario gestire OptimisticConcurrencyException manualmente le eccezioni per fornire un messaggio di errore descrittivo.

Successivamente, verrà configurata la pagina Courses.aspx (che usa un EntityDataSource controllo) per consentire le operazioni di aggiornamento ed eliminazione e per visualizzare un messaggio di errore se si verifica un conflitto di concorrenza. L'entità Course non ha una colonna di rilevamento della concorrenza, quindi si userà lo stesso metodo eseguito con l'entità Department : tenere traccia dei valori di tutte le proprietà non chiave.

Aprire il file SchoolModel.edmx . Per le proprietà non chiave dell'entità Course (Title, Creditse DepartmentID), impostare la proprietà Modalità di concorrenza su Fixed. Salvare e chiudere il modello di dati.

Aprire la pagina Courses.aspx e apportare le modifiche seguenti:

  • CoursesEntityDataSource Nel controllo aggiungere EnableUpdate="true" gli attributi e EnableDelete="true" . Il tag di apertura per tale controllo è ora simile all'esempio seguente:

    <asp:EntityDataSource ID="CoursesEntityDataSource" runat="server" 
            ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="false" 
            AutoGenerateWhereClause="True" EntitySetName="Courses"
            EnableUpdate="true" EnableDelete="true">
    
  • CoursesGridView Nel controllo modificare il valore dell'attributo DataKeyNames in "CourseID,Title,Credits,DepartmentID". Aggiungere quindi un CommandField elemento all'elemento Columns che mostra i pulsanti Modifica ed Elimina (<asp:CommandField ShowEditButton="True" ShowDeleteButton="True" />). Il GridView controllo è ora simile all'esempio seguente:

    <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>
    

Eseguire la pagina e creare una situazione di conflitto come in precedenza nella pagina Reparti. Eseguire la pagina in due finestre del browser, fare clic su Modifica nella stessa riga in ogni finestra e apportare una modifica diversa in ogni finestra. Fare clic su Aggiorna in una finestra e quindi su Aggiorna nell'altra finestra. Quando si fa clic su Aggiorna la seconda volta, viene visualizzata la pagina di errore risultante da un'eccezione di concorrenza non gestita.

Immagine22

Questo errore viene gestito in modo molto simile al modo in cui è stato gestito per il ObjectDataSource controllo. Aprire la pagina Courses.aspx e nel CoursesEntityDataSource controllo specificare gestori per gli Deleted eventi e Updated . Il tag di apertura del controllo ora è simile all'esempio seguente:

<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">

Prima del controllo, aggiungere il CoursesGridView controllo seguente ValidationSummary :

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

In Courses.aspx.cs aggiungere un'istruzione per lo System.Data spazio dei nomi, aggiungere un using metodo che verifica le eccezioni di concorrenza e aggiungere gestori per i gestori e Deleted i gestori del UpdatedEntityDataSource controllo. Il codice sarà simile al seguente:

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;
    }
}

L'unica differenza tra questo codice e quello eseguito per il ObjectDataSource controllo è che in questo caso l'eccezione di concorrenza è nella Exception proprietà dell'oggetto argomenti eventi anziché nella proprietà dell'eccezione InnerException .

Eseguire di nuovo la pagina e creare un conflitto di concorrenza. Questa volta viene visualizzato un messaggio di errore:

Immagine23

Questo argomento completa l'introduzione alla gestione dei conflitti di concorrenza. L'esercitazione successiva fornisce indicazioni su come migliorare le prestazioni in un'applicazione Web che usa Entity Framework.