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.
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).
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à.
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 SQLUpdate
oDelete
comandi.Questo è lo scopo della
Timestamp
colonna nellaOfficeAssignment
tabella.Il tipo di dati della
Timestamp
colonna viene chiamatoTimestamp
anche . 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 unUpdate
comando oDelete
laWhere
clausola include il valore originaleTimestamp
. Se la riga aggiornata è stata modificata da un altro utente, il valore inTimestamp
è diverso dal valore originale, quindi laWhere
clausola non restituisce alcuna riga da aggiornare. Quando Entity Framework rileva che nessuna riga è stata aggiornata dal comando oDelete
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 diUpdate
eDelete
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 unTimestamp
campo, ma può essere inefficiente. Per le tabelle di database con molte colonne, può comportare clausole molto grandiWhere
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 (Department
entità).
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 nelSaveChanges
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
.
Eseguire la stessa operazione per le altre proprietà scalari non primarie (Budget
, StartDate
e 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 .
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.
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).
Fare clic su Modifica nella stessa riga modificata in precedenza e modificare il valore Budget impostando un valore diverso.
Nella seconda finestra del browser fare clic su Aggiorna. L'importo budget viene modificato correttamente in questo nuovo valore.
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.
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.
Fare clic con il pulsante destro del mouse sull'entità OfficeAssignment
e scegliere Mapping stored procedure.
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 .
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 .
Fare clic su Modifica in una riga e modificare il valore nella colonna Posizione .
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).
Fare clic su Modifica nella stessa riga modificata in precedenza e modificare il valore Location impostando un valore diverso.
Nella seconda finestra del browser fare clic su Aggiorna.
Passare alla prima finestra del browser e fare clic su Aggiorna.
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
, Credits
e 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 aggiungereEnableUpdate="true"
gli attributi eEnableDelete="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'attributoDataKeyNames
in"CourseID,Title,Credits,DepartmentID"
. Aggiungere quindi unCommandField
elemento all'elementoColumns
che mostra i pulsanti Modifica ed Elimina (<asp:CommandField ShowEditButton="True" ShowDeleteButton="True" />
). IlGridView
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.
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 Updated
EntityDataSource
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:
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.