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.