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.
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.)
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é.)
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 SQLUpdate
ouDelete
de commandes.C’est l’objectif de la
Timestamp
colonne dans laOfficeAssignment
table.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 , laWhere
clause inclut la valeur d’origineUpdate
Timestamp
.Delete
Si la ligne en cours de mise à jour a été modifiée par un autre utilisateur, la valeur dansTimestamp
est différente de la valeur d’origine, de sorte que laWhere
clause ne retourne aucune ligne à mettre à jour. Quand Entity Framework détecte qu’aucune ligne n’a été mise à jour par la commande actuelleUpdate
ouDelete
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 desUpdate
commandes etDelete
.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’unTimestamp
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 volumineusesWhere
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 laSaveChanges
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 ConcurrencyMode
Fixed
.
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 .
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.)
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).
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.
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.
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.
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.)
Cliquez avec le bouton droit sur l’entité OfficeAssignment
et sélectionnez Mappage de procédures stockées.
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 .
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 .
Cliquez sur Modifier dans une ligne et modifiez la valeur dans la colonne Emplacement .
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).
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.
Dans la deuxième fenêtre de navigateur, cliquez sur Mettre à jour.
Basculez vers la première fenêtre de navigateur, puis cliquez sur Mettre à jour.
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, ajoutezEnableUpdate="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’attributDataKeyNames
par"CourseID,Title,Credits,DepartmentID"
. Ajoutez ensuite unCommandField
élément à l’élémentColumns
qui affiche les boutons Modifier et Supprimer (<asp:CommandField ShowEditButton="True" ShowDeleteButton="True" />
). LeGridView
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.
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 Updated
Deleted
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 :
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.