Partager via


Insertion par lots (C#)

par Scott Mitchell

Télécharger le PDF

Découvrez comment insérer plusieurs enregistrements de base de données dans une seule opération. Dans la couche Interface utilisateur, nous étendons GridView pour permettre à l’utilisateur d’entrer plusieurs nouveaux enregistrements. Dans la couche d’accès aux données, nous encapsulons les opérations d’insertion multiples au sein d’une transaction pour vous assurer que toutes les insertions réussissent ou que toutes les insertions sont restaurées.

Présentation

Dans le didacticiel de mise à jour par lots , nous avons examiné la personnalisation du contrôle GridView pour présenter une interface où plusieurs enregistrements étaient modifiables. L’utilisateur qui visite la page peut apporter une série de modifications, puis, avec un seul clic sur le bouton, effectuer une mise à jour par lots. Pour les situations où les utilisateurs mettent généralement à jour de nombreux enregistrements en une seule fois, une telle interface peut enregistrer d’innombrables clics et commutateurs de contexte clavier-à-souris par rapport aux fonctionnalités d’édition par ligne par défaut qui ont été d’abord explorées dans le didacticiel Une vue d’ensemble de l’insertion, de la mise à jour et de la suppression de données .

Ce concept peut également être appliqué lors de l’ajout d’enregistrements. Imaginez qu’ici chez Northwind Traders, nous recevons couramment des expéditions de fournisseurs qui contiennent un certain nombre de produits pour une catégorie particulière. Par exemple, nous pourrions recevoir une expédition de six produits de thé et de café différents de Tokyo Traders. Si un utilisateur entre les six produits un à la fois par le biais d’un contrôle DetailsView, il devra choisir plusieurs des mêmes valeurs : il devra choisir la même catégorie (Boissons), le même fournisseur (Tokyo Traders), la même valeur abandonnée (False) et les mêmes unités sur la valeur de commande (0). Cette entrée de données répétitive ne prend pas seulement du temps, mais est sujette à des erreurs.

Avec un peu de travail, nous pouvons créer une interface d’insertion par lots qui permet à l’utilisateur de choisir le fournisseur et la catégorie une fois, d’entrer une série de noms de produits et de prix unitaires, puis de cliquer sur un bouton pour ajouter les nouveaux produits à la base de données (voir la figure 1). À mesure que chaque produit est ajouté, ses champs de données ProductName et UnitPrice sont affectés aux valeurs entrées dans les zones de texte, tandis que ses valeurs CategoryID et SupplierID sont affectées aux valeurs des listes déroulantes en haut du formulaire. Les valeurs Discontinued et UnitsOnOrder sont définies respectivement sur les valeurs codées en dur de false et 0.

Interface d’insertion par lots

Figure 1 : Interface d’insertion par lot (cliquez pour afficher l’image de taille complète)

Dans ce tutoriel, nous allons créer une page qui implémente l’interface d’insertion par lots illustrée à la figure 1. Comme avec les deux didacticiels précédents, nous allons encapsuler les insertions dans le cadre d'une transaction pour garantir l’atomicité. Commençons !

Étape 1 : Création de l’interface d’affichage

Ce tutoriel se compose d’une page unique divisée en deux régions : une région d’affichage et une région d’insertion. L’interface d’affichage, que nous allons créer à cette étape, affiche les produits d’un GridView et inclut un bouton intitulé Traiter l’expédition des produits. Lorsque ce bouton est cliqué, l’interface d’affichage est remplacée par l’interface d’insertion, qui est illustrée dans la figure 1. L’interface d’affichage revient lorsque les boutons Ajouter des produits de l’expédition ou Annuler sont cliqués. Nous allons créer l’interface d’insertion à l’étape 2.

Lors de la création d'une page contenant deux interfaces, dont une seule est visible à la fois, chaque interface est généralement placée dans un contrôle de panneau Web, qui sert de conteneur pour d’autres contrôles. Par conséquent, notre page aura deux contrôles Panneau, chacun pour une interface.

Commencez par ouvrir la BatchInsert.aspx page dans le BatchData dossier et faites glisser un panneau de la boîte à outils vers le concepteur (voir la figure 2). Définissez la propriété ID du Panneau sur DisplayInterface. Lors de l’ajout du panneau au Concepteur, ses propriétés Height et Width sont définies respectivement sur 50px et 125px. Effacez ces valeurs de propriété de la fenêtre Propriétés.

Faire glisser un panneau de la boîte à outils vers le concepteur

Figure 2 : Faire glisser un panneau de la boîte à outils vers le Concepteur (cliquez pour afficher l’image de taille complète)

Ensuite, faites glisser un contrôle de bouton et de grille dans le panneau. Définissez la propriété Button ID sur ProcessShipment et sa Text propriété sur Traiter l’expédition du produit. Définissez la propriété GridView ID sur ProductsGrid et, à partir de sa balise active, liez-la à un nouvel ObjectDataSource nommé ProductsDataSource. Configurez le ObjectDataSource pour extraire ses données de la méthode ProductsBLL de la classe GetProducts. Étant donné que ce GridView est utilisé uniquement pour afficher les données, définissez les listes déroulantes dans les onglets UPDATE, INSERT et DELETE sur (Aucun). Cliquez sur Terminer pour finaliser l’Assistant de configuration de la source de données.

Afficher les données retournées à partir de la méthode GetProducts de la classe ProductsBLL

Figure 3 : Afficher les données retournées à partir de la ProductsBLL méthode de GetProducts classe (Cliquez pour afficher l’image de taille complète)

Définissez les listes Drop-Down dans les onglets UPDATE, INSERT et DELETE sur (Aucun)

Figure 4 : Définir les listes Drop-Down dans les onglets UPDATE, INSERT et DELETE sur (Aucun) (Cliquez pour afficher l’image de taille complète)

Une fois l’Assistant ObjectDataSource terminé, Visual Studio ajoutera des BoundFields et un CheckBoxField pour les champs de données du produit. Supprimez tous les champs sauf les ProductName, CategoryName, SupplierName, UnitPrice, et Discontinued. N’hésitez pas à effectuer toutes les personnalisations esthétiques. J’ai décidé de mettre en forme le UnitPrice champ comme valeur monétaire, de réorganiser les champs et de renommer plusieurs des valeurs de champs HeaderText . Configurez également GridView pour inclure la prise en charge de la pagination et du tri en cochant les cases Activer la pagination et Activer le tri dans la balise intelligente de GridView.

Après avoir ajouté les contrôles Panel, Button, GridView et ObjectDataSource et la personnalisation des champs GridView, le balisage déclaratif de votre page doit ressembler à ce qui suit :

<asp:Panel ID="DisplayInterface" runat="server">
    <p>
        <asp:Button ID="ProcessShipment" runat="server" 
            Text="Process Product Shipment" /> 
    </p>
    <asp:GridView ID="ProductsGrid" runat="server" AllowPaging="True" 
        AllowSorting="True" AutoGenerateColumns="False" 
        DataKeyNames="ProductID" DataSourceID="ProductsDataSource">
        <Columns>
            <asp:BoundField DataField="ProductName" HeaderText="Product" 
                SortExpression="ProductName" />
            <asp:BoundField DataField="CategoryName" HeaderText="Category" 
                ReadOnly="True" SortExpression="CategoryName" />
            <asp:BoundField DataField="SupplierName" HeaderText="Supplier" 
                ReadOnly="True" SortExpression="SupplierName" />
            <asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}" 
                HeaderText="Price" HtmlEncode="False" 
                SortExpression="UnitPrice">
                <ItemStyle HorizontalAlign="Right" />
            </asp:BoundField>
            <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" 
                SortExpression="Discontinued">
                <ItemStyle HorizontalAlign="Center" />
            </asp:CheckBoxField>
        </Columns>
    </asp:GridView>
    <asp:ObjectDataSource ID="ProductsDataSource" runat="server" 
        OldValuesParameterFormatString="original_{0}"
        SelectMethod="GetProducts" TypeName="ProductsBLL">
    </asp:ObjectDataSource>
</asp:Panel>

Notez que le balisage du bouton et du GridView s’affichent dans les balises d’ouverture et de fermeture <asp:Panel> . Étant donné que ces contrôles se trouvent dans le DisplayInterface Panneau, nous pouvons les masquer en définissant simplement la propriété Visible du Panneau sur false. L'étape 3 explore la manière de modifier par programmation la propriété du Visible Panneau en réponse à un clic de bouton, afin d'afficher une interface tout en cachant l'autre.

Prenez un moment pour voir notre progression via un navigateur. Comme le montre la figure 5, vous devez voir un bouton Traiter l’expédition des produits au-dessus d'un GridView qui affiche les produits par groupe de dix.

GridView répertorie les fonctionnalités de tri et de pagination des produits et des offres

Figure 5 : GridView répertorie les fonctionnalités de tri et de pagination des produits et offres (cliquez pour afficher l’image de taille complète)

Étape 2 : Création de l’interface d’insertion

Une fois l’interface d’affichage terminée, nous sommes prêts à créer l’interface d’insertion. Pour ce tutoriel, nous allons créer une interface d’insertion qui demande une valeur de fournisseur et de catégorie unique, puis permet à l’utilisateur d’entrer jusqu’à cinq noms de produits et valeurs de prix unitaires. Avec cette interface, l’utilisateur peut ajouter un à cinq nouveaux produits qui partagent tous la même catégorie et le même fournisseur, mais ont des noms de produits et des prix uniques.

Commencez par faire glisser un panneau de la boîte à outils vers le Concepteur, en le plaçant sous le panneau existant DisplayInterface . Définissez la ID propriété de ce panneau InsertingInterface nouvellement ajouté et définissez sa Visible propriété sur false. Nous ajouterons du code qui définit la propriété du Panneau InsertingInterface à Visible lors de l’étape 3. Effacez également les valeurs des propriétés des éléments Height et Width du Panneau.

Ensuite, nous devons créer l’interface d’insertion qui a été affichée dans la figure 1. Cette interface peut être créée à l’aide de diverses techniques HTML, mais nous allons utiliser une table à quatre colonnes, sept lignes.

Remarque

Lorsque j'entre le balisage pour les éléments HTML <table>, je préfère utiliser la vue 'Source'. Bien que Visual Studio dispose d’outils permettant d’ajouter <table> des éléments via le Concepteur, le concepteur semble trop disposé à injecter des paramètres non sollicités dans le balisage style. Une fois que j’ai créé le <table> balisage, je retourne généralement au Concepteur pour ajouter les contrôles Web et définir leurs propriétés. Lors de la création de tables avec des colonnes et des lignes prédéfinies, je préfère utiliser le code HTML statique plutôt que le contrôle Web Table , car tous les contrôles Web placés dans un contrôle Web table ne sont accessibles qu’à l’aide du FindControl("controlID") modèle. Toutefois, j’utilise des contrôles Web table pour les tables de taille dynamique (celles dont les lignes ou colonnes sont basées sur des critères de base de données ou spécifiés par l’utilisateur), car le contrôle Web Table peut être construit par programmation.

Entrez le balisage suivant dans les <asp:Panel> balises du InsertingInterface panneau :

<table class="DataWebControlStyle" cellspacing="0">
    <tr class="BatchInsertHeaderRow">
        <td class="BatchInsertLabel">Supplier:</td>
        <td></td>
        <td class="BatchInsertLabel">Category:</td>
        <td></td>
    </tr>
    <tr class="BatchInsertRow">
        <td class="BatchInsertLabel">Product:</td>
        <td></td>
        <td class="BatchInsertLabel">Price:</td>
        <td></td>
    </tr>
    <tr class="BatchInsertAlternatingRow">
        <td class="BatchInsertLabel">Product:</td>
        <td></td>
        <td class="BatchInsertLabel">Price:</td>
        <td></td>
    </tr>
    <tr class="BatchInsertRow">
        <td class="BatchInsertLabel">Product:</td>
        <td></td>
        <td class="BatchInsertLabel">Price:</td>
        <td></td>
    </tr>
    <tr class="BatchInsertAlternatingRow">
        <td class="BatchInsertLabel">Product:</td>
        <td></td>
        <td class="BatchInsertLabel">Price:</td>
        <td></td>
    </tr>
    <tr class="BatchInsertRow">
        <td class="BatchInsertLabel">Product:</td>
        <td></td>
        <td class="BatchInsertLabel">Price:</td>
        <td></td>
    </tr>
    <tr class="BatchInsertFooterRow">
        <td colspan="4">
        </td>
    </tr>
</table>

Ce <table> balisage n’inclut pas encore de contrôles Web, nous allons ajouter ces contrôles momentanément. Notez que chaque élément <tr> contient un paramètre de classe CSS particulier : BatchInsertHeaderRow pour la ligne d’en-tête où le fournisseur et la catégorie DropDownLists seront placés ; BatchInsertFooterRow pour la ligne de pied de page où les boutons Ajouter des produits à partir de l’expédition et Annuler seront placés ; et des valeurs BatchInsertRow et BatchInsertAlternatingRow alternées pour les lignes qui contiendront les contrôles TextBox du produit et du prix unitaire. J’ai créé des classes CSS correspondantes dans le Styles.css fichier pour donner à l’interface d’insertion une apparence similaire aux contrôles GridView et DetailsView que nous avons utilisés dans ces didacticiels. Ces classes CSS sont indiquées ci-dessous.

/*** Styles for ~/BatchData/BatchInsert.aspx tutorial ***/
.BatchInsertLabel
{
    font-weight: bold;
    text-align: right;
}
.BatchInsertHeaderRow td
{
    color: White;
    background-color: #900;
    padding: 11px;
}
.BatchInsertFooterRow td
{
    text-align: center;
    padding-top: 5px;
}
.BatchInsertRow
{
}
.BatchInsertAlternatingRow
{
    background-color: #fcc;
}

Avec ce balisage entré, revenez à la vue Création. Cela <table> doit s’afficher sous la forme d’une table à quatre colonnes, de sept lignes dans le Concepteur, comme l’illustre la figure 6.

L’interface d’insertion est composée d’une table à quatre colonnes Seven-Row

Figure 6 : L’interface d’insertion est composée d’une table à quatre colonnes Seven-Row (cliquez pour afficher l’image de taille complète)

Nous sommes maintenant prêts à ajouter les contrôles Web à l’interface d’insertion. Faites glisser deux listes déroulantes de la boîte à outils dans les cellules appropriées du tableau, une pour le fournisseur et une pour la catégorie.

Définissez la propriété ID du DropDownList fournisseur à Suppliers et liez-la à un ObjectDataSource nommé SuppliersDataSource. Configurez la nouvelle ObjectDataSource pour récupérer ses données à partir de la classe SuppliersBLL méthode GetSuppliers et définissez la liste déroulante de l'onglet MISE À JOUR sur (Aucun). Cliquez sur Terminer pour finaliser l'Assistant.

Configurer ObjectDataSource pour utiliser la méthode GetSuppliers de la classe SuppliersBLL

Figure 7 : Configurer ObjectDataSource pour utiliser la SuppliersBLL méthode de classe GetSuppliers (Click pour afficher l’image de taille complète)

Faire en sorte que la Suppliers liste déroulante affiche le champ de données CompanyName et utilise le champ de données SupplierID comme ses valeurs ListItem.

Afficher le champ de données CompanyName et utiliser SupplierID comme valeur

Figure 8 : Afficher le CompanyName champ de données et utiliser SupplierID comme valeur (cliquez pour afficher l’image de taille complète)

Nommez le deuxième DropDownList Categories et liez-le à un nouvel ObjectDataSource nommé CategoriesDataSource. Configurez CategoriesDataSource ObjectDataSource pour utiliser la méthode CategoriesBLL de classe GetCategories ; définissez les listes déroulantes des onglets UPDATE et DELETE sur (Aucun) et cliquez sur Terminer pour terminer l’Assistant. Enfin, faites afficher le champ de données CategoryName par la liste déroulante et utilisez CategoryID comme valeur.

Une fois ces deux DropDownLists ajoutés et liés à des ObjectDataSources configurés de manière appropriée, votre écran doit ressembler à la figure 9.

La ligne d’en-tête contient maintenant les listes déroulantes Fournisseurs et catégories

Figure 9 : La ligne d’en-tête contient maintenant les listes déroulantes Suppliers et Categories (Cliquez pour afficher l’image en taille réelle)

Nous devons maintenant créer les TextBox pour collecter le nom et le prix de chaque nouveau produit. Faites glisser un contrôle TextBox de la boîte à outils vers le Concepteur pour chacune des cinq lignes de noms de produits et de prix. Définissez les propriétés des zones de texte à ID, ProductName1, UnitPrice1, ProductName2, UnitPrice2, ProductName3, et ainsi de suite.

Ajoutez un CompareValidator après chacune des zones de texte du prix unitaire, en définissant la propriété ControlToValidate sur ID appropriée. Définissez également la propriété Operator à GreaterThanEqual, ValueToCompare à 0 et Type à Currency. Ces paramètres indiquent à CompareValidator de s’assurer que le prix, s’il est entré, est une valeur monétaire valide supérieure ou égale à zéro. Définissez la propriété Text à *, et définissez ErrorMessage à 'Le prix doit être supérieur ou égal à zéro.' En outre, omettez tous les symboles monétaires.

Remarque

L’interface d’insertion n’inclut aucun contrôleur RequiredFieldValidator, même si le champ ProductName de la table de base de données Products n’autorise pas les valeurs NULL. Cela est dû au fait que nous voulons permettre à l’utilisateur d’entrer jusqu’à cinq produits. Par exemple, si l’utilisateur devait fournir le nom du produit et le prix unitaire pour les trois premières lignes, en laissant les deux dernières lignes vides, nous allons simplement ajouter trois nouveaux produits au système. Étant donné que ProductName est requis, nous devons néanmoins vérifier par programme que si un prix unitaire est saisi, une valeur de nom de produit correspondante est fournie. Nous aborderons cette vérification à l’étape 4.

Lors de la validation de l’entrée de l’utilisateur, compareValidator signale des données non valides si la valeur contient un symbole monétaire. Ajoutez un $ devant chacun des textBox de prix unitaires pour servir de repère visuel qui indique à l’utilisateur d’omettre le symbole monétaire lors de l’entrée du prix.

Enfin, ajoutez un contrôle ValidationSummary dans le InsertingInterface Panneau, en définissant sa propriété ShowMessageBox à true et sa propriété ShowSummary à false. Avec ces paramètres, si l’utilisateur entre une valeur de prix unitaire non valide, un astérisque apparaît en regard des contrôles TextBox incriminés et validationSummary affiche une boîte de message côté client qui affiche le message d’erreur que nous avons spécifié précédemment.

À ce stade, votre écran doit ressembler à la figure 10.

L’interface d’insertion inclut désormais des zones de texte pour les noms et les prix des produits

Figure 10 : L’interface d’insertion inclut désormais des zones de texte pour les noms et les prix des produits (cliquez pour afficher l’image de taille complète)

Ensuite, nous devons ajouter les boutons Ajouter des produits depuis l'expédition et Annuler à la ligne de pied de page. Faites glisser deux contrôles Button de la boîte à outils dans le pied de page de l'interface d'insertion, en définissant les propriétés du premier Button sur Ajouter des produits de l'expédition et celles du deuxième Button sur Annuler, respectivement. En outre, définissez la propriété CancelButton du contrôle CausesValidation à false.

Enfin, nous devons ajouter un contrôle Web Label qui affiche les messages d’état pour les deux interfaces. Par exemple, lorsqu’un utilisateur ajoute avec succès une nouvelle expédition de produits, nous voulons revenir à l’interface d’affichage et afficher un message de confirmation. Toutefois, si l’utilisateur fournit un prix pour un nouveau produit, mais quitte le nom du produit, nous devons afficher un message d’avertissement, car le ProductName champ est requis. Étant donné que nous avons besoin que ce message s’affiche pour les deux interfaces, placez-le en haut de la page en dehors des panneaux.

Faites glisser un contrôle Web Label de la boîte à outils vers le haut de la page dans le designer. Définissez la propriété ID sur StatusLabel, supprimez la propriété Text, et définissez les propriétés Visible et EnableViewState sur false. Comme nous l’avons vu dans les tutoriels précédents, définir la propriété EnableViewState à false nous permet de modifier par programmation les valeurs de la propriété Label et de revenir automatiquement à leurs valeurs par défaut lors de la publication suivante. Cela simplifie le code permettant d’afficher un message d’état en réponse à une action utilisateur qui disparaît lors de la publication suivante. Enfin, définissez la propriété StatusLabel du contrôle CssClass sur Warning, qui est le nom d’une classe CSS définie dans Styles.css qui affiche le texte dans une police volumineuse, italique, en gras, rouge.

La figure 11 montre le Concepteur Visual Studio une fois que l’étiquette a été ajoutée et configurée.

Placer le contrôle StatusLabel au-dessus des deux contrôles du panneau

Figure 11 : Placer le StatusLabel contrôle au-dessus des deux contrôles du panneau (cliquez pour afficher l’image de taille complète)

Étape 3 : Basculer entre les interfaces d’affichage et d’insertion

À ce stade, nous avons terminé le balisage pour nos interfaces d’affichage et d’insertion, mais nous sommes toujours laissés avec deux tâches :

  • Basculement entre les interfaces d’affichage et d’insertion
  • Ajout des produits dans l’expédition à la base de données

Actuellement, l’interface d’affichage est visible, mais l’interface d’insertion est masquée. Cela est dû au fait que la propriété du DisplayInterface Panneau est définie sur Visible (la valeur par défaut), tandis que la propriété du true Panneau est définie sur InsertingInterface. Pour basculer entre les deux interfaces, nous devons simplement activer/désactiver chaque valeur de propriété du Visible contrôle.

Nous voulons passer de l’interface d’affichage à l’interface d’insertion lorsque le bouton Traiter l’expédition du produit est cliqué. Par conséquent, créez un gestionnaire d’événements pour cet événement Button Click qui contient le code suivant :

protected void ProcessShipment_Click(object sender, EventArgs e)
{
    DisplayInterface.Visible = false;
    InsertingInterface.Visible = true;
}

Ce code masque simplement le DisplayInterface panneau et affiche le InsertingInterface panneau.

Ensuite, créez des gestionnaires d’événements pour les contrôles Add Products from Shipment and Cancel Button dans l’interface d’insertion. Lorsque l’un de ces boutons est cliqué, nous devons revenir à l’interface d’affichage. Créez Click des gestionnaires d'événements pour les deux contrôles de bouton afin qu'ils appellent ReturnToDisplayInterface, une méthode que nous ajouterons temporairement. Outre le masquage du InsertingInterface panneau et l’affichage du DisplayInterface panneau, la ReturnToDisplayInterface méthode doit retourner les contrôles Web à leur état avant modification. Cela implique de définir les propriétés DropDownLists SelectedIndex sur 0 et de supprimer les Text propriétés des contrôles TextBox.

Remarque

Considérez ce qui peut se produire si nous n’avons pas retourné les contrôles à leur état de préversion avant de revenir à l’interface d’affichage. Un utilisateur peut cliquer sur le bouton Traiter l’expédition du produit, entrer les produits à partir de l’expédition, puis cliquer sur Ajouter des produits à partir de l’expédition. Cela ajouterait les produits et renvoyait l’utilisateur à l’interface d’affichage. À ce stade, l’utilisateur peut souhaiter ajouter une autre expédition. Lorsque vous cliquez sur le bouton Traiter l’expédition du produit, ils retournent à l’interface d’insertion, mais les sélections DropDownList et les valeurs TextBox sont toujours renseignées avec leurs valeurs précédentes.

protected void AddProducts_Click(object sender, EventArgs e)
{
    // TODO: Save the products
    // Revert to the display interface
    ReturnToDisplayInterface();
}
protected void CancelButton_Click(object sender, EventArgs e)
{
    // Revert to the display interface
    ReturnToDisplayInterface();
}
const int firstControlID = 1;
const int lastControlID = 5;
private void ReturnToDisplayInterface()
{
    // Reset the control values in the inserting interface
    Suppliers.SelectedIndex = 0;
    Categories.SelectedIndex = 0;
    for (int i = firstControlID; i <= lastControlID; i++)
    {
        ((TextBox)InsertingInterface.FindControl("ProductName" + i.ToString())).Text =
            string.Empty;
        ((TextBox)InsertingInterface.FindControl("UnitPrice" + i.ToString())).Text = 
            string.Empty;
    }
    DisplayInterface.Visible = true;
    InsertingInterface.Visible = false;
}

Les deux Click gestionnaires d’événements appellent simplement la ReturnToDisplayInterface méthode, bien que nous revenions au gestionnaire d’événements "Add Products from Shipment" Click à l’étape 4 et ajouterons du code pour enregistrer les produits. ReturnToDisplayInterface commence par ramener les listes déroulantes Suppliers et Categories à leurs premières options. Les deux constantes firstControlID et lastControlID marquent les valeurs d’index de contrôle de début et de fin utilisées pour nommer le nom du produit et les textBox de prix unitaire dans l’interface d’insertion et sont utilisées dans les limites de la for boucle qui définit les Text propriétés des contrôles TextBox sur une chaîne vide. Enfin, les propriétés panneaux Visible sont réinitialisées afin que l’interface d’insertion soit masquée et l’interface d’affichage affichée.

Prenez un moment pour tester cette page dans un navigateur. Lors de la première visite de la page, vous devez voir l’interface d’affichage comme illustré dans la figure 5. Cliquez sur le bouton Traiter l’expédition du produit. La page effectuera un retour et vous devriez maintenant voir l’interface d’insertion comme indiqué dans la figure 12. En cliquant sur les boutons Ajouter des produits à partir de l’expédition ou Annuler, vous revenez à l’interface d’affichage.

Remarque

Lors de l'affichage de l'interface d'insertion, prenez un moment pour tester les CompareValidators sur les champs de texte de prix unitaires. Vous devriez voir un avertissement dans une boîte de dialogue côté client lorsque vous cliquez sur le bouton Ajouter des produits de l’expédition avec des valeurs de devise non valides ou des prix ayant une valeur inférieure à zéro.

L’interface d’insertion s’affiche après avoir cliqué sur le bouton Traiter l’expédition du produit

Figure 12 : L’interface d’insertion s’affiche après avoir cliqué sur le bouton Traiter l’expédition du produit (cliquez pour afficher l’image de taille complète)

Étape 4 : Ajout des produits

Tout ce qui reste pour ce tutoriel consiste à enregistrer les produits dans la base de données dans le gestionnaire d’événements Add Products from Shipment Button.Click Pour ce faire, créez une ProductsDataTable instance et ajoutez une ProductsRow instance pour chacun des noms de produits fournis. Une fois ces ProductsRow ajoutés, nous allons appeler la méthode ProductsBLL de la classe UpdateWithTransaction en passant le ProductsDataTable. Rappelez-vous que la méthode UpdateWithTransaction, qui a été créée lors du tutoriel Modifications de la base de données encapsulées dans une transaction, passe la ProductsDataTable à la méthode s ProductsTableAdapterUpdateWithTransaction. À partir de là, une transaction ADO.NET est démarrée et TableAdapter émet une INSERT instruction à la base de données pour chaque ajout ProductsRow dans DataTable. En supposant que tous les produits sont ajoutés sans erreur, la transaction est validée, sinon elle est restaurée.

Le code du gestionnaire d’événements pour le bouton Click Add Products from Shipment doit aussi effectuer un contrôle d'erreurs. Étant donné qu’aucun RequiredFieldValidators n’est utilisé dans l’interface d’insertion, un utilisateur peut entrer un prix pour un produit tout en omettant son nom. Étant donné que le nom du produit est requis, si une telle condition se déroule, nous devons alerter l’utilisateur et ne pas poursuivre les insertions. Le code complet Click du gestionnaire d’événements suit :

protected void AddProducts_Click(object sender, EventArgs e)
{
    // Make sure that the UnitPrice CompareValidators report valid data...
    if (!Page.IsValid)
        return;
    // Add new ProductsRows to a ProductsDataTable...
    Northwind.ProductsDataTable products = new Northwind.ProductsDataTable();
    for (int i = firstControlID; i <= lastControlID; i++)
    {
        // Read in the values for the product name and unit price
        string productName = ((TextBox)InsertingInterface.FindControl
            ("ProductName" + i.ToString())).Text.Trim();
        string unitPrice = ((TextBox)InsertingInterface.FindControl
            ("UnitPrice" + i.ToString())).Text.Trim();
        // Ensure that if unitPrice has a value, so does productName
        if (unitPrice.Length > 0 && productName.Length == 0)
        {
            // Display a warning and exit this event handler
            StatusLabel.Text = "If you provide a unit price you must also " +
                "include the name of the product.";
            StatusLabel.Visible = true;
            return;
        }
        // Only add the product if a product name value is provided
        if (productName.Length > 0)
        {
            // Add a new ProductsRow to the ProductsDataTable
            Northwind.ProductsRow newProduct = products.NewProductsRow();
            // Assign the values from the web page
            newProduct.ProductName = productName;
            newProduct.SupplierID = Convert.ToInt32(Suppliers.SelectedValue);
            newProduct.CategoryID = Convert.ToInt32(Categories.SelectedValue);
            if (unitPrice.Length > 0)
                newProduct.UnitPrice = Convert.ToDecimal(unitPrice);
            // Add any "default" values
            newProduct.Discontinued = false;
            newProduct.UnitsOnOrder = 0;
            products.AddProductsRow(newProduct);
        }
    }
    // If we reach here, see if there were any products added
    if (products.Count > 0)
    {
        // Add the new products to the database using a transaction
        ProductsBLL productsAPI = new ProductsBLL();
        productsAPI.UpdateWithTransaction(products);
        // Rebind the data to the grid so that the products just added are displayed
        ProductsGrid.DataBind();
        // Display a confirmation (don't use the Warning CSS class, though)
        StatusLabel.CssClass = string.Empty;
        StatusLabel.Text = string.Format(
            "{0} products from supplier {1} have been added and filed under " + 
            "category {2}.", products.Count, Suppliers.SelectedItem.Text, 
            Categories.SelectedItem.Text);
        StatusLabel.Visible = true;
        // Revert to the display interface
        ReturnToDisplayInterface();
    }
    else
    {
        // No products supplied!
        StatusLabel.Text = "No products were added. Please enter the product " + 
            "names and unit prices in the textboxes.";
        StatusLabel.Visible = true;
    }
}

Le gestionnaire d’événements commence par s’assurer que la Page.IsValid propriété retourne une valeur de true. Si elle retourne false, cela signifie qu’une ou plusieurs des CompareValidators signalent des données non valides ; dans ce cas, nous ne voulons pas tenter d’insérer les produits entrés ou nous finirons par une exception lors de la tentative d’affectation de la valeur de prix unitaire entrée par l’utilisateur à la ProductsRow propriété s UnitPrice .

Ensuite, une nouvelle ProductsDataTable instance est créée (products). Une for boucle est utilisée pour itérer dans le nom du produit et les TextBoxes de prix unitaires, et les Text propriétés sont lues dans les variables locales productName et unitPrice. Si l’utilisateur a entré une valeur pour le prix unitaire, mais pas pour le nom du produit correspondant, le StatusLabel message s’affiche si vous fournissez un prix unitaire, vous devez également inclure le nom du produit et le gestionnaire d’événements est quitté.

Si un nom de produit a été fourni, une nouvelle ProductsRow instance est créée à l’aide de la ProductsDataTable méthode s NewProductsRow . Cette nouvelle instance ProductsRow a sa propriété ProductName définie sur le nom de produit actuel dans la TextBox, tandis que les propriétés SupplierID et CategoryID sont affectées aux propriétés SelectedValue des listes déroulantes situées dans l’en-tête de l’interface d’insertion. Si l’utilisateur a entré une valeur pour le prix du produit, elle est attribuée à la propriété ProductsRow de l’instance UnitPrice ; sinon, la propriété reste non attribuée, ce qui entraînera une valeur NULL pour UnitPrice dans la base de données. Enfin, les propriétés Discontinued et UnitsOnOrder sont affectées aux valeurs fixées false et à 0 respectivement.

Une fois que les propriétés ont été affectées à l’instance ProductsRow , elle est ajoutée au ProductsDataTable.

À l’achèvement de la for boucle, nous vérifions si des produits ont été ajoutés. L’utilisateur peut, après tout, avoir cliqué sur Ajouter des produits à partir de l’expédition avant d’entrer des noms de produits ou des prix. S'il y a au moins un produit dans le ProductsDataTable, la méthode s de la classe ProductsBLL est appelée. Ensuite, les données sont reliées à ProductsGrid GridView afin que les produits nouvellement ajoutés apparaissent dans l’interface de visualisation. ** Le StatusLabel est mis à jour pour afficher un message de confirmation, et le ReturnToDisplayInterface est invoqué, masquant l'interface d'insertion et affichant l'interface de visualisation.

Si aucun produit n’a été entré, l’interface d’insertion reste affichée, mais le message Aucun produit n’a été ajouté. Entrez les noms de produits et les prix unitaires dans les zones de texte.

La figure 13, 14 et 15 montre les interfaces d’insertion et d’affichage en action. Dans la figure 13, l’utilisateur a entré une valeur de prix unitaire sans nom de produit correspondant. La figure 14 montre l’interface d’affichage après l’ajout de trois nouveaux produits, tandis que la figure 15 montre deux des nouveaux produits ajoutés dans GridView (le troisième est sur la page précédente).

Un nom de produit est requis lors de l’entrée d’un prix unitaire

Figure 13 : Un nom de produit est obligatoire lors de l’entrée d’un prix unitaire (cliquez pour afficher l’image de taille complète)

Trois nouveaux veggies ont été ajoutés pour le fournisseur Mayumi s

Figure 14 : Trois nouveaux veggies ont été ajoutés pour le fournisseur Mayumi s (cliquez pour afficher l’image pleine taille)

Les nouveaux produits sont disponibles dans la dernière page du GridView

Figure 15 : Les nouveaux produits sont disponibles dans la dernière page du GridView (cliquez pour afficher l’image de taille complète)

Remarque

La logique d’insertion par lots utilisée dans ce didacticiel encapsule les insertions dans le cadre d'une transaction. Pour vérifier cela, introduisez délibérément une erreur au niveau de la base de données. Par exemple, plutôt que d’affecter la nouvelle ProductsRow propriété d’instance CategoryID à la valeur sélectionnée dans Categories dropDownList, affectez-la à une valeur telle que i * 5. Voici i l’indexeur de boucle et a des valeurs comprises entre 1 et 5. Par conséquent, lors de l’ajout de deux produits ou plus dans l’insertion par lots, le premier produit aura une valeur valide CategoryID (5), mais les produits suivants auront CategoryID des valeurs qui ne correspondent pas aux CategoryID valeurs dans la Categories table. L'effet net est le suivant : la première opération réussira, mais les suivantes échoueront en raison d'une erreur de contrainte de clé étrangère. Étant donné que l'insertion par lot est atomique, la première INSERT sera annulée, ramenant la base de données à son état avant le début du processus d'insertion par lot.

Résumé

Dans ce didacticiel et les deux précédents, nous avons créé des interfaces qui permettent de mettre à jour, de supprimer et d’insérer des lots de données, toutes utilisant le support de transaction que nous avons ajouté à la couche d’accès aux données dans le didacticiel Encapsuler les modifications de la base de données dans une transaction. Pour certains scénarios, ces interfaces utilisateur de traitement par lots améliorent considérablement l’efficacité de l’utilisateur final en réduisant le nombre de clics, de postbacks et de commutateurs contextuels clavier à souris, tout en conservant l’intégrité des données sous-jacentes.

Ce tutoriel complète notre présentation de l’utilisation des données par lots. L’ensemble de didacticiels suivant explore divers scénarios avancés de couche d’accès aux données, notamment l’utilisation de procédures stockées dans les méthodes tableAdapter, la configuration des paramètres de connexion et de niveau commande dans le dal, le chiffrement des chaînes de connexion, etc.

Bonne programmation !

À propos de l’auteur

Scott Mitchell, auteur de sept livres ASP/ASP.NET et fondateur de 4GuysFromRolla.com, travaille avec les technologies Web Microsoft depuis 1998. Scott travaille en tant que consultant indépendant, formateur et écrivain. Son dernier livre est Sams Teach Yourself ASP.NET 2.0 en 24 heures. Il peut être contacté à mitchell@4GuysFromRolla.com.

Merci spécial à

Cette série de tutoriels a été examinée par de nombreux réviseurs utiles. Les réviseurs principaux pour ce tutoriel étaient Hilton Giesenow et S ren Jacob Lauritsen. Vous souhaitez consulter mes prochains articles MSDN ? Si c’est le cas, déposez-moi une ligne à mitchell@4GuysFromRolla.com.