LINQ to DataSet
Transcription
LINQ to DataSet
Linq.book Page 333 Mercredi, 18. février 2009 7:58 07 10 LINQ to DataSet Bien que LINQ to SQL n’ait pas encore été abordé dans cet ouvrage, je voudrais signaler que, pour utiliser LINQ to SQL sur une base de données, les classes de code source doivent être générées et compilées spécifiquement pour cette base de données, ou qu’un fichier de mapping doit être créé. Cela signifie qu’il est impossible d’effectuer des requêtes LINQ to SQL sur une base de données inconnue jusqu’à l’exécution. Mais alors que doit faire le développeur ? Les opérateurs LINQ to DataSet permettent d’exécuter des requêtes LINQ sur des DataSet. Étant donné qu’un DataSet peut être récupéré par une requête SQL ADO.NET, LINQ to DataSet permet d’effectuer des requêtes sur toute base de données accessible via ADO.NET. Cela offre un dynamisme bien plus grand que si vous utilisiez LINQ to SQL. Vous pouvez vous demander dans quelles circonstances la base de données pourrait ne pas être connue jusqu’à l’exécution. Effectivement, dans les applications traditionnelles, la base de données est connue pendant le développement, et LINQ to DataSet n’est pas un passage obligé. Mais qu’en est-il si vous développez un utilitaire pour bases de données ? Considérons une application telle que SQL Server Enterprise Manager (l’interface graphique de SQL Server pour les tâches de création et d’administration des bases de données). Jusqu’à l’exécution, cette application ne connaît pas les bases de données qui ont été installées. Cependant, elle vous permet de connaître leur nom ainsi que celui des différentes tables accessibles dans chacune d’entre elles. Le développeur d’une telle application n’a aucun moyen de générer les classes LINQ to SQL nécessaires à l’interfaçage des différentes bases de données à l’exécution. LINQ to DataSet devient donc une nécessité. Bien que ce chapitre soit intitulé "LINQ to DataSet", vous verrez que les opérateurs passés en revue sont essentiellement relatifs aux objets DataTable, DataRow et DataColumn. Ne soyez pas surpris si ce chapitre ne fait pas souvent référence aux objets DataSets. Il est bien entendu qu’en circonstances réelles vos objets DataTable viendront Linq.book Page 334 Mercredi, 18. février 2009 7:58 07 334 LINQ to DataSet Partie IV essentiellement d’objets DataSets. Cependant, pour des raisons d’indépendance, de concision et de clarté, la plupart des exemples de ce chapitre se basent sur de simples objets DataTable créés par programme. Les données traitées ne sont donc pas extraites d’une base de données existante. LINQ to DataSet donne accès à plusieurs opérateurs spécifiques issus de différents assemblies et espaces de noms. Ces opérateurs permettent au développeur d’effectuer les actions suivantes : m définitions de séquences d’objets DataRows ; m recherche et modification de valeurs DataColumn ; m obtention de séquences LINQ standard IEnumerable<T> à partir de DataTable afin de pouvoir leur appliquer des opérateurs de requête standard ; m copie de séquences de DataRow modifiées dans un DataTable. Outre ces opérateurs LINQ to DataSet, une fois l’opérateur AsEnumerable appelé, vous pouvez utiliser les opérateurs de requête standard de LINQ to Objects sur la séquence DataRow retournée, ce qui ajoute encore plus de puissance et de flexibilité à LINQ to DataSet. Référence des assemblies Pour exécuter les exemples de ce chapitre, vous devrez (si elles ne sont pas déjà présentes) ajouter des références aux assemblies System.Data.dll et System.Data.DataSetExtensions.dll. Espaces de noms référencés Pour être en mesure d’utiliser les opérateurs LINQ to DataSet, vous devez ajouter (si elles ne sont pas déjà présentes) les deux directives using suivantes en tête de votre code : using System.Data; using System.Linq; Code commun utilisé dans les exemples Tous les exemples de ce chapitre ont besoin d’un objet DataTable pour effectuer des requêtes LINQ to DataSet. Dans un code de production réel, ces objets sont typiquement obtenus en effectuant une requête sur une base de données. Dans plusieurs exemples de ce chapitre, cette configuration est inconfortable, voire insuffisante. À titre d’exemple, nous aurons besoin de deux enregistrements identiques pour illustrer la méthode Distinct. Plutôt que jongler avec une base de données pour obtenir les enregistrements Linq.book Page 335 Mercredi, 18. février 2009 7:58 07 Chapitre 10 LINQ to DataSet 335 nécessaires, nous avons préféré créer par programme un objet DataTable qui contient les données nécessaires à chaque exemple. Pour faciliter la définition de l’objet DataTable, nous utiliserons un tableau d’objets contenu dans la classe prédéfinie Student. Une classe simpliste avec deux membres publics class Student { public int Id; public string Name; } Vous n’avez qu’à imaginer que nous interrogeons la table Students, composée de deux colonnes (Id et Name) et dans laquelle chaque enregistrement représente un étudiant. Pour faciliter la création du DataTable et pour ne pas nuire aux détails de chaque exemple, nous utiliserons une méthode commune pour convertir un tableau d’objets Student en un objet DataTable. Ceci nous permettra de faire varier simplement les données d’un exemple à l’autre. Voici le code de cette méthode commune : Conversion d’un tableau d’objets Student en un DataTable static DataTable GetDataTable(Student[] students) { DataTable table = new DataTable(); table.Columns.Add("Id", typeof(Int32)); table.Columns.Add("Name", typeof(string)); foreach (Student student in students) { table.Rows.Add(student.Id, student.Name); } return (table); } Cette méthode n’a rien de bien compliqué. Elle se contente d’instancier un objet DataTable, puis d’ajouter deux colonnes et une ligne pour chacun des éléments du tableau students passé en argument. Pour plusieurs des exemples de ce chapitre, il est nécessaire d’afficher un objet DataTable, pour s’assurer que les résultats sont conformes aux attentes. D’un exemple à l’autre, les données du DataTable peuvent varier, mais le code permettant d’afficher le contenu du DataTable reste le même. Plutôt que répéter ce code dans tous les exemples, nous avons créé une méthode commune que nous appellerons chaque fois que cela sera nécessaire : La méthode OutputDataTableHeader static void OutputDataTableHeader(DataTable dt, int columnWidth) { string format = string.Format("{0}0,-{1}{2}", "{", columnWidth, "}"); // Display the column headings. Linq.book Page 336 Mercredi, 18. février 2009 7:58 07 336 LINQ to DataSet Partie IV foreach(DataColumn column in dt.Columns) { Console.Write(format, column.ColumnName); } Console.WriteLine(); foreach(DataColumn column in dt.Columns) { for(int i = 0; i < columnWidth; i++) { Console.Write("="); } } Console.WriteLine(); } Cette méthode affiche l’en-tête d’un objet DataTable sous une forme tabulaire. Opérateurs dédiés aux DataRow Vous vous souvenez certainement que l’API LINQ to Objects contient un ensemble d’opérateurs de requête standard très utiles lorsqu’il s’agit d’initialiser et/ou de comparer des séquences. Je fais référence aux opérateurs Distinct, Except, Intersect, Union et SequenceEqual, qui définissent une séquence en fonction d’une autre. Chacun de ces opérateurs doit être en mesure de tester l’égalité des éléments d’une séquence pour effectuer l’opération pour laquelle il a été conçu. Le test d’égalité se fait en appliquant les méthodes GetHashCode et Equals aux éléments. En ce qui concerne les DataRow, ces deux méthodes provoquent la comparaison des références des éléments, ce qui n’est pas le comportement souhaité. Mais, rassurez-vous, ces opérateurs possèdent un autre prototype dont nous n’avons pas parlé dans les chapitres relatifs à LINQ to Objects. Ce prototype permet de passer un argument complémentaire : IEqualityComparer. Par commodité, un objet comparateur a été spécialement défini pour ces versions des opérateurs : System.Data.DataRowComparer.Default. Cette classe se trouve dans l’espace de noms System.Data et l’assembly System.Data.Entity.dll. L’égalité est déterminée en comparant le nombre de colonnes et le type de donnée statique de chaque colonne et en utilisant l’interface IComparable sur le type de donnée dynamique de la colonne si celui-ci l’implémente. Dans le cas contraire, la méthode System.Object Equals est appelée. Ces prototypes sont définis dans la même classe statique que les autres : System.Linq.Enumerable. Dans cette section, nous allons donner quelques exemples pour illustrer la mauvaise et, bien entendu, la bonne façon d’effectuer des comparaisons sur des objets DataSet. Opérateur Distinct L’opérateur Distinct supprime les lignes en double dans une séquence d’objets. Il retourne un objet dont l’énumération renvoie la séquence source privée des doublons. Linq.book Page 337 Mercredi, 18. février 2009 7:58 07 Chapitre 10 LINQ to DataSet 337 Cet opérateur devrait pouvoir déterminer l’égalité entre les différentes lignes en appelant les méthodes GetHashCode et Equals sur chacun des éléments. Cependant, pour des objets de type DataRow, cette technique ne donne pas le résultat recherché. Pour obtenir le résultat escompté, nous appellerons un nouveau prototype de cet opérateur et nous lui passerons le comparateur System.Data.DataRowComparer.Default dans son deuxième argument. La comparaison est effectuée sur chacune des colonnes d’une ligne. Elle se base sur le type de donnée statique de chaque colonne. L’interface IComparable est utilisée sur les colonnes qui implémentent cette interface. La méthode statique System.Object.Equals est utilisée sur les autres. Prototype Un seul prototype de l’opérateur Distinct sera étudié dans ce chapitre : public static IEnumerable<T> Distinct<T> ( this IEnumerable<T> source, IEqualityComparer<T> comparer); Exemples Dans le premier exemple, l’objet DataTable sera créé en appliquant la méthode commune GetDataTable à un tableau d’objet Student. À dessein, ce tableau comprendra deux fois la même ligne : celle dont le champ Id vaut 1. Pour mettre en évidence la ligne en double dans le DataTable, le tableau sera affiché. La ligne en double sera ensuite enlevée à l’aide de l’opérateur Distinct, et l’objet DataTable sera à nouveau affiché, pour montrer que le doublon a été supprimé. Le code utilisé apparaît dans le Listing 10.1. Listing 10.1 : L’opérateur Distinct associé à un comparateur d’égalité. Student[] students new Student { Id new Student { Id new Student { Id new Student { Id new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = = = = = { 1, Name = "Joe Rattz" }, 6, Name = "Ulyses Hutchens" }, 19, Name = "Bob Tanko" }, 45, Name = "Erin Doutensal" }, 1, Name = "Joe Rattz" }, 12, Name = "Bob Mapplethorpe" }, 17, Name = "Anthony Adams" }, 32, Name = "Dignan Stephens" } DataTable dt = GetDataTable(students); Console.WriteLine("{0}Avant l’appel à Distinct(){0}", System.Environment.NewLine); OutputDataTableHeader(dt, 15); Linq.book Page 338 Mercredi, 18. février 2009 7:58 07 338 LINQ to DataSet Partie IV foreach (DataRow dataRow in dt.Rows) { Console.WriteLine("{0,-15}{1,-15}", dataRow.Field<int>(0), dataRow.Field<string>(1)); } IEnumerable<DataRow> distinct = dt.AsEnumerable().Distinct(DataRowComparer.Default); Console.WriteLine("{0}Après l’appel à Distinct(){0}", System.Environment.NewLine); OutputDataTableHeader(dt, 15); foreach (DataRow dataRow in distinct) { Console.WriteLine("{0,-15}{1,-15}", dataRow.Field<int>(0), dataRow.Field<string>(1)); } L’opérateur AsEnumerable a été utilisé pour obtenir la séquence d’objets DataRow à partir du DataTable. Ceci afin d’assurer la compatibilité avec l’opérateur Distinct. Remarquez également que, dans le tableau students, la ligne dont le champ Id vaut "1" apparaît en double. Vous avez sans doute noté que la méthode Field a été appelée sur l’objet DataRow. Pour l’instant, tout ce que vous devez en savoir, c’est qu’il s’agit d’une méthode qui facilite l’obtention des valeurs des objets DataColumn à partir d’un DataRow. L’opérateur Field<T> sera étudié en détail un peu plus loin dans ce chapitre, dans la section "Opérateurs dédiés aux champs". Voici les résultats : Avant l’appel à Distinct() Id Name ============================== 1 Joe Rattz 6 Ulyses Hutchens 19 Bob Tanko 45 Erin Doutensal 1 Joe Rattz 12 Bob Mapplethorpe 17 Anthony Adams 32 Dignan Stephens Après l’appel à Distinct() Id Name ============================== 1 Joe Rattz 6 aUlyses Hutchens 19 Bob Tanko 45 Erin Doutensal 12 Bob Mapplethorpe 17 Anthony Adams 32 Dignan Stephens Linq.book Page 339 Mercredi, 18. février 2009 7:58 07 Chapitre 10 LINQ to DataSet 339 Comme vous le voyez, la ligne dont le champ Id vaut 1 apparaît en double avant l’appel à l’opérateur Distinct. Elle n’apparaît plus qu’une seule fois lorsque cet opérateur a été appelé. Dans notre deuxième exemple, nous allons voir ce qui se passerait si l’opérateur Distinct avait été appelé sans spécifier l’objet comparer (voir Listing 10.2). Listing 10.2 : L’opérateur Distinct appelé sans comparateur d’égalité. Student[] students new Student { Id new Student { Id new Student { Id new Student { Id new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = = = = = { 1, Name = "Joe Rattz" }, 6, Name = "Ulyses Hutchens" }, 19, Name = "Bob Tanko" }, 45, Name = "Erin Doutensal" }, 1, Name = "Joe Rattz" }, 12, Name = "Bob Mapplethorpe" }, 17, Name = "Anthony Adams" }, 32, Name = "Dignan Stephens" } DataTable dt = GetDataTable(students); Console.WriteLine("{0}Avant l’appel à Distinct(){0}", System.Environment.NewLine); OutputDataTableHeader(dt, 15); foreach (DataRow dataRow in dt.Rows) { Console.WriteLine("{0,-15}{1,-15}", dataRow.Field<int>(0), dataRow.Field<string>(1)); } IEnumerable<DataRow> distinct = dt.AsEnumerable().Distinct(); Console.WriteLine("{0}Après l’appel à Distinct(){0}", System.Environment.NewLine); OutputDataTableHeader(dt, 15); foreach (DataRow dataRow in distinct) { Console.WriteLine("{0,-15}{1,-15}", dataRow.Field<int>(0), dataRow.Field<string>(1)); } La seule différence entre ce code et le précédent se situe au niveau de l’opérateur Distinct : dans le premier cas, on utilise un comparateur d’égalité, dans le second cas, non. Cette deuxième technique va-t-elle supprimer le doublon ? Jetons un œil aux résultats : Avant l’appel à Distinct() Id Name ============================== 1 Joe Rattz 6 Ulyses Hutchens 19 Bob Tanko 45 Erin Doutensal Linq.book Page 340 Mercredi, 18. février 2009 7:58 07 340 LINQ to DataSet 1 12 17 32 Joe Rattz Bob Mapplethorpe Anthony Adams Dignan Stephens Partie IV Après l’appel à Distinct() Id Name ============================== 1 Joe Rattz 6 Ulyses Hutchens 19 Bob Tanko 45 Erin Doutensal 1 Joe Rattz 12 Bob Mapplethorpe 17 Anthony Adams 32 Dignan Stephens Ces résultats ne sont pas concluants. Comme vous le voyez, la deuxième technique de comparaison est inefficace. Opérateur Except L’opérateur Except renvoie une séquence composée des objets DataRow de la première séquence qui n’appartiennent pas à la seconde. Les éléments de la séquence de sortie apparaissent dans l’ordre original de la séquence d’entrée. Pour déterminer quels éléments sont uniques, l’opérateur Except doit être en mesure de déterminer si deux éléments sont égaux. Pour ce faire, il devrait suffire d’utiliser les méthodes GetHashCode et Equals. Cependant, étant donné que les objets comparés sont des DataRow, un prototype spécifique doit être utilisé. Le deuxième argument de ce prototype désigne le comparateur (System.Data.DataRowComparer.Default) à utiliser. La comparaison est effectuée sur chacune des colonnes d’une ligne. Elle se base sur le type de donnée statique de chaque colonne. L’interface IComparable est utilisée sur les colonnes qui l’implémentent. La méthode statique System.Object.Equals est utilisée sur les autres. Prototype Nous nous intéresserons à un seul prototype de cet opérateur : public static IEnumerable<T> Except<T> ( this IEnumerable<T> first, IEnumerable<T> second, IEqualityComparer<T> comparer); Exemple Dans cet exemple, nous appellerons l’opérateur Except à deux reprises. Dans le premier appel, le comparateur passé sera System.Data.DataRowComparer.Default. Les résultats de la comparaison devraient donc être conformes aux attentes. Dans le Linq.book Page 341 Mercredi, 18. février 2009 7:58 07 Chapitre 10 LINQ to DataSet 341 second appel, aucun comparateur ne sera passé au prototype. Comme vous le verrez, la comparaison ne fonctionnera pas (voir Listing 10.3). Listing 10.3 : Appel de l’opérateur Except avec et sans comparateur. Student[] students new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = { 1, Name = "Joe Rattz" }, 7, Name = "Anthony Adams" }, 13, Name = "Stacy Sinclair" }, 72, Name = "Dignan Stephens" } Student[] students2 = { new Student { Id = 5, Name = "Abe Henry" }, new Student { Id = 7, Name = "Anthony Adams" }, new Student { Id = 29, Name = "Future Man" }, new Student { Id = 72, Name = "Dignan Stephens" } }; DataTable dt1 = GetDataTable(students); IEnumerable<DataRow> seq1 = dt1.AsEnumerable(); DataTable dt2 = GetDataTable(students2); IEnumerable<DataRow> seq2 = dt2.AsEnumerable(); IEnumerable<DataRow> except = seq1.Except(seq2, System.Data.DataRowComparer.Default); Console.WriteLine("{0}Résultats de l’opérateur Except() avec le comparateur{0}", System.Environment.NewLine); OutputDataTableHeader(dt1, 15); foreach (DataRow dataRow in except) { Console.WriteLine("{0,-15}{1,-15}", dataRow.Field<int>(0), dataRow.Field<string>(1)); } except = seq1.Except(seq2); Console.WriteLine("{0}Résultats de l’opérateur Except() sans le comparateur{0}", System.Environment.NewLine); OutputDataTableHeader(dt1, 15); foreach (DataRow dataRow in except) { Console.WriteLine("{0,-15}{1,-15}", dataRow.Field<int>(0), dataRow.Field<string>(1)); } Cet exemple crée deux objets DataTable et les remplit avec les données stockées dans les tableaux Student. La méthode AsEnumerable est alors appelée pour transformer les deux objets DataTable en séquences. Enfin, l’opérateur Except est appelé sur les deux séquences et les résultats sont affichés. Comme vous pouvez le voir, le premier appel à l’opérateur Except transmet le comparateur System.Data.DataRowComparer.Default dans le deuxième argument. Le second appel ne transmet aucun comparateur. Linq.book Page 342 Mercredi, 18. février 2009 7:58 07 342 LINQ to DataSet Partie IV Voici les résultats affichés lors de l’appui sur Ctrl+F5 : Résultats de l’opérateur Except() avec le comparateur Id Name ============================== 1 Joe Rattz 13 Stacy Sinclair Résultats de l’opérateur Except() sans le comparateur Id Name ============================== 1 Joe Rattz 7 Anthony Adams 13 Stacy Sinclair 72 Dignan Stephens Comme vous pouvez le voir, seul le premier appel à l’opérateur Except a été en mesure de comparer de façon correcte les données des deux séquences. Opérateur Intersect L’opérateur Intersect renvoie une séquence d’objets DataRow qui représente l’intersection des deux séquences DataRow passées en entrée. La séquence de sortie contient les éléments uniques des deux séquences d’entrée, listés dans leur ordre d’apparition original. Pour déterminer quels éléments sont uniques, l’opérateur Intersect doit être en mesure de déterminer si deux éléments sont égaux. Pour ce faire, il devrait lui suffire d’utiliser les méthodes GetHashCode et Equals. Cependant, étant donné que les objets comparés sont de type DataRow, un prototype spécifique doit être utilisé. Le deuxième argument de ce prototype désigne le comparateur (System.Data.DataRowComparer.Default) à utiliser. La comparaison est effectuée sur chacune des colonnes d’une ligne. Elle se base sur le type de donnée statique de chaque colonne. L’interface IComparable est utilisée sur les colonnes qui implémentent cette interface. La méthode statique System.Object.Equals est utilisée sur les autres colonnes. Prototype Nous nous intéresserons à un seul prototype de cet opérateur : public static IEnumerable<T> Intersect<T> ( this IEnumerable<T> first, IEnumerable<T> second, IEqualityComparer<T> comparer); Exemple Nous utiliserons le même code que dans l’exemple de l’opérateur Except mais, ici, c’est l’opérateur Intersect qui sera appelé (voir Listing 10.4). Linq.book Page 343 Mercredi, 18. février 2009 7:58 07 Chapitre 10 LINQ to DataSet 343 Listing 10.4 : Appel de l’opérateur Intersect avec et sans comparateur. Student[] students new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = { 1, Name = "Joe Rattz" }, 7, Name = "Anthony Adams" }, 13, Name = "Stacy Sinclair" }, 72, Name = "Dignan Stephens" } Student[] students2 = { new Student { Id = 5, Name = "Abe Henry" }, new Student { Id = 7, Name = "Anthony Adams" }, new Student { Id = 29, Name = "Future Man" }, new Student { Id = 72, Name = "Dignan Stephens" } }; DataTable dt1 = GetDataTable(students); IEnumerable<DataRow> seq1 = dt1.AsEnumerable(); DataTable dt2 = GetDataTable(students2); IEnumerable<DataRow> seq2 = dt2.AsEnumerable(); IEnumerable<DataRow> intersect = seq1.Intersect(seq2, System.Data.DataRowComparer.Default); Console.WriteLine("{0}Résultats de l’opérateur Intersect() avec le comparateur{0}", System.Environment.NewLine); OutputDataTableHeader(dt1, 15); foreach (DataRow dataRow in intersect) { Console.WriteLine("{0,-15}{1,-15}", dataRow.Field<int>(0), dataRow.Field<string>(1)); } intersect = seq1.Intersect(seq2); Console.WriteLine("{0}Résultats de l’opérateur Intersect() sans le comparateur{0}", System.Environment.NewLine); OutputDataTableHeader(dt1, 15); foreach (DataRow dataRow in intersect) { Console.WriteLine("{0,-15}{1,-15}", dataRow.Field<int>(0), dataRow.Field<string>(1)); } Rien de nouveau dans ce code : deux objets DataTable sont créés et initialisés avec les données des tableaux Student. Ils sont ensuite convertis en séquences, puis l’opérateur Intersect leur est appliqué, avec puis sans comparateur. Les résultats sont affichés après chaque appel à l’opérateur Intersect. Linq.book Page 344 Mercredi, 18. février 2009 7:58 07 344 LINQ to DataSet Partie IV Voici les informations affichées suite à l’appui sur Ctrl+F5 : Résultats de l’opérateur Intersect() avec le comparateur Id Name ============================== 7 Anthony Adams 72 Dignan Stephens Résultats de l’opérateur Intersect() sans le comparateur Id Name ============================== Comme vous pouvez le voir, seul le premier appel à l’opérateur Intersect a été en mesure de comparer de façon correcte les données des deux séquences. Opérateur Union L’opérateur Union renvoie une séquence d’objets DataRow qui représente la réunion des deux séquences DataRow passées en entrée. La séquence de sortie contient les éléments de la première séquence suivis des éléments de la seconde séquence qui n’ont pas déjà été cités. Pour déterminer quels éléments ont déjà été sélectionnés dans la première séquence, l’opérateur Union doit être en mesure de déterminer si deux éléments sont égaux. Pour ce faire, il devrait lui suffire d’utiliser les méthodes GetHashCode et Equals. Cependant, étant donné que les objets comparés sont de type DataRow, un prototype spécifique doit être utilisé. Le deuxième argument de ce prototype désigne le comparateur (System.Data.DataRowComparer.Default) à utiliser. La comparaison est effectuée sur chacune des colonnes d’une ligne. Elle se base sur le type de donnée statique de chaque colonne. L’interface IComparable est utilisée sur les colonnes qui implémentent cette interface. La méthode statique System.Object.Equals est utilisée sur les autres colonnes. Prototype Nous nous intéresserons à un seul prototype de cet opérateur : public static IEnumerable<T> Union<T> ( this IEnumerable<T> first, IEnumerable<T> second, IEqualityComparer<T> comparer); Exemple Nous utiliserons le même code que dans l’exemple de l’opérateur Intersect mais, ici, c’est l’opérateur Union qui sera appelé (voir Listing 10.5). Linq.book Page 345 Mercredi, 18. février 2009 7:58 07 Chapitre 10 LINQ to DataSet 345 Listing 10.5 : Appel de l’opérateur Union avec et sans comparateur. Student[] students new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = { 1, Name = "Joe Rattz" }, 7, Name = "Anthony Adams" }, 13, Name = "Stacy Sinclair" }, 72, Name = "Dignan Stephens" } Student[] students2 = { new Student { Id = 5, Name = "Abe Henry" }, new Student { Id = 7, Name = "Anthony Adams" }, new Student { Id = 29, Name = "Future Man" }, new Student { Id = 72, Name = "Dignan Stephens" } }; DataTable dt1 = GetDataTable(students); IEnumerable<DataRow> seq1 = dt1.AsEnumerable(); DataTable dt2 = GetDataTable(students2); IEnumerable<DataRow> seq2 = dt2.AsEnumerable(); IEnumerable<DataRow> union = seq1.Union(seq2, System.Data.DataRowComparer.Default); Console.WriteLine("{0}Résultats de l’opérateur Union() avec le comparateur{0}", System.Environment.NewLine); OutputDataTableHeader(dt1, 15); foreach (DataRow dataRow in union) { Console.WriteLine("{0,-15}{1,-15}", dataRow.Field<int>(0), dataRow.Field<string>(1)); } union = seq1.Union(seq2); Console.WriteLine("{0}Résultats de l’opérateur Union() sans le comparateur{0}", System.Environment.NewLine); OutputDataTableHeader(dt1, 15); foreach (DataRow dataRow in union) { Console.WriteLine("{0,-15}{1,-15}", dataRow.Field<int>(0), dataRow.Field<string>(1)); } Ici encore, rien de nouveau dans ce code : deux objets DataTable sont créés et initialisés avec les données des tableaux Student. Ils sont ensuite convertis en séquences, puis l’opérateur Union leur est appliqué, avec puis sans comparateur. Les résultats sont affichés après chaque appel à l’opérateur Union. Linq.book Page 346 Mercredi, 18. février 2009 7:58 07 346 LINQ to DataSet Partie IV Voici les informations affichées suite à l’appui sur Ctrl+F5 : Résultats de l’opérateur Union() avec le comparateur Id Name ============================== 1 Joe Rattz 7 Anthony Adams 13 Stacy Sinclair 72 Dignan Stephens 5 Abe Henry 29 Future Man Résultats de l’opérateur Union() sans le comparateur Id Name ============================== 1 Joe Rattz 7 Anthony Adams 13 Stacy Sinclair 72 Dignan Stephens 5 Abe Henry 7 Anthony Adams 29 Future Man 72 Dignan Stephens Comme vous pouvez le voir, seul le premier appel à l’opérateur Union a donné les résultats escomptés. Opérateur SequencialEqual L’opérateur SequencialEqual compare deux séquences d’objets DataRow et détermine leur égalité. Pour ce faire, les deux séquences sources sont énumérées et leurs objets DataRow, comparés. Si les deux séquences sources ont le même nombre de lignes, et si tous les objets DataRow sont égaux, l’opérateur retourne la valeur true. Dans le cas contraire, il retourne la valeur false. Cet opérateur doit être en mesure de déterminer si deux éléments sont égaux. Pour ce faire, il devrait lui suffire d’utiliser les méthodes GetHashCode et Equals. Cependant, étant donné que les objets comparés sont de type DataRow, un prototype spécifique doit être utilisé. Le deuxième argument de ce prototype désigne le comparateur (System.Data.DataRowComparer.Default) à utiliser. La comparaison est effectuée sur chacune des colonnes d’une ligne. Elle se base sur le type de donnée statique de chaque colonne. L’interface IComparable est utilisée sur les colonnes qui l’implémentent. La méthode statique System.Object.Equals est utilisée sur les autres colonnes. Prototype Nous nous intéresserons à un seul prototype de cet opérateur : public static bool SequenceEqual<T> ( this IEnumerable<T> first, IEnumerable<T> second, IEqualityComparer<T> comparer); Linq.book Page 347 Mercredi, 18. février 2009 7:58 07 Chapitre 10 LINQ to DataSet 347 Exemple Dans cet exemple, nous allons construire deux séquences identiques d’objets DataRow et les comparer avec l’opérateur SequencialEqual. Deux comparaisons seront effectuées. La première utilisera un comparateur et la seconde, non (voir Listing 10.6). Listing 10.6 : Appel de l’opérateur SequenceEqual avec et sans comparateur. Student[] students new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = { 1, Name = "Joe Rattz" }, 7, Name = "Anthony Adams" }, 13, Name = "Stacy Sinclair" }, 72, Name = "Dignan Stephens" } DataTable dt1 = GetDataTable(students); IEnumerable<DataRow> seq1 = dt1.AsEnumerable(); DataTable dt2 = GetDataTable(students); IEnumerable<DataRow> seq2 = dt2.AsEnumerable(); bool equal = seq1.SequenceEqual(seq2, System.Data.DataRowComparer.Default); Console.WriteLine("Appel de SequenceEqual() avec comparateur : {0}", equal); equal = seq1.SequenceEqual(seq2); Console.WriteLine("Appel de SequenceEqual() sans le comparateur : {0}", equal); Comme on pouvait s’y attendre, le premier appel à l’opérateur SequenceEqual indique que les deux séquences sont égales, alors que le second indique qu’elles sont différentes : Appel de SequenceEqual() avec comparateur : True Appel de SequenceEqual() sans comparateur : False Opérateurs dédiés aux champs Ces opérateurs viennent compléter ceux passés en revue dans la section précédente. Ils sont définis dans l’assembly System.Data.DataSetExtensions.dll, dans la classe statique System.Data.DataRowExtensions. Vous avez certainement remarqué que, dans la plupart des exemples précédents, nous avons utilisé l’opérateur Field<T> pour extraire d’un DataRow la valeur d’un objet DataColumn. Cet opérateur a deux intérêts : grâce à lui, la comparaison de données est possible, et il gère la valeur null. La manipulation des objets DataRow présente un problème : les DataColumn, de type "valeur" (à opposer au type "référence"), ne peuvent pas être comparés correctement. En effet, ils peuvent contenir une donnée de type quelconque : un entier, une chaîne ou un autre type de donnée. Si, par exemple, un DataColumn contient une valeur de type int, il doit être converti en une référence de type Object. Cette conversion est connue sous le nom "boxing" dans l’environnement de développement .NET. L’opération inverse (la transformation d’un type référence en un type valeur) est appelée "unboxing". Le problème se situe au niveau du boxing.