2015-04-13 1 views
1

J'ai une procédure stockée que j'ai besoin d'exécuter dans le cadre d'une étendue de transaction plus large en utilisant Entity Framework 4.0. Voici une démonstration rapide du scénario que j'ai:Comment puis-je retourner les mises à jour effectuées dans une procédure stockée dans un TransactionScope à l'aide d'Entity Framework?

private void button1_Click(object sender, EventArgs e) 
{ 
    using (var scope = GetTransaction(IsolationLevel.Serializable)) 
    { 
     var model = new SPUpdateTestEntities(); 

     var rows = model.TestTables.ToList(); 

     Debug.WriteLine("rows:"); 
     foreach (var row in rows) 
     { 
      Debug.WriteLine(row); 
     } 

     var random = new Random(); 

     var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 
     var result = new string(
      Enumerable.Repeat(chars, 8) 
         .Select(s => s[random.Next(s.Length)]) 
         .ToArray()); 

     model.UpdateTestTable(rows[random.Next(rows.Count())].Id, result, random.Next()); 

     model.SaveChanges(); 

     var rowsAgain = model.TestTables.ToList(); 

     Debug.WriteLine("rowsAgain:"); 
     foreach (var row in rowsAgain) 
     { 
      Debug.WriteLine(row); 
     } 

     scope.Complete(); 
    } 
} 

private TransactionScope GetTransaction(IsolationLevel isolationLevel) 
{ 
    var transactionOptions = new TransactionOptions(); 
    transactionOptions.IsolationLevel = isolationLevel; 
    transactionOptions.Timeout = TransactionManager.MaximumTimeout; 

    return new TransactionScope(TransactionScopeOption.Required, transactionOptions); 
} 

Le but de la procédure stockée UpdateTestTable est à la fois insérer et mettre à jour les enregistrements du TestTable.

ALTER PROCEDURE [dbo].[UpdateTestTable] 
    @Id AS INTEGER, 
    @Text as VARCHAR(255), 
    @Number AS INTEGER 
AS 
BEGIN 
    INSERT INTO TestTable(Text, Number) 
    VALUES (CONVERT(varchar(255), NEWID()), ABS(CHECKSUM(NewId())) % 14) 

    UPDATE TestTable 
    SET Text = @Text, Number = @Number 
    WHERE Id = @Id 
END 

Le comportement que je me attends est de allRowsAgain pour contenir toutes les modifications qui ont été apportées à UpdateTestTable. Le comportement actuel que j'obtiens est que les enregistrements insérés sont renvoyés mais les mises à jour des enregistrements existants ne le sont pas.

J'ai essayé les niveaux d'isolement Serializable (comme indiqué), ReadComitted, et ReadUncomitted mais cela ne fait pas de différence sur le résultat comme je l'espérais.

Edit: J'ai ajouté quelques instructions de débogage pour rendre les résultats un peu plus clair, voici la sortie:

Première course:

rows: 
Id:2 - Number:1 - Text:Hello 

rowsAgain: 
Id:2 - Number:1 - Text:Hello 
Id:19 - Number:1 - Text:0B22C83C-58E9-403C-96B4-FB3940E1F250 

Deuxième course:

rows: 
Id:2 - Number:2135368409 - Text:CQCXCTAY 
Id:19 - Number:1 - Text:0B22C83C-58E9-403C-96B4-FB3940E1F250 

rowsAgain: 
Id:2 - Number:2135368409 - Text:CQCXCTAY 
Id:19 - Number:1 - Text:0B22C83C-58E9-403C-96B4-FB3940E1F250 
Id:20 - Number:8 - Text:D5A6684B-D140-415F-A81B-36705915FAF6 

Merci, Ant

+0

peut-être parce que l'étendue de la transaction est en attente des modifications de la base de données jusqu'à la fin de la transaction? – Claies

+0

Je suppose que vous avez probablement raison, mais j'espérais être en mesure d'utiliser les valeurs avant de commettre la transaction. Aussi je suis curieux de savoir pourquoi la ligne insérée est disponible mais pas la mise à jour. Peut-être la mise en cache EF? –

+0

semble étrange, pour certain. – Claies

Répondre

0

Entité F ramework ouvre une nouvelle connexion pour chaque requête sauf si vous ouvrez explicitement la connexion vous-même. Par conséquent, plusieurs connexions sont enrôlées dans une transaction distribuée ici. Chaque connexion voit une vue différente des données. C'est un anti-pattern.

Ce comportement n'est pas utile et j'ai demandé à l'équipe de le modifier. EF devrait s'intégrer avec TS, mais pour le moment l'intégration est faible. Ouvrez la connexion manuellement avant d'émettre la première commande. EF vérifie que pour chaque ligne, il y a au plus un objet .NET. Lorsque vous interrogez une ligne, vous obtenez toujours l'objet existant. Ici, il a d'anciennes valeurs. Leçon: ne mélangez pas DML EF et SQL. Vous devez être très prudent en faisant cela. Utilisez un nouveau contexte EF avec l'ancienne connexion ou utilisez MergeOption.

+0

Merci pour votre contribution @usr. Pourriez-vous expliquer pourquoi l'insertion serait retournée mais pas les résultats de la mise à jour? Je vais regarder manuellement le contrôle de la connexion. –

+0

Je doute vraiment que ce soit le cas. Soyez là une transaction ou pas. La seule exception serait si la deuxième déclaration échoue et que vous ne remarquez pas l'erreur. Assurez-vous vraiment que vous n'interprétez pas quelque chose ici. Par exemple, dites 'SET Number = 0/0' pour tester que la deuxième instruction fonctionne et affecte au moins une ligne. – usr

+0

J'ai modifié mon code pour montrer le code exact qui est en cours d'exécution. Aucune exception n'est émise pour indiquer une défaillance de quelque sorte que ce soit. J'ai ajouté des instructions de débogage qui montrent le contenu de chaque collection après la première exécution, puis une deuxième exécution.(J'ai une requête pour réinitialiser la table à son état initial avant chaque test.) –