Condividi tramite


Esercitazione: Gestire la concorrenza con Entity Framework in un'app MVC 5 ASP.NET

Nelle esercitazioni precedenti si è appreso come aggiornare i dati. Questa esercitazione illustra come usare la concorrenza ottimistica per gestire i conflitti quando più utenti aggiornano contemporaneamente la stessa entità. Si modificano le pagine Web che funzionano con l'entità Department in modo che gestiscano gli errori di concorrenza. Le illustrazioni seguenti visualizzano le pagine Edit (Modifica) e Delete (Elimina) e includono alcuni messaggi che vengono visualizzati se si verifica un conflitto di concorrenza.

Screenshot che mostra la pagina Modifica con i valori per Nome reparto, Budget, Data inizio e Amministratore con i valori correnti evidenziati.

Screenshot che mostra la pagina Elimina per un record con un messaggio sull'operazione di eliminazione e un pulsante Elimina.

In questa esercitazione:

  • Scoprire di più sui conflitti di concorrenza
  • Aggiungere la concorrenza ottimistica
  • Modificare il controller del reparto
  • Testare la gestione della concorrenza
  • Aggiornare la pagina Delete (Elimina)

Prerequisiti

Conflitti di concorrenza

Un conflitto di concorrenza si verifica quando un utente visualizza i dati di un'entità per modificarli mentre un altro utente aggiorna i dati della stessa entità prima che la modifica del primo utente venga scritta nel database. Se non si abilita il rilevamento di questi conflitti, l'ultimo utente che aggiorna il database sovrascrive le modifiche apportate dall'altro utente. In molte applicazioni questo rischio è accettabile: se il numero di utenti è ridotto o se l'eventuale sovrascrittura di alcune modifiche non è un aspetto critico, i costi della programmazione per la concorrenza possono superare i vantaggi. In tal caso non è necessario configurare l'applicazione per la gestione dei conflitti di concorrenza.

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. Si tratta di una 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 svantaggi. La sua programmazione può risultare complicata. Richiede molte risorse di gestione del database e può causare problemi di prestazioni quando il numero di utenti di un'applicazione aumenta. Per questi motivi non tutti i sistemi di gestione di database supportano la concorrenza pessimistica. Entity Framework non offre 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 Di modifica reparti, modifica l'importo budget per il reparto inglese da $ 350.000.00 a $ 0,00.

Prima che John faccia clic su Salva, Jane esegue la stessa pagina e modifica il campo Data inizio da 9/1/2007 a 8/8/2013.

Giorgio fa clic su Salva per primo e visualizza la modifica quando il browser torna alla pagina Indice, quindi Jane fa clic su Salva. Le operazioni successive dipendono da come si decide di gestire i conflitti di concorrenza. Di seguito sono elencate alcune opzioni:

  • È 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 esplora il reparto inglese, vedranno le modifiche di John e Jane , una data di inizio dell'8/8/2013 e un budget pari a Zero dollari.

    Questo metodo di aggiornamento può ridurre il numero di conflitti che causano la perdita di dati, ma non può evitare la perdita di dati se vengono apportate modifiche in competizione tra loro alla stessa proprietà di un'entità. Questo funzionamento di Entity Framework dipende dalla modalità di implementazione del codice di aggiornamento. In molti casi in un'app Web questo approccio risulta poco pratico, perché richiede la gestione di grandi quantità di codice statico per tenere traccia di tutti i valori di proprietà originali per un'entità, nonché dei nuovi valori. La gestione di grandi quantità di codice statico può influire sulle prestazioni dell'applicazione, perché richiede risorse di server o deve essere inclusa nella pagina Web stessa (ad esempio in campi nascosti) o in un cookie.

  • Puoi lasciare che Jane cambi sovrascrivendo il cambiamento di John. La prossima volta che qualcuno esplora il reparto inglese, vedrà 8/8/2013 e il valore di $350.000.00 ripristinato. Questo scenario è detto Priorità client o Last in Wins (Priorità ultimo accesso). Tutti i valori del client hanno la precedenza su ciò che si trova nell'archivio dati. Come indicato nell'introduzione a questa sezione, se non si esegue alcuna codifica per la gestione della concorrenza, questa operazione verrà eseguita automaticamente.

  • È possibile impedire che la modifica di Jane venga aggiornata nel database. In genere, viene visualizzato un messaggio di errore, viene visualizzato lo stato corrente dei dati e si consente di riapplicare le modifiche se si vuole ancora apportare tali modifiche. Questo scenario è detto Store Wins (Priorità archivio). I valori dell'archivio dati hanno la precedenza sui valori inviati dal client. In questa esercitazione verrà implementato lo scenario Delle vittorie nello Store. Questo metodo garantisce che nessuna modifica venga sovrascritta senza che un utente riceva un avviso.

Rilevamento di conflitti di concorrenza

È possibile risolvere i conflitti gestendo le eccezioni OptimisticConcurrencyException 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:

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

    Il tipo di dati della colonna di rilevamento è in genere rowversion. Il valore rowversion è un numero sequenziale incrementato ogni volta che la riga viene aggiornata. In un Update comando o Delete la Where clausola include il valore originale della colonna di rilevamento (la versione originale della riga). Se la riga da aggiornare è stata modificata da un altro utente, il valore nella rowversion colonna è diverso dal valore originale, quindi l'istruzione Update o Delete non riesce a trovare la riga da aggiornare a causa della Where clausola . Quando Entity Framework rileva che nessuna riga è stata aggiornata dal Update comando o Delete ( ovvero quando il numero di righe interessate è zero), interpreta tale valore come conflitto di concorrenza.

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

    Come nella prima opzione, se una riga è cambiata dopo la prima lettura della riga, la Where clausola non restituirà una riga da aggiornare, che Entity Framework interpreta come conflitto di concorrenza. Per le tabelle di database con molte colonne, questo approccio può comportare clausole molto grandi Where e può richiedere la gestione di grandi quantità di stato. Come notato in precedenza, la gestione di grandi quantità di codice statico può ridurre le prestazioni dell'applicazione. Pertanto questo approccio è in genere sconsigliato e non è il metodo usato in questa esercitazione.

    Se si vuole implementare questo approccio alla concorrenza, è necessario contrassegnare tutte le proprietà non chiave primaria nell'entità per cui si vuole tenere traccia della concorrenza aggiungendo l'attributo ConcurrencyCheck . Questa modifica consente a Entity Framework di includere tutte le colonne nella clausola SQL WHERE delle UPDATE istruzioni.

Nella parte restante di questa esercitazione si aggiungerà una proprietà di rilevamento rowversion all'entità Department , si creerà un controller e una vista e si testerà per verificare che tutto funzioni correttamente.

Aggiungere la concorrenza ottimistica

In Models\Department.cs aggiungere una proprietà di rilevamento denominata RowVersion:

public class Department
{
    public int DepartmentID { get; set; }

    [StringLength(50, MinimumLength = 3)]
    public string Name { get; set; }

    [DataType(DataType.Currency)]
    [Column(TypeName = "money")]
    public decimal Budget { get; set; }

    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
    [Display(Name = "Start Date")]
    public DateTime StartDate { get; set; }

    [Display(Name = "Administrator")]
    public int? InstructorID { get; set; }

    [Timestamp]
    public byte[] RowVersion { get; set; }

    public virtual Instructor Administrator { get; set; }
    public virtual ICollection<Course> Courses { get; set; }
}

L'attributo Timestamp specifica che questa colonna verrà inclusa nella Where clausola dei Update comandi e Delete inviati al database. L'attributo è denominato Timestamp perché le versioni precedenti di SQL Server usavano un tipo di dati timestamp SQL prima che la rowversion SQL lo sostituissi. Il tipo .Net per rowversion è una matrice di byte.

Se si preferisce usare l'API Fluent, è possibile usare il metodo IsConcurrencyToken per specificare la proprietà di rilevamento, come illustrato nell'esempio seguente:

modelBuilder.Entity<Department>()
    .Property(p => p.RowVersion).IsConcurrencyToken();

In seguito all'aggiunta di una proprietà il modello di database è stato modificato, pertanto è necessario eseguire una nuova migrazione. Nella console di Gestione pacchetti immettere i comandi seguenti:

Add-Migration RowVersion
Update-Database

Modificare il controller del reparto

In Controllers\DepartmentController.cs aggiungere un'istruzione using :

using System.Data.Entity.Infrastructure;

Nel file DepartmentController.cs modificare tutte e quattro le occorrenze di "LastName" in "FullName" in modo che gli elenchi a discesa dell'amministratore del reparto contengano il nome completo dell'insegnante anziché solo il cognome.

ViewBag.InstructorID = new SelectList(db.Instructors, "ID", "FullName");

Sostituire il codice esistente per il HttpPost Edit metodo con il codice seguente:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(int? id, byte[] rowVersion)
{
    string[] fieldsToBind = new string[] { "Name", "Budget", "StartDate", "InstructorID", "RowVersion" };

    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }

    var departmentToUpdate = await db.Departments.FindAsync(id);
    if (departmentToUpdate == null)
    {
        Department deletedDepartment = new Department();
        TryUpdateModel(deletedDepartment, fieldsToBind);
        ModelState.AddModelError(string.Empty,
            "Unable to save changes. The department was deleted by another user.");
        ViewBag.InstructorID = new SelectList(db.Instructors, "ID", "FullName", deletedDepartment.InstructorID);
        return View(deletedDepartment);
    }

    if (TryUpdateModel(departmentToUpdate, fieldsToBind))
    {
        try
        {
            db.Entry(departmentToUpdate).OriginalValues["RowVersion"] = rowVersion;
            await db.SaveChangesAsync();

            return RedirectToAction("Index");
        }
        catch (DbUpdateConcurrencyException ex)
        {
            var entry = ex.Entries.Single();
            var clientValues = (Department)entry.Entity;
            var databaseEntry = entry.GetDatabaseValues();
            if (databaseEntry == null)
            {
                ModelState.AddModelError(string.Empty,
                    "Unable to save changes. The department was deleted by another user.");
            }
            else
            {
                var databaseValues = (Department)databaseEntry.ToObject();

                if (databaseValues.Name != clientValues.Name)
                    ModelState.AddModelError("Name", "Current value: "
                        + databaseValues.Name);
                if (databaseValues.Budget != clientValues.Budget)
                    ModelState.AddModelError("Budget", "Current value: "
                        + String.Format("{0:c}", databaseValues.Budget));
                if (databaseValues.StartDate != clientValues.StartDate)
                    ModelState.AddModelError("StartDate", "Current value: "
                        + String.Format("{0:d}", databaseValues.StartDate));
                if (databaseValues.InstructorID != clientValues.InstructorID)
                    ModelState.AddModelError("InstructorID", "Current value: "
                        + db.Instructors.Find(databaseValues.InstructorID).FullName);
                ModelState.AddModelError(string.Empty, "The record you attempted to edit "
                    + "was modified by another user after you got the original value. The "
                    + "edit operation was canceled and the current values in the database "
                    + "have been displayed. If you still want to edit this record, click "
                    + "the Save button again. Otherwise click the Back to List hyperlink.");
                departmentToUpdate.RowVersion = databaseValues.RowVersion;
            }
        }
        catch (RetryLimitExceededException /* dex */)
        {
            //Log the error (uncomment dex variable name and add a line here to write a log.)
            ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
        }
    }
    ViewBag.InstructorID = new SelectList(db.Instructors, "ID", "FullName", departmentToUpdate.InstructorID);
    return View(departmentToUpdate);
}

Se il metodo FindAsync restituisce null, il reparto è stato eliminato da un altro utente. Il codice illustrato usa i valori del modulo pubblicati per creare un'entità reparto in modo che la pagina Modifica possa essere riprodotta con un messaggio di errore. In alternativa, non è necessario creare nuovamente l'entità del reparto se si visualizza solo un messaggio di errore senza visualizzare di nuovo i campi del reparto.

La vista archivia il valore originale RowVersion in un campo nascosto e il metodo lo riceve nel rowVersion parametro . Prima della chiamata di SaveChanges è necessario inserire il valore originale della proprietà RowVersion nella raccolta OriginalValues dell'entità. Quindi, quando Entity Framework crea un comando SQL UPDATE , tale comando includerà una WHERE clausola che cerca una riga con il valore originale RowVersion .

Se nessuna riga è interessata dal UPDATE comando (nessuna riga ha il valore originale RowVersion ), Entity Framework genera un'eccezione DbUpdateConcurrencyException e il codice nel catch blocco ottiene l'entità interessata Department dall'oggetto eccezione.

var entry = ex.Entries.Single();

Questo oggetto contiene i nuovi valori immessi dall'utente nella relativa Entity proprietà ed è possibile ottenere i valori letti dal database chiamando il GetDatabaseValues metodo .

var clientValues = (Department)entry.Entity;
var databaseEntry = entry.GetDatabaseValues();

Il GetDatabaseValues metodo restituisce null se un utente ha eliminato la riga dal database. In caso contrario, è necessario eseguire il cast dell'oggetto restituito alla Department classe per accedere Department alle proprietà. Poiché è già stata verificata l'eliminazione, databaseEntry sarebbe null solo se il reparto è stato eliminato dopo FindAsync l'esecuzione e prima SaveChanges dell'esecuzione.

if (databaseEntry == null)
{
    ModelState.AddModelError(string.Empty,
        "Unable to save changes. The department was deleted by another user.");
}
else
{
    var databaseValues = (Department)databaseEntry.ToObject();

Il codice aggiunge quindi un messaggio di errore personalizzato per ogni colonna con valori di database diversi da quelli immessi dall'utente nella pagina Modifica:

if (databaseValues.Name != currentValues.Name)
    ModelState.AddModelError("Name", "Current value: " + databaseValues.Name);
    // ...

Un messaggio di errore più lungo spiega cosa è successo e cosa fare su di esso:

ModelState.AddModelError(string.Empty, "The record you attempted to edit "
    + "was modified by another user after you got the original value. The"
    + "edit operation was canceled and the current values in the database "
    + "have been displayed. If you still want to edit this record, click "
    + "the Save button again. Otherwise click the Back to List hyperlink.");

Infine, il codice imposta il RowVersion valore dell'oggetto Department sul nuovo valore recuperato dal database. Questo nuovo valore RowVersion viene archiviato nel campo nascosto quando viene visualizzata nuovamente la pagina Edit (Modifica). Quando l'utente torna a fare clic su Salva vengono rilevati solo gli errori di concorrenza che si verificano dopo la nuova visualizzazione della pagina Edit (Modifica).

In Views\Department\Edit.cshtml aggiungere un campo nascosto per salvare il valore della RowVersion proprietà, subito dopo il campo nascosto per la DepartmentID proprietà:

@model ContosoUniversity.Models.Department

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    
    <div class="form-horizontal">
        <h4>Department</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.DepartmentID)
        @Html.HiddenFor(model => model.RowVersion)

Testare la gestione della concorrenza

Eseguire il sito e fare clic su Reparti.

Fare clic con il pulsante destro del mouse sul collegamento ipertestuale Modifica per il reparto inglese e scegliere Apri nella nuova scheda, quindi fare clic sul collegamento ipertestuale Modifica per il reparto inglese. Le due schede visualizzano le stesse informazioni.

Modificare un campo nella prima scheda del browser e fare clic su Salva.

Il browser visualizza la pagina Index con il valore modificato.

Modificare un campo nella seconda scheda del browser e fare clic su Salva. Viene visualizzato un messaggio di errore:

Screenshot che mostra la pagina Modifica con un messaggio che spiega che l'operazione è stata annullata perché il valore è stato modificato da un altro utente.

Fare di nuovo clic su Salva. Il valore immesso nella seconda scheda del browser viene salvato insieme al valore originale dei dati modificati nel primo browser. I valori salvati vengono visualizzati nella pagina Index.

Aggiornare la pagina Delete (Elimina)

Per la pagina Delete (Elimina), Entity Framework rileva conflitti di concorrenza causati da un altro utente che ha modificato il reparto con modalità simili. Quando il HttpGet Delete metodo visualizza la visualizzazione di conferma, la visualizzazione include il valore originale RowVersion in un campo nascosto. Tale valore è quindi disponibile per il HttpPost Delete metodo chiamato quando l'utente conferma l'eliminazione. Quando Entity Framework crea il comando SQL DELETE , include una WHERE clausola con il valore originale RowVersion . Se il comando restituisce zero righe interessate (ovvero la riga è stata modificata dopo la visualizzazione della pagina di conferma elimina), viene generata un'eccezione di concorrenza e il HttpGet Delete metodo viene chiamato con un flag di errore impostato su true per riprodurre la pagina di conferma con un messaggio di errore. È anche possibile che nessuna riga sia stata interessata perché la riga è stata eliminata da un altro utente, quindi in tal caso viene visualizzato un messaggio di errore diverso.

In DepartmentController.cs sostituire il HttpGet Delete metodo con il codice seguente:

public async Task<ActionResult> Delete(int? id, bool? concurrencyError)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Department department = await db.Departments.FindAsync(id);
    if (department == null)
    {
        if (concurrencyError.GetValueOrDefault())
        {
            return RedirectToAction("Index");
        }
        return HttpNotFound();
    }

    if (concurrencyError.GetValueOrDefault())
    {
        ViewBag.ConcurrencyErrorMessage = "The record you attempted to delete "
            + "was modified by another user after you got the original values. "
            + "The delete operation was canceled and the current values in the "
            + "database have been displayed. If you still want to delete this "
            + "record, click the Delete button again. Otherwise "
            + "click the Back to List hyperlink.";
    }

    return View(department);
}

Il metodo accetta un parametro facoltativo che indica se la pagina viene nuovamente visualizzata dopo un errore di concorrenza. Se questo flag è true, viene inviato un messaggio di errore alla visualizzazione usando una ViewBag proprietà .

Sostituire il codice nel HttpPost Delete metodo (denominato DeleteConfirmed) con il codice seguente:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Delete(Department department)
{
    try
    {
        db.Entry(department).State = EntityState.Deleted;
        await db.SaveChangesAsync();
        return RedirectToAction("Index");
    }
    catch (DbUpdateConcurrencyException)
    {
        return RedirectToAction("Delete", new { concurrencyError = true, id=department.DepartmentID });
    }
    catch (DataException /* dex */)
    {
        //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
        ModelState.AddModelError(string.Empty, "Unable to delete. Try again, and if the problem persists contact your system administrator.");
        return View(department);
    }
}

Nel codice sottoposto a scaffolding appena sostituito, questo metodo accettava solo un ID record:

public async Task<ActionResult> DeleteConfirmed(int id)

Questo parametro è stato modificato in un'istanza Department di entità creata dal gestore di associazione di modelli. In questo modo è possibile accedere al valore della RowVersion proprietà oltre alla chiave del record.

public async Task<ActionResult> Delete(Department department)

Anche il nome del metodo di azione è stato modificato da DeleteConfirmed a Delete. Codice con scaffolding denominato il HttpPost Delete metodo per assegnare al HttpPost metodo DeleteConfirmed una firma univoca. ( CLR richiede metodi di overload per avere parametri di metodo diversi. Ora che le firme sono univoche, è possibile attenersi alla convenzione MVC e usare lo stesso nome per i HttpPost metodi ed HttpGet delete.

Se viene rilevato un errore di concorrenza, il codice visualizza nuovamente la pagina di conferma Delete (Elimina) e visualizza un flag indicante che è necessario visualizzare un messaggio di errore di concorrenza.

In Views\Department\Delete.cshtml sostituire il codice scaffolded con il codice seguente che aggiunge un campo messaggio di errore e campi nascosti per le proprietà DepartmentID e RowVersion. Le modifiche sono evidenziate.

@model ContosoUniversity.Models.Department

@{
    ViewBag.Title = "Delete";
}

<h2>Delete</h2>

<p class="error">@ViewBag.ConcurrencyErrorMessage</p>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Department</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            Administrator
        </dt>

        <dd>
            @Html.DisplayFor(model => model.Administrator.FullName)
        </dd>

        <dt>
            @Html.DisplayNameFor(model => model.Name)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.Name)
        </dd>

        <dt>
            @Html.DisplayNameFor(model => model.Budget)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.Budget)
        </dd>

        <dt>
            @Html.DisplayNameFor(model => model.StartDate)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.StartDate)
        </dd>

    </dl>

    @using (Html.BeginForm()) {
        @Html.AntiForgeryToken()
        @Html.HiddenFor(model => model.DepartmentID)
        @Html.HiddenFor(model => model.RowVersion)

        <div class="form-actions no-color">
            <input type="submit" value="Delete" class="btn btn-default" /> |
            @Html.ActionLink("Back to List", "Index")
        </div>
    }
</div>

Questo codice aggiunge un messaggio di errore tra le h2 intestazioni e h3 :

<p class="error">@ViewBag.ConcurrencyErrorMessage</p>

LastName Sostituisce con FullName nel Administrator campo :

<dt>
  Administrator
</dt>
<dd>
  @Html.DisplayFor(model => model.Administrator.FullName)
</dd>

Infine, aggiunge campi nascosti per le DepartmentID proprietà e RowVersion dopo l'istruzione Html.BeginForm :

@Html.HiddenFor(model => model.DepartmentID)
@Html.HiddenFor(model => model.RowVersion)

Eseguire la pagina Indice reparti. Fare clic con il pulsante destro del mouse sul collegamento ipertestuale Elimina per il reparto inglese e selezionare Apri nella nuova scheda, quindi nella prima scheda fare clic sul collegamento ipertestuale Modifica per il reparto inglese.

Nella prima finestra modificare uno dei valori e fare clic su Salva.

La pagina Indice conferma la modifica.

Nella seconda scheda fare clic su Delete (Elimina).

Viene visualizzato il messaggio di errore di concorrenza e i valori di Department (Reparto) vengono aggiornati con i dati attualmente presenti nel database.

Department_Delete_confirmation_page_with_concurrency_error

Se si fa di nuovo clic su Delete (Elimina) viene visualizzata la pagina Index che indica che il reparto è stato eliminato.

Ottenere il codice

Scaricare il progetto completato

Risorse aggiuntive

I collegamenti ad altre risorse di Entity Framework sono disponibili in ASP.NET Accesso ai dati - Risorse consigliate.

Per informazioni su altri modi per gestire vari scenari di concorrenza, vedere Modelli di concorrenza ottimistica e Utilizzo dei valori delle proprietà su MSDN. L'esercitazione successiva illustra come implementare l'ereditarietà di tabelle per gerarchia per le Instructor entità e Student .

Passaggi successivi

In questa esercitazione:

  • Scoprire di più sui conflitti di concorrenza
  • Aggiunta della concorrenza ottimistica
  • Controller reparto modificato
  • Gestione della concorrenza testata
  • Aggiornare la pagina Delete

Passare all'articolo successivo per informazioni su come implementare l'ereditarietà nel modello di dati.