2009-06-22 11 views
1

Editer: tout commentaire si vous pensez que c'est un bug .NET ou non serait apprécié.Est-ce un bogue dans l'API DataTable? Les changements sont stockés/exécutés dans la "mauvaise séquence"

J'ai un bug que j'ai réussi à simplifier au scénario suivant:

J'ai un DataTable où les clés primaires doivent être conservés consécutifs, par exemple Si vous insérez une ligne entre les autres lignes, vous devez d'abord incrémenter l'ID des lignes suivantes pour libérer de l'espace, puis insérer la ligne.

Et si vous supprimez une ligne, vous devez décrémenter l'ID de toutes les lignes suivantes pour remplir l'espace laissé par la ligne dans la table.

cas de test qui fonctionne correctement

Commencez avec 3 lignes de la table, avec les ID 1, 2 et 3.

Ensuite, supprimez ID = 2 et définissez ID = 2 où ID = 3 (pour combler l'écart); Cela fonctionne correctement. DataTable.GetChanges() contient la ligne supprimée, puis la ligne modifiée; Lorsque vous exécutez dataAdapter.Update (table), il s'exécute correctement.

cas de test qui ne fonctionne pas

Cependant, si vous commencez avec 2 lignes (ID 1 et 2), puis définissez ID = 3 où ID = 2, et insérer ID = 2, puis commit (ou accepter) changements. Cela devrait être maintenant le même état que le premier test.

Ensuite, vous faites les mêmes étapes qu'auparavant, c'est-à-dire supprimer ID = 2 et définir ID = 2 où ID = 3, mais maintenant dataTable.GetChanges() sont dans le mauvais ordre. La première ligne est une ligne modifiée et la deuxième ligne est la ligne supprimée. Ensuite, si vous essayez dataAdapter.Update (table), il donnera une violation de clé primaire - il a essayé de modifier une ligne à une ligne déjà existante avant de la supprimer.

Solution

Je peux penser à une solution au problème, à savoir la force pour que les lignes supprimées sont engagées d'abord, puis les lignes modifiées, puis ajouté des lignes. Mais pourquoi cela se passe-t-il? Y a-t-il une autre solution?

Je pense avoir déjà rencontré un "problème" similaire avec les dictionnaires: si vous ajoutez des éléments, supprimez-les, puis réinsérez-les, ils ne seront pas dans la même séquence que vous les avez ajoutés (lorsque vous énumérez le dictionnaire).

Voici deux tests NUnit qui montrent le problème:

[Test] 
public void GetChanges_Working() 
{ 
    // Setup ID table with three rows, ID=1, ID=2, ID=3 
    DataTable idTable = new DataTable(); 
    idTable.Columns.Add("ID", typeof(int)); 

    idTable.PrimaryKey = new DataColumn[] { idTable.Columns["ID"] }; 

    idTable.Rows.Add(1); 
    idTable.Rows.Add(2); 
    idTable.Rows.Add(3); 

    idTable.AcceptChanges(); 

    // Delete ID=2, and move old ID=3 to ID=2 
    idTable.Select("ID = 2")[0].Delete(); 
    idTable.Select("ID = 3")[0]["ID"] = 2; 

    // Debug GetChanges 
    foreach (DataRow row in idTable.GetChanges().Rows) 
    { 
     if (row.RowState == DataRowState.Deleted) 
      Console.WriteLine("Deleted: {0}", row["ID", DataRowVersion.Original]); 
     else 
      Console.WriteLine("Modified: {0} = {1}", row["ID", DataRowVersion.Original], row["ID", DataRowVersion.Current]); 
    } 

    // Check GetChanges 
    Assert.AreEqual(DataRowState.Deleted, idTable.GetChanges().Rows[0].RowState, "1st row in GetChanges should be deleted row"); 
    Assert.AreEqual(DataRowState.Modified, idTable.GetChanges().Rows[1].RowState, "2nd row in GetChanges should be modified row"); 
} 

Sortie:

Deleted: 2 
Modified: 3 = 2 

1 passed, 0 failed, 0 skipped, took 4.27 seconds (NUnit 2.4). 

Prochain test:

[Test] 
public void GetChanges_NotWorking() 
{ 
    // Setup ID table with two rows, ID=1, ID=2 
    DataTable idTable = new DataTable(); 
    idTable.Columns.Add("ID", typeof(int)); 

    idTable.PrimaryKey = new DataColumn[] { idTable.Columns["ID"] }; 

    idTable.Rows.Add(1); 
    idTable.Rows.Add(2); 

    idTable.AcceptChanges(); 

    // Move old ID=2 to ID=3, and add ID=2 
    idTable.Select("ID = 2")[0]["ID"] = 3; 
    idTable.Rows.Add(2); 

    idTable.AcceptChanges(); 

    // Delete ID=2, and move old ID=3 to ID=2 
    idTable.Select("ID = 2")[0].Delete(); 
    idTable.Select("ID = 3")[0]["ID"] = 2; 

    // Debug GetChanges 
    foreach (DataRow row in idTable.GetChanges().Rows) 
    { 
     if (row.RowState == DataRowState.Deleted) 
      Console.WriteLine("Deleted: {0}", row["ID", DataRowVersion.Original]); 
     else 
      Console.WriteLine("Modified: {0} = {1}", row["ID", DataRowVersion.Original], row["ID", DataRowVersion.Current]); 
    } 

    // Check GetChanges 
    Assert.AreEqual(DataRowState.Deleted, idTable.GetChanges().Rows[0].RowState, "1st row in GetChanges should be deleted row"); 
    Assert.AreEqual(DataRowState.Modified, idTable.GetChanges().Rows[1].RowState, "2nd row in GetChanges should be modified row"); 
} 

Sortie:

Modified: 3 = 2 
Deleted: 2 
TestCase 'GetChanges_NotWorking' 
failed: 
    1st row in GetChanges should be deleted row 
    Expected: Deleted 
    But was: Modified 

Répondre

2

Ce n'est pas un bogue, le fait est que vous utilisez des ID d'une manière (très) non standard. Deux réponses:

1) Utiliser DataTable.GetChanges (DataRowState.Modifié) pour traiter vos mises à jour dans l'ordre (je pense qu'il serait supprimé, modifié, inséré). C'est aussi ce que vous devez faire avec les relations Maître/Détail (avant .net 3.0)

2) Repenser votre design, en général les ID doivent être immuables et tenir compte des écarts, etc. plus fiable et beaucoup plus facile. Vous pouvez utiliser une autre colonne pour conserver une numérotation séquentielle à présenter à l'utilisateur.

+0

+1 pour «en général, les ID doivent être immuables» et «Vous pouvez utiliser une autre colonne pour conserver une numérotation séquentielle à présenter à l'utilisateur». – TcKs

+0

Je vois votre point de vue que les clés primaires ne devraient pas être utilisées de cette façon; mais il semble toujours que le DataTable me semble étrange, c'est-à-dire qu'il devrait fonctionner si je l'utilise de cette façon. Relié, pourquoi le comportement par défaut de DataAdapter.Update n'est pas traité dans l'ordre supprimé, modifié puis inséré. (BTW c'est l'ordre que j'avais l'intention de taper dans ma solution de contournement - je l'ai corrigé dans le texte original maintenant). – RickL

+0

@Rick: L'adaptateur a une fonctionnalité 'limitée' (c'est-à-dire qu'il ne peut pas gérer les relations), donc si vous voulez quelque chose de non standard, vous devrez le faire vous-même. –