2010-02-13 7 views
0

Je dois laisser dans un DataTable uniquement les enregistrements dont la date ne figure pas dans la base de données.Supprimer les doublons par champ d'une table en utilisant une autre en utilisant LINQ

Donc je lis toutes les dates existantes en utilisant la procédure stockée (est-il exact?):

SELECT DISTINCT CAST(S.[date] AS DATE) -- original date is DATETIME2(0) 
FROM ... 
WHERE ... 

et le charger à un DataTable:

var tableDate = new DataTable(); 
new SqlDataAdapter(command).Fill(tableDate); 

Comment supprimer maintenant d'une autre table tout lignes inutiles? Je pense que LINQ pourrait aider, mais je ne sais pas comment ..

+0

Je ne suis pas sûr de comprendre les exigences. Voulez-vous dire que vous devez trouver toutes les valeurs de date qui n'existent pas dans une table donnée dans la base de données ou voulez-vous dire que vous devez supprimer des lignes du tableau A dans la base de données où la valeur n'existe pas dans le tableau B? – Thomas

+0

@ Thomas: Laissez-moi décrire la situation. J'ai besoin d'ajouter de nouvelles données à la base de données en utilisant SqlBulCopy. Mais d'abord je dois le nettoyer - je n'ai pas ajouté de données déjà existantes dans la base de données. Critères - date. Par exemple, j'ai d'abord ajouté des données pour Jan, 1,2,3. Et après cela - pour 2,3,4. Je dois retirer Jan, 2 du second set. – abatishchev

Répondre

2

Je regarde votre réponse, vous dites œuvres, et que vous voulez juste savoir comment le faire dans une « seule requête LINQ. » Gardez à l'esprit que ces requêtes ont tous reporté l'exécution, de sorte que les deux requêtes suivantes sont fonctionnellement équivalentes:

var q = 
    from d in dates 
    select d.Field<DateTime>("date"); 
return 
    (from r in records 
    where !q.Contains(r.Field<DateTime>("date")) 
    select r).CopyToDataTable(); 

Et:

return 
    (from r in records 
    where !dates 
     .Select(d => d.Field<DateTime>("date")) 
     .Contains(r.Field<DateTime>("date")) 
    select r).CopyToDataTable(); 

La deuxième version est beaucoup plus difficile à lire, mais néanmoins, il est "une requête".


Cela dit, aucun de ces exemples semblent vraiment correspondre à votre titre de la question, ce qui laisse supposer que vous essayez de supprimer des lignes en double . Si tel est bien ce que vous essayez de faire, voici une méthode qui le fera:

static DataTable RemoveDuplicates(DataTable dt) 
{ 
    return 
     (from row in dt.Rows.OfType<DataRow>() 
     group row by row.Field<string>("date") into g 
     select g 
      .OrderBy(r => r.Field<int>("ID")) 
      .First()).CopyToDataTable(); 
} 

Si vous ne se soucient pas de qui doublons supprimés alors vous pouvez simplement supprimer la ligne OrderBy. Vous pouvez tester cela comme suit:

static void Main(string[] args) 
{ 
    using (DataTable original = CreateSampleTable()) 
    using (DataTable filtered = RemoveDuplicates(original)) 
    { 
     DumpTable(filtered); 
    } 
    Console.ReadKey(); 
} 

static DataTable CreateSampleTable() 
{ 
    DataTable dt = new DataTable(); 
    dt.Columns.Add("ID", typeof(int)); 
    dt.Columns.Add("Code", typeof(string)); 
    dt.Columns.Add("Name", typeof(string)); 
    dt.Rows.Add(1, "123", "Alice"); 
    dt.Rows.Add(2, "456", "Bob"); 
    dt.Rows.Add(3, "456", "Chris"); 
    dt.Rows.Add(4, "789", "Dave"); 
    dt.Rows.Add(5, "123", "Elen"); 
    dt.Rows.Add(6, "123", "Frank"); 
    return dt; 
} 

static void DumpTable(DataTable dt) 
{ 
    foreach (DataRow row in dt.Rows) 
    { 
     Console.WriteLine("{0},{1},{2}", 
      row.Field<int>("ID"), 
      row.Field<string>("Code"), 
      row.Field<string>("Name")); 
    } 
} 

(il suffit de remplacer « date » avec « Code » dans la méthode RemoveDuplicates pour cet exemple)

Espérons que l'un de ces réponses à votre question. Sinon, je pense que vous allez devoir être plus clair avec vos exigences.

1

Vous pouvez utiliser Except()

return records.Except(dates);

MISE À JOUR: Si votre DataTable a des champs typés, alors il devrait être comme suit :

var excluded = arbDates.Rows.OfType<System.Data.DataRow>().Select(a => a[0]) .Except(excDates.Rows.OfType<System.Data.DataRow>().Select(e => e[0]));

sinon vous pourriez jeter:

var excluded = arbDates.Rows.OfType<System.Data.DataRow>() .Select(a => Convert.ToDateTime(a[0].ToString())) .Except( excDates.Rows.OfType<System.Data.DataRow>() .Select(e => Convert.ToDateTime(e[0].ToString())));

+0

Salut. Pourriez-vous s'il vous plaît m'aider à écrire une seule requête LINQ avec q1.Except (q2)? Comment combiner les sélections en une? – abatishchev

+0

Malheureusement, cela n'a pas fonctionné. table.AsEnumerable(). Except (Database.CreateDataTable (commande) .AsEnumerable()). ToArray() renvoie chaque fois le même nombre d'enregistrements que la table à l'origine. Je vais essayer d'utiliser le comparateur personnalisé et le rapport. – abatishchev

+0

Mon comparateur personnalisé existant n'a pas aidé. Je l'ai posté dans ma réponse ci-dessous. Des idées? – abatishchev

1

Votre instruction SQL est correcte. Si je comprends bien, vous lancez le casting pour obtenir la valeur de temps par défaut à partir de minuit. Par conséquent, les dates de l'autre table comparée doivent également correspondre à ce format afin de comparer les dates avec des heures neutres.Si ce n'est pas le cas, vous pouvez toujours utiliser le code ci-dessous mais vous devez ajouter la propriété .Date partout où le champ tableResult est référencé. J'ai également utilisé Field<DateTime>(0) mais en fonction de votre requête et en fonction de votre exemple précédent, vous devrez peut-être utiliser Field<DateTime>("date").

Pas besoin d'un comparateur personnalisé. Pour fusionner vos requêtes LINQ en une seule requête, vous pouvez simplement utiliser le mot clé let et transmettre le résultat intermédiaire via la requête et le référencer.

pour cette solution:

var tableDate = new DataTable(); 
new SqlDataAdapter(command).Fill(tableDate); 

// this is the other table that has other dates, so populate as needed 
var tableResult = new DataTable(); 

var newTable = 
    (from row in tableResult.AsEnumerable() 
    let uniqueRows = tableResult.AsEnumerable().Select(r => r.Field<DateTime>(0)) 
           .Except(tableDate.AsEnumerable().Select(r => r.Field<DateTime>(0))) 
    where uniqueRows.Contains(row.Field<DateTime>(0)) 
    select row).CopyToDataTable(); 

Dans la notation dot la requête serait:

var newTable = tableResult.AsEnumerable() 
    .Select(row => new 
    { 
     Row = row, 
     UniqueRows = tableResult.AsEnumerable() 
           .Select(r => r.Field<DateTime>(0)) 
           .Except(tableDate.AsEnumerable().Select(r => r.Field<DateTime>(0))) 
    }) 
    .Where(item => item.UniqueRows.Contains(item.Row.Field<DateTime>(0))) 
    .Select(item => item.Row) 
    .CopyToDataTable(); 

Au lieu de tableResult.AsEnumerable() vous pouvez utiliser tableResult.Rows.Cast<DataRow>() ou tableResult.Rows.OfType<DataRow>(). Les résultats sont les mêmes entre toutes ces approches.

Si vous voulez supprimer les doublons de la table existante (plutôt que de le copier sur une nouvelle table), vous pouvez supprimer les articles retournés par le Intersect method de la table:

var commonDates = tableDate.AsEnumerable().Select(row => row.Field<DateTime>(0)) 
          .Intersect(tableResult.AsEnumerable().Select(row => row.Field<DateTime>(0))); 

for (int index = tableResult.Rows.Count - 1; index >= 0; index--) 
{ 
    if (commonDates.Contains(tableResult.Rows[index].Field<DateTime>(0))) 
    { 
     tableResult.Rows.RemoveAt(index); 
    } 
} 
1

Si je comprends bien le problème , vous essayez de dé-doubler les données provenant de certaines importations. Vous n'avez peut-être pas besoin de faire cela en utilisant LINQ. Bien que le titre du post suggère LINQ, vous vous demandez plus tard si LINQ pourrait être la meilleure solution et, compte tenu de ce que nous savons, je pense que vous pourriez le faire en utilisant une seule instruction Insert.

D'abord, je vous suggère de gros copier les données dans un emplacement temporaire dans la db (si vous faites pas déjà) comme ceci:

Create Table TempBulkCopyData 
(
    Id int not null identity(1,1) 
    , Date DateTime2 not null 
    , ... 
) 

L'un des avantages de la copie en vrac dans un temporaire l'emplacement est que vous pouvez ajouter des index et autres pour accélérer le processus de nettoyage. Pour Déduplication les données, vous pouvez alors exécuter une requête comme ceci:

Insert DestinationData(...) 
Select ... 
From BulkCopyData As BCD 
Where Id = (
      Select Min(BCD2.[Id]) 
      From BulkCopyData As BCD2 
      Where Cast(BCD2.[Date] As Date) = Cast(BCD.[Date] As Date) 
      ) 

Ou

Insert DestinationData(...) 
Select ... 
From BulkCopyData As BCD 
Where Id = (
      Select Min(BCD2.[Id]) 
      From BulkCopyData As BCD2 
      Where DateDiff(d, BCD.[Date], BCD2.[Date]) = 0 
      ) 

Cela va tirer la première date qu'il trouve (celui avec l'identifiant le plus bas). C'est évidemment quelque peu arbitraire, mais pour être plus précis, nous devons en savoir plus sur la structure et les exigences des données.

Questions connexes