web-dev-qa-db-fra.com

Créez une table de données combinée à partir de deux tables de données jointes à LINQ. C #

J'ai le code suivant qui remplit dataTable1 et dataTable2 avec deux requêtes SQL simples, dataTableSqlJoined est rempli à partir des mêmes tables mais jointes.

J'essaie d'écrire une requête LINQ pouvant créer la dataTableLinqJoined comme si elle avait été créée à l'aide de SQL. Dans mon exemple ci-dessous, il ne renvoie que les valeurs de dataTable1.

Le problème que j'ai est ce qu'il faut mettre dans la SELECT de la requête linq. Comment créer un nouveau DataRow contenant toutes les colonnes des deux DataRows. Je ne saurai pas les noms de colonne/schémas exacts des requêtes jusqu'au moment de l'exécution.

sqlCommand = new SqlCommand("SELECT ID, A, B FROM Table1", sqlConnection, sqlTransaction);
sqlAdapter = new SqlDataAdapter(sqlCommand);
DataTable dataTable1 = new DataTable();
sqlAdapter.Fill(dataTable1);

sqlCommand = new SqlCommand("SELECT ID, C, D FROM Table2", sqlConnection, sqlTransaction);
sqlAdapter = new SqlDataAdapter(sqlCommand);
DataTable dataTable2 = new DataTable();
sqlAdapter.Fill(dataTable2);

sqlCommand = new SqlCommand("SELECT Table1.ID, A, B, Table2.ID, C, D FROM Table1 INNER JOIN Table2 ON Table1.ID = Table2.ID", sqlConnection, sqlTransaction);
sqlAdapter = new SqlDataAdapter(sqlCommand);
DataTable dataTableSqlJoined = new DataTable();
sqlAdapter.Fill(dataTableSqlJoined);

var dataRows =
    from
        dataRows1 in dataTable1.AsEnumerable()
    join
        dataRows2 in dataTable2.AsEnumerable()
    on
        dataRows1.Field<int>("ID") equals dataRows2.Field<int>("ID")
    select
        dataRows1; // + dataRows2;

DataTable dataTableLinqJoined = dataRows.CopyToDataTable();

Pour un peu plus d’arrière-plan, la requête combinée consomme beaucoup de base de données et pose des problèmes de performances. Les données renvoyées par la première requête sont relativement statiques et peuvent être fortement mises en cache. Les données renvoyées par la deuxième requête changent constamment mais sont rapides à exécuter et n'ont donc pas besoin d'être mises en cache. De plus, beaucoup de code dépend du passage du DataTable combiné et, par conséquent, il n’ya pas beaucoup d’options possibles pour transmettre les données dans un format différent.

22
Robin Day

Avez-vous déjà regardé cette page?

COMMENT FAIRE: Implémenter une classe d'assistance DataSet JOIN dans Visual C # .NET

Si cette approche ne vous suffit pas, vous pouvez décomposer les données de ligne en tableaux d'objets:

DataTable targetTable = dataTable1.Clone();
var dt2Columns = dataTable2.Columns.OfType<DataColumn>().Select(dc => 
    new DataColumn(dc.ColumnName, dc.DataType, dc.Expression, dc.ColumnMapping));
targetTable.Columns.AddRange(dt2Columns.ToArray());
var rowData =
    from row1 in dataTable1.AsEnumerable()
    join row2 in dataTable2.AsEnumerable()
        on row1.Field<int>("ID") equals row2.Field<int>("ID")
    select row1.ItemArray.Concat(row2.ItemArray).ToArray();
foreach (object[] values in rowData)
    targetTable.Rows.Add(values);

Je pense que c'est à peu près aussi sommaire que vous pourrez le faire et je vais expliquer pourquoi: c'est le schéma.

Un DataRow n'est pas un objet indépendant; cela dépend de sa propre DataTable et ne peut pas vivre sans elle. Il n'y a aucun moyen pris en charge de créer une "variable déconnectée" DataRow; la méthode d'extension CopyToDataTable() fonctionne sur les lignes qui existent déjà dans une DataTable et copie simplement le schéma de la source (rappelez-vous, chaque DataRow a une référence à son parent Table) avant de copier les lignes elles-mêmes (probablement avec ImportRow effectivement ouvert réflecteur pour vérifier).

Dans ce cas, vous devez créer un nouveau schéma. Avant de pouvoir créer de nouvelles lignes, vous devez créer la table dans laquelle elles doivent contenir first, ce qui implique d'écrire au moins les 3 lignes de code figurant en haut de la méthode ci-dessus.

Ensuite, vous pouvez enfin créer les lignes - mais une seule à la fois, car DataTable et sa DataRowCollection associée n’exposent aucune méthode pour ajouter plusieurs lignes à la fois. Vous pouvez bien sûr ajouter votre propre méthode d’extension pour la variable DataRowCollection afin de rendre ce "rendu" plus agréable:

public static void AddRange(this DataRowCollection rc,
    IEnumerable<object[]> tuples)
{
    foreach (object[] data in tuples)
        rc.Add(tuples);
}

Ensuite, vous pourriez vous débarrasser de la variable foreach dans la première méthode et la remplacer par:

targetTable.Rows.AddRange(rowData);

Bien que ce ne soit vraiment que déplacer la verbosité, pas l'éliminer.

En bout de ligne, tant que vous travaillez avec la hiérarchie de classe DataSet, il y aura toujours un peu de cruel. Les extensions Linq to DataSet sont Nice, mais ce ne sont que des extensions et ne peuvent pas modifier les limitations ci-dessus.

20
Aaronaught

Aaronaught c'était génial. Mais voudriez ajouter quelques améliorations à votre code LINQy. Lors de l'ajout de colonnes de dataTable2 à la table cible, il est probable que peu de colonnes existent déjà dans la table cible (à laquelle nous rejoignons). Alors on y va.

DataTable targetTable = dataTable1.Clone();
var dt2Columns = dataTable2.Columns.OfType<DataColumn>().Select(dc => 
new DataColumn(dc.ColumnName, dc.DataType, dc.Expression, dc.ColumnMapping));
var dt2FinalColumns=from dc in dt2Columns.AsEnumerable()
                    where targetTable.Columns.Contains(dc.ColumnName) == false
                    select dc;
targetTable.Columns.AddRange(dt2FinalColumns.ToArray());
var rowData =from row1 in dataTable1.AsEnumerable()
             join row2 in dataTable2.AsEnumerable()
             on row1.Field<int>("ID") equals row2.Field<int>("ID")
             select row1.ItemArray.Concat(row2.ItemArray.Where(r2=> row1.ItemArray.Contains(r2)==false)).ToArray();
foreach (object[] values in rowData)
targetTable.Rows.Add(values);

J'espère que cela sera utile pour les gars comme moi.

5
suryakiran

Pardonnez-moi si je ressemble à un idiot.

Je pense que vous devriez avoir la table finale prête (avec tous les champs des tables A et B).
Et, au lieu d’utiliser LINQ, faites une jointure puis faites une ForEach sur le résultat et insérez la valeur dans datatable final.

Pseudocode

dt1.Join (dt2) .Where (...). ForEach (row => code pour lire le contenu de l'objet anonyme et l'ajouter à finalTable.Rows)

1
shahkalpesh
select new {
    ID = dataRows1.ID,  // no need to select dataRows2.ID, because of JOIN.
    A = dataRows1.A,
    B = dataRows1.B,
    C = dataRows2.C,
    D = dataRows2.D 
};
0
Roger Lipscombe