Share via


Gestion de la concurrence avec Entity Framework 4.0 dans une application web ASP.NET 4

par Tom Dykstra

Cette série de tutoriels s’appuie sur l’application web Contoso University créée par le Prise en main avec la série de didacticiels Entity Framework 4.0. Si vous n’avez pas terminé les didacticiels précédents, comme point de départ de ce didacticiel, vous pouvez télécharger l’application que vous auriez créée. Vous pouvez également télécharger l’application créée par la série complète de tutoriels. Si vous avez des questions sur les didacticiels, vous pouvez les publier sur le forum ASP.NET Entity Framework.

Dans le tutoriel précédent, vous avez appris à trier et filtrer des données à l’aide du ObjectDataSource contrôle et d’Entity Framework. Ce tutoriel présente les options de gestion de l’accès concurrentiel dans une application web ASP.NET qui utilise Entity Framework. Vous allez créer une page web dédiée à la mise à jour des affectations de bureau des instructeurs. Vous allez gérer les problèmes d’accès concurrentiel dans cette page et dans la page Départements que vous avez créée précédemment.

Image06

Image01

Conflits d’accès concurrentiel

Un conflit d’accès concurrentiel se produit lorsqu’un utilisateur modifie un enregistrement et qu’un autre utilisateur modifie le même enregistrement avant que la modification du premier utilisateur soit écrite dans la base de données. Si vous ne configurez pas Entity Framework pour détecter de tels conflits, quiconque met à jour la base de données en dernier remplace les modifications de l’autre utilisateur. Dans de nombreuses applications, ce risque est acceptable et vous n’avez pas besoin de configurer l’application pour gérer d’éventuels conflits d’accès concurrentiel. (S’il y a peu d’utilisateurs, ou peu de mises à jour, ou si n’est pas vraiment critique si certaines modifications sont remplacées, le coût de la programmation pour l’accès concurrentiel peut l’emporter sur l’avantage.) Si vous n’avez pas besoin de vous soucier des conflits d’accès concurrentiel, vous pouvez ignorer ce tutoriel . Les deux autres tutoriels de la série ne dépendent pas de quoi que ce soit que vous générez dans celui-ci.

Accès concurrentiel pessimiste (verrouillage)

Si votre application doit éviter la perte accidentelle de données dans des scénarios d’accès concurrentiel, une manière de le faire consiste à utiliser des verrous de base de données. C’est ce qu’on appelle l’accès concurrentiel pessimiste. Par exemple, avant de lire une ligne d’une base de données, vous demandez un verrou pour lecture seule ou pour accès avec mise à jour. Si vous verrouillez une ligne pour accès avec mise à jour, aucun autre utilisateur n’est autorisé à verrouiller la ligne pour lecture seule ou pour accès avec mise à jour, car ils obtiendraient ainsi une copie de données qui sont en cours de modification. Si vous verrouillez une ligne pour accès en lecture seule, d’autres utilisateurs peuvent également la verrouiller pour accès en lecture seule, mais pas pour accès avec mise à jour.

La gestion des verrous présente certains inconvénients. Elle peut être complexe à programmer. Elle nécessite des ressources de gestion de base de données importantes et peut entraîner des problèmes de performances à mesure que le nombre d’utilisateurs d’une application augmente (autrement dit, elle ne se met pas à l’échelle). Pour ces raisons, certains systèmes de gestion de base de données ne prennent pas en charge l’accès concurrentiel pessimiste. Entity Framework ne fournit aucune prise en charge intégrée, et ce tutoriel ne vous montre pas comment l’implémenter.

Accès concurrentiel optimiste

L’alternative à l’accès concurrentiel pessimiste est l’accès concurrentiel optimiste. L’accès concurrentiel optimiste signifie autoriser la survenance des conflits d’accès concurrentiel, puis de réagir correctement quand ils surviennent. Par exemple, John exécute la page Department.aspx , clique sur le lien Modifier pour le service d’historique et réduit le montant du budget de 1 000 000,00 $ à 125 000,00 $. (John administre un département concurrent et veut libérer de l’argent pour son propre département.)

Image07

Avant que John ne clique sur Mettre à jour, Jane exécute la même page, clique sur le lien Modifier pour le service Historique, puis modifie le champ Date de début du 10/01/2011 au 1/1/1999. (Jane administre le département d’histoire et veut lui donner plus d’ancienneté.)

Image08

John clique d’abord sur Mettre à jour , puis Jane sur Mettre à jour. Le navigateur de Jane indique maintenant le montant du budget à 1 000 000,00 $, mais ce n’est pas correct, car le montant a été modifié par John à 125 000,00 $.

Voici quelques-unes des actions que vous pouvez effectuer dans ce scénario :

  • Vous pouvez effectuer le suivi des propriétés modifiées par un utilisateur et mettre à jour seulement les colonnes correspondantes dans la base de données. Dans l’exemple de scénario, aucune donnée ne serait perdue, car des propriétés différentes ont été mises à jour par chacun des deux utilisateurs. La prochaine fois que quelqu’un naviguera dans le département d’histoire, il verra le 1/1/1999 et 125 000,00 $.

    Il s’agit du comportement par défaut dans Entity Framework, qui peut réduire considérablement le nombre de conflits susceptibles d’entraîner une perte de données. Toutefois, ce comportement n’évite pas la perte de données si des modifications concurrentes sont apportées à la même propriété d’une entité. En outre, ce comportement n’est pas toujours possible ; lorsque vous mappez des procédures stockées à un type d’entité, toutes les propriétés d’une entité sont mises à jour lorsque des modifications apportées à l’entité sont apportées dans la base de données.

  • Vous pouvez laisser Jane changer le changement de John. Une fois que Jane a cliqué sur Mettre à jour, le montant du budget revient à 1 000 000 ,00 $. Ceci s’appelle un scénario Priorité au client ou Priorité au dernier entré (Last in Wins). (Les valeurs du client sont prioritaires sur ce qui se trouve dans le magasin de données.)

  • Vous pouvez empêcher la mise à jour de la modification de Jane dans la base de données. En règle générale, vous affichez un message d’erreur, vous lui montrerez l’état actuel des données et vous lui permettrez d’effectuer de nouveau ses modifications si elle souhaite toujours les apporter. Vous pouvez automatiser davantage le processus en enregistrant son entrée et en lui donnant la possibilité de la réappliquer sans avoir à la saisir à nouveau. Il s’agit alors d’un scénario Priorité au magasin. (Les valeurs du magasin de données sont prioritaires par rapport à celles soumises par le client.)

Détection des conflits d’accès concurrentiel

Dans Entity Framework, vous pouvez résoudre les conflits en gérant les OptimisticConcurrencyException exceptions levées par Entity Framework. Pour savoir quand lever ces exceptions, Entity Framework doit être en mesure de détecter les conflits. Par conséquent, vous devez configurer de façon appropriée la base de données et le modèle de données. Voici quelques options pour l’activation de la détection des conflits :

  • Dans la base de données, incluez une colonne de table qui peut être utilisée pour déterminer quand une ligne a été modifiée. Vous pouvez ensuite configurer Entity Framework pour inclure cette colonne dans la Where clause de SQL Update ou Delete de commandes.

    C’est l’objectif de la Timestamp colonne dans la OfficeAssignment table.

    Image09

    Le type de données de la Timestamp colonne est également appelé Timestamp. Toutefois, la colonne ne contient pas réellement de valeur de date ou d’heure. Au lieu de cela, la valeur est un nombre séquentiel qui est incrémenté chaque fois que la ligne est mise à jour. Dans une commande ou , la Where clause inclut la valeur d’origine UpdateTimestamp.Delete Si la ligne en cours de mise à jour a été modifiée par un autre utilisateur, la valeur dans Timestamp est différente de la valeur d’origine, de sorte que la Where clause ne retourne aucune ligne à mettre à jour. Quand Entity Framework détecte qu’aucune ligne n’a été mise à jour par la commande actuelle Update ou Delete la commande (autrement dit, lorsque le nombre de lignes affectées est égal à zéro), il l’interprète comme un conflit d’accès concurrentiel.

  • Configurez Entity Framework pour inclure les valeurs d’origine de chaque colonne de la table dans la Where clause des Update commandes et Delete .

    Comme dans la première option, si quelque chose dans la ligne a changé depuis la première lecture de la ligne, la Where clause ne retourne pas de ligne à mettre à jour, ce que Entity Framework interprète comme un conflit d’accès concurrentiel. Cette méthode est aussi efficace que l’utilisation d’un Timestamp champ, mais peut être inefficace. Pour les tables de base de données qui ont de nombreuses colonnes, cela peut entraîner des clauses très volumineuses Where et, dans une application web, il peut être nécessaire de conserver de grandes quantités d’état. La maintenance de grandes quantités d’état peut affecter les performances de l’application, car elle nécessite des ressources de serveur (par exemple, l’état de session) ou doit être incluse dans la page web elle-même (par exemple, l’état d’affichage).

Dans ce tutoriel, vous allez ajouter la gestion des erreurs pour les conflits d’accès concurrentiel optimistes pour une entité qui n’a pas de propriété de suivi (l’entité Department ) et pour une entité qui a une propriété de suivi (l’entité OfficeAssignment ).

Gestion de l’accès concurrentiel optimiste sans propriété de suivi

Pour implémenter l’accès concurrentiel optimiste pour l’entité Department , qui n’a pas de propriété de suivi (Timestamp), vous devez effectuer les tâches suivantes :

  • Modifiez le modèle de données pour activer le suivi de la concurrence pour Department les entités.
  • Dans la SchoolRepository classe , gérez les exceptions d’accès concurrentiel dans la SaveChanges méthode .
  • Dans la page Departments.aspx , gérez les exceptions d’accès concurrentiel en affichant un message d’avertissement à l’utilisateur indiquant que les tentatives de modifications ont échoué. L’utilisateur peut alors voir les valeurs actuelles et réessayer les modifications si elles sont toujours nécessaires.

Activation du suivi concurrentiel dans le modèle de données

Dans Visual Studio, ouvrez l’application web Contoso University avec laquelle vous avez travaillé dans le didacticiel précédent de cette série.

Ouvrez SchoolModel.edmx et, dans le concepteur de modèle de données, cliquez avec le bouton droit sur la Name propriété dans l’entité Department , puis cliquez sur Propriétés. Dans la fenêtre Propriétés , remplacez la propriété par ConcurrencyModeFixed.

Image16

Faites de même pour les autres propriétés scalaires non de clé primaire (Budget, StartDate, et Administrator.) (Vous ne pouvez pas le faire pour les propriétés de navigation.) Cela spécifie que chaque fois que Entity Framework génère une Update commande SQL ou Delete pour mettre à jour l’entité Department dans la base de données, ces colonnes (avec des valeurs d’origine) doivent être incluses dans la Where clause . Si aucune ligne n’est trouvée lorsque la Update commande ou s’exécute Delete , Entity Framework lève une exception d’accès concurrentiel optimiste.

Enregistrez et fermez le modèle de données.

Gestion des exceptions d’accès concurrentiel dans le dal

Ouvrez SchoolRepository.cs et ajoutez l’instruction suivante using pour l’espace de System.Data noms :

using System.Data;

Ajoutez la nouvelle SaveChanges méthode suivante, qui gère les exceptions d’accès concurrentiel optimiste :

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

Si une erreur d’accès concurrentiel se produit lorsque cette méthode est appelée, les valeurs de propriété de l’entité en mémoire sont remplacées par les valeurs actuellement dans la base de données. L’exception d’accès concurrentiel est de nouveau levée afin que la page web puisse la gérer.

Dans les DeleteDepartment méthodes et UpdateDepartment , remplacez l’appel existant à context.SaveChanges() par un appel à SaveChanges() afin d’appeler la nouvelle méthode.

Gestion des exceptions d’accès concurrentiel dans la couche présentation

Ouvrez Departments.aspx et ajoutez un OnDeleted="DepartmentsObjectDataSource_Deleted" attribut au DepartmentsObjectDataSource contrôle. La balise d’ouverture du contrôle ressemblera maintenant à l’exemple suivant.

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

Dans le DepartmentsGridView contrôle, spécifiez toutes les colonnes de table dans l’attribut DataKeyNames , comme illustré dans l’exemple suivant. Notez que cela créera des champs d’état d’affichage très volumineux, ce qui explique pourquoi l’utilisation d’un champ de suivi est généralement le moyen préféré pour suivre les conflits d’accès concurrentiel.

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

Ouvrez Departments.aspx.cs et ajoutez l’instruction suivante using pour l’espace de System.Data noms :

using System.Data;

Ajoutez la nouvelle méthode suivante, que vous allez appeler à partir des gestionnaires d’événements et Deleted du contrôle de source de Updated données pour gérer les exceptions d’accès concurrentiel :

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

Ce code vérifie le type d’exception et, s’il s’agit d’une exception de concurrence, le code crée dynamiquement un CustomValidator contrôle qui à son tour affiche un message dans le ValidationSummary contrôle.

Appelez la nouvelle méthode à partir du Updated gestionnaire d’événements que vous avez ajouté précédemment. En outre, créez un gestionnaire Deleted d’événements qui appelle la même méthode (mais ne fait rien d’autre) :

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 de l’accès concurrentiel optimiste dans la page Services

Exécutez la page Departments.aspx .

Capture d’écran montrant la page Services.

Cliquez sur Modifier dans une ligne et modifiez la valeur dans la colonne Budget . (N’oubliez pas que vous pouvez uniquement modifier les enregistrements que vous avez créés pour ce didacticiel, car les enregistrements de base de données existants School contiennent des données non valides. Le dossier du département d’économie est un dossier sûr à expérimenter.)

Image18

Ouvrez une nouvelle fenêtre de navigateur et réexécutez la page (copiez l’URL de la zone d’adresse de la première fenêtre de navigateur vers la deuxième fenêtre de navigateur).

Capture d’écran montrant une nouvelle fenêtre de navigateur prête pour l’entrée.

Cliquez sur Modifier dans la même ligne que vous avez modifiée précédemment et remplacez la valeur Budget par quelque chose de différent.

Image19

Dans la deuxième fenêtre de navigateur, cliquez sur Mettre à jour. Le montant du budget est remplacé avec succès par cette nouvelle valeur.

Image20

Dans la première fenêtre du navigateur, cliquez sur Mettre à jour. La mise à jour échoue. Le montant du budget est réaffiché à l’aide de la valeur que vous avez définie dans la deuxième fenêtre de navigateur, et un message d’erreur s’affiche.

Image21

Gestion de la concurrence optimiste à l’aide d’une propriété de suivi

Pour gérer l’accès concurrentiel optimiste pour une entité qui a une propriété de suivi, vous devez effectuer les tâches suivantes :

  • Ajoutez des procédures stockées au modèle de données pour gérer les OfficeAssignment entités. (Les propriétés de suivi et les procédures stockées n’ont pas besoin d’être utilisées ensemble ; elles sont simplement regroupées ici à des fins d’illustration.)
  • Ajoutez des méthodes CRUD au DAL et au BLL pour les entités, y compris le code pour OfficeAssignment gérer les exceptions d’accès concurrentiel optimiste dans le DAL.
  • Créez une page web d’affectations office.
  • Testez la concurrence optimiste dans la nouvelle page web.

Ajout de procédures stockées OfficeAssignment au modèle de données

Ouvrez le fichier SchoolModel.edmx dans le concepteur de modèles, cliquez avec le bouton droit sur l’aire de conception, puis cliquez sur Mettre à jour le modèle à partir de la base de données. Sous l’onglet Ajouter de la boîte de dialogue Choisir vos objets de base de données , développez Procédures stockées , sélectionnez les trois OfficeAssignment procédures stockées (voir la capture d’écran suivante), puis cliquez sur Terminer. (Ces procédures stockées se trouvaient déjà dans la base de données lorsque vous l’avez téléchargée ou créée à l’aide d’un script.)

Image02

Cliquez avec le bouton droit sur l’entité OfficeAssignment et sélectionnez Mappage de procédures stockées.

Image03

Définissez les fonctions Insérer, Mettre à jour et Supprimer pour utiliser leurs procédures stockées correspondantes. Pour le OrigTimestamp paramètre de la Update fonction, définissez la propriété sur Timestamp et sélectionnez l’option Utiliser la valeur d’origine .

Image04

Quand Entity Framework appelle la UpdateOfficeAssignment procédure stockée, il transmet la valeur d’origine de la Timestamp colonne dans le OrigTimestamp paramètre. La procédure stockée utilise ce paramètre dans sa Where clause :

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 procédure stockée sélectionne également la nouvelle valeur de la Timestamp colonne après la mise à jour afin que Entity Framework puisse maintenir l’entité OfficeAssignment en mémoire synchronisée avec la ligne de base de données correspondante.

(Notez que la procédure stockée de suppression d’une affectation office n’a pas de OrigTimestamp paramètre. Pour cette raison, Entity Framework ne peut pas vérifier qu’une entité est inchangée avant de la supprimer.)

Enregistrez et fermez le modèle de données.

Ajout de méthodes OfficeAssignment au DAL

Ouvrez ISchoolRepository.cs et ajoutez les méthodes CRUD suivantes pour le jeu d’entités OfficeAssignment :

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

Ajoutez les nouvelles méthodes suivantes à SchoolRepository.cs. Dans la UpdateOfficeAssignment méthode, vous appelez la méthode locale SaveChanges au lieu de 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();
}

Dans le projet de test, ouvrez MockSchoolRepository.cs et ajoutez-y la collection et les méthodes CRUD suivantes OfficeAssignment . (Le référentiel fictif doit implémenter l’interface du référentiel, sinon la solution ne sera pas compilée.)

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

Ajout de méthodes OfficeAssignment à la BLL

Dans le projet main, ouvrez SchoolBL.cs et ajoutez les méthodes CRUD suivantes pour l’entité OfficeAssignment définie :

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

Création d’une page Web OfficeAssignments

Créez une page web qui utilise la page Site.Master master et nommez-la OfficeAssignments.aspx. Ajoutez le balisage suivant au Content contrôle nommé 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>

Notez que dans l’attribut DataKeyNames , le balisage spécifie la Timestamp propriété ainsi que la clé d’enregistrement (InstructorID). Si vous spécifiez des propriétés dans l’attribut DataKeyNames , le contrôle les enregistre dans un état de contrôle (similaire à l’état d’affichage) afin que les valeurs d’origine soient disponibles pendant le traitement de la publication.

Si vous n’avez pas enregistré la Timestamp valeur, Entity Framework ne l’aurait pas pour la Where clause de la commande SQL Update . Par conséquent, rien ne serait trouvé pour la mise à jour. Par conséquent, Entity Framework lève une exception d’accès concurrentiel optimiste chaque fois qu’une OfficeAssignment entité est mise à jour.

Ouvrez OfficeAssignments.aspx.cs et ajoutez l’instruction suivante using pour la couche d’accès aux données :

using ContosoUniversity.DAL;

Ajoutez la méthode suivante Page_Init , qui active la fonctionnalité Données dynamiques. Ajoutez également le gestionnaire suivant pour l’événement ObjectDataSource du Updated contrôle afin de case activée pour les erreurs d’accès concurrentiel :

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 de l’accès concurrentiel optimiste dans la page OfficeAssignments

Exécutez la page OfficeAssignments.aspx .

Capture d’écran montrant la page Affectations Office.

Cliquez sur Modifier dans une ligne et modifiez la valeur dans la colonne Emplacement .

Image11

Ouvrez une nouvelle fenêtre de navigateur et réexécutez la page (copiez l’URL de la première fenêtre de navigateur vers la deuxième fenêtre de navigateur).

Capture d’écran montrant une nouvelle fenêtre de navigateur.

Cliquez sur Modifier dans la même ligne que celle que vous avez modifiée précédemment et remplacez la valeur Location par quelque chose de différent.

Image12

Dans la deuxième fenêtre de navigateur, cliquez sur Mettre à jour.

Image13

Basculez vers la première fenêtre de navigateur, puis cliquez sur Mettre à jour.

Image15

Vous voyez un message d’erreur et la valeur Location a été mise à jour pour afficher la valeur que vous avez modifiée dans la deuxième fenêtre du navigateur.

Gestion de la concurrence avec le contrôle EntityDataSource

Le EntityDataSource contrôle inclut une logique intégrée qui reconnaît les paramètres d’accès concurrentiel dans le modèle de données et gère les opérations de mise à jour et de suppression en conséquence. Toutefois, comme pour toutes les exceptions, vous devez gérer les OptimisticConcurrencyException exceptions vous-même afin de fournir un message d’erreur convivial.

Ensuite, vous allez configurer la page Courses.aspx (qui utilise un EntityDataSource contrôle) pour autoriser les opérations de mise à jour et de suppression et pour afficher un message d’erreur en cas de conflit d’accès concurrentiel. L’entité Course n’a pas de colonne de suivi de la concurrence. Vous utiliserez donc la même méthode que celle que vous avez utilisée avec l’entité Department : suivre les valeurs de toutes les propriétés non clés.

Ouvrez le fichier SchoolModel.edmx . Pour les propriétés non clés de l’entité Course (Title, Credits, et DepartmentID), définissez la propriété Mode concurrentiel sur Fixed. Enregistrez et fermez ensuite le modèle de données.

Ouvrez la page Courses.aspx et apportez les modifications suivantes :

  • Dans le CoursesEntityDataSource contrôle, ajoutez EnableUpdate="true" des attributs et .EnableDelete="true" La balise d’ouverture de ce contrôle ressemble maintenant à l’exemple suivant :

    <asp:EntityDataSource ID="CoursesEntityDataSource" runat="server" 
            ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="false" 
            AutoGenerateWhereClause="True" EntitySetName="Courses"
            EnableUpdate="true" EnableDelete="true">
    
  • Dans le CoursesGridView contrôle, remplacez la valeur de l’attribut DataKeyNames par "CourseID,Title,Credits,DepartmentID". Ajoutez ensuite un CommandField élément à l’élément Columns qui affiche les boutons Modifier et Supprimer (<asp:CommandField ShowEditButton="True" ShowDeleteButton="True" />). Le GridView contrôle ressemble maintenant à l’exemple suivant :

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

Exécutez la page et créez une situation de conflit comme vous l’avez fait auparavant dans la page Services. Exécutez la page dans deux fenêtres de navigateur, cliquez sur Modifier dans la même ligne dans chaque fenêtre, puis apportez une modification différente dans chacune d’elles. Cliquez sur Mettre à jour dans une fenêtre, puis sur Mettre à jour dans l’autre fenêtre. Lorsque vous cliquez sur Mettre à jour la deuxième fois, vous voyez la page d’erreur qui résulte d’une exception de concurrence non prise en charge.

Image22

Vous gérez cette erreur d’une manière très similaire à celle que vous avez gérée pour le ObjectDataSource contrôle. Ouvrez la page Courses.aspx et, dans le CoursesEntityDataSource contrôle, spécifiez des gestionnaires pour les Deleted événements et Updated . La balise d’ouverture du contrôle ressemble maintenant à l’exemple suivant :

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

Avant le CoursesGridView contrôle, ajoutez le contrôle suivant ValidationSummary :

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

Dans Courses.aspx.cs, ajoutez une using instruction pour l’espace System.Data de noms, ajoutez une méthode qui recherche les exceptions d’accès concurrentiel et ajoutez des gestionnaires pour les gestionnaires et UpdatedDeleted le EntityDataSource contrôle. Le code se présente comme suit :

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

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

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

La seule différence entre ce code et ce que vous avez fait pour le ObjectDataSource contrôle est que, dans ce cas, l’exception d’accès concurrentiel se trouve dans la Exception propriété de l’objet d’arguments d’événement plutôt que dans la propriété de InnerException cette exception.

Exécutez la page et créez à nouveau un conflit d’accès concurrentiel. Cette fois, vous voyez un message d’erreur :

Image23

Ceci termine l’introduction à la gestion des conflits d’accès concurrentiel. Le tutoriel suivant fournit des conseils sur la façon d’améliorer les performances d’une application web qui utilise Entity Framework.