Que puis-je faire pour résoudre une Exception "Row not found or changed" dans LINQ en SQL sur une base de données SQL Server Compact Edition?

lors de L'exécution de sous-Bitchanges au DataContext après mise à jour d'un couple de propriétés avec une connexion LINQ à SQL (contre SQL Server Compact Edition) j'obtiens une "ligne non trouvée ou changée."ChangeConflictException.

var ctx = new Data.MobileServerDataDataContext(Common.DatabasePath);
var deviceSessionRecord = ctx.Sessions.First(sess => sess.SessionRecId == args.DeviceSessionId);

deviceSessionRecord.IsActive = false;
deviceSessionRecord.Disconnected = DateTime.Now;

ctx.SubmitChanges();

la requête génère le SQL suivant:

UPDATE [Sessions]
SET [Is_Active] = @p0, [Disconnected] = @p1
WHERE 0 = 1
-- @p0: Input Boolean (Size = 0; Prec = 0; Scale = 0) [False]
-- @p1: Input DateTime (Size = 0; Prec = 0; Scale = 0) [9/4/2008 5:12:02 PM]
-- Context: SqlProvider(SqlCE) Model: AttributedMetaModel Build: 3.5.21022.8

le problème évident est le où 0=1 , après l'enregistrement a été chargé, j'ai confirmé que toutes les propriétés dans le "devicessionrecord" sont corrects pour inclure la clé primaire. Aussi lors de la capture de la "ChangeConflictException" il n'y a aucune information supplémentaire sur la raison de cet échec. J'ai également confirmé que cette exception est lancée avec exactement un enregistrement dans la base de données (l'enregistrement que je tente de mettre à jour)

ce qui est étrange, c'est que j'ai une déclaration de mise à jour très similaire dans une section de code différente et qu'elle génère le SQL suivant et met effectivement à jour mon serveur SQL Base de données de l'édition compacte.

UPDATE [Sessions]
SET [Is_Active] = @p4, [Disconnected] = @p5
WHERE ([Session_RecId] = @p0) AND ([App_RecId] = @p1) AND ([Is_Active] = 1) AND ([Established] = @p2) AND ([Disconnected] IS NULL) AND ([Member_Id] IS NULL) AND ([Company_Id] IS NULL) AND ([Site] IS NULL) AND (NOT ([Is_Device] = 1)) AND ([Machine_Name] = @p3)
-- @p0: Input Guid (Size = 0; Prec = 0; Scale = 0) [0fbbee53-cf4c-4643-9045-e0a284ad131b]
-- @p1: Input Guid (Size = 0; Prec = 0; Scale = 0) [7a174954-dd18-406e-833d-8da650207d3d]
-- @p2: Input DateTime (Size = 0; Prec = 0; Scale = 0) [9/4/2008 5:20:50 PM]
-- @p3: Input String (Size = 0; Prec = 0; Scale = 0) [CWMOBILEDEV]
-- @p4: Input Boolean (Size = 0; Prec = 0; Scale = 0) [False]
-- @p5: Input DateTime (Size = 0; Prec = 0; Scale = 0) [9/4/2008 5:20:52 PM]
-- Context: SqlProvider(SqlCE) Model: AttributedMetaModel Build: 3.5.21022.8

j'ai confirmé que les valeurs des champs primaires appropriés ont été identifiées dans le schéma de la base de données et dans le DBML qui génère les classes LINQ.

je suppose que c'est presque une question en deux parties:

  1. pourquoi l'exception est-elle lancée?
  2. après avoir passé en revue le deuxième ensemble de SQL généré, il semble que pour détecter les conflits, il serait bien pour vérifier tous les champs, mais j'imagine que ce serait assez inefficaces. Est-ce la façon dont cela fonctionne toujours? Est-il un paramètre pour vérifier uniquement la clé primaire?

je me bats avec ça depuis deux heures donc toute aide serait appréciée.

86
demandé sur Luke Girvin 2008-09-05 04:39:31

13 réponses

c'est méchant, mais simple:

vérifiez si les types de données pour tous les champs dans le O/R-Designer correspondent aux types de données dans votre table SQL. Double vérification de nullable! une colonne doit être soit NULL dans les deux O/R-Designer et SQL, ou non NULL dans les deux.

par exemple, une colonne NVARCHAR" title " est marquée comme NULLable dans votre base de données, et contient la valeur NULL. Même si la colonne est marquée comme non NULLable dans votre O / R-Mapping, LINQ le chargera avec succès et définira la colonne-String à null.

  • Maintenant vous changez quelque chose et appelez SubmitChanges ().
  • LINQ va générer une requête SQL contenant "WHERE [title] IS NULL", pour s'assurer que le titre n'a pas été modifié par quelqu'un d'autre.
  • LINQ cherche les propriétés de [titre] dans la cartographie.
  • LINQ jugera que [titre] N'est pas nul.
  • étant donné que le [titre] n'est pas nul, par logique il ne pourrait jamais être nul!
  • donc, optimiser la requête, LINQ remplace par "0 = 1", le Équivalent SQL de "jamais".

le même symptôme apparaît lorsque les types de données d'un champ ne correspondent pas au type de données dans SQL, ou si les champs sont manquants, puisque LINQ ne sera pas en mesure de s'assurer que les données SQL n'a pas changé depuis la lecture des données.

172
répondu Sam 2008-09-19 10:15:24

il y a une méthode sur DataContext appelée Refresh qui peut aider ici. Il vous permet de recharger l'enregistrement de la base de données avant que les modifications ne soient soumises, et offre différents modes pour déterminer les valeurs à conserver. "KeepChanges" semble le plus intelligent pour mes buts, il est destiné à fusionner mes modifications avec n'importe quel changement non-conflictuel qui s'est produit dans la base de données dans l'intervalle.

Si je comprends bien. :)

13
répondu Matt Sherman 2010-09-03 04:05:31

tout d'abord, il est utile de savoir, ce qui cause le problème. Google solution devrait aider, vous pouvez enregistrer les détails (tableau, colonne, ancienne valeur, nouvelle valeur) sur le conflit pour trouver une meilleure solution pour résoudre le conflit plus tard:

public class ChangeConflictExceptionWithDetails : ChangeConflictException
{
    public ChangeConflictExceptionWithDetails(ChangeConflictException inner, DataContext context)
        : base(inner.Message + " " + GetChangeConflictExceptionDetailString(context))
    {
    }

    /// <summary>
    /// Code from following link
    /// https://ittecture.wordpress.com/2008/10/17/tip-of-the-day-3/
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    static string GetChangeConflictExceptionDetailString(DataContext context)
    {
        StringBuilder sb = new StringBuilder();

        foreach (ObjectChangeConflict changeConflict in context.ChangeConflicts)
        {
            System.Data.Linq.Mapping.MetaTable metatable = context.Mapping.GetTable(changeConflict.Object.GetType());

            sb.AppendFormat("Table name: {0}", metatable.TableName);
            sb.AppendLine();

            foreach (MemberChangeConflict col in changeConflict.MemberConflicts)
            {
                sb.AppendFormat("Column name : {0}", col.Member.Name);
                sb.AppendLine();
                sb.AppendFormat("Original value : {0}", col.OriginalValue.ToString());
                sb.AppendLine();
                sb.AppendFormat("Current value : {0}", col.CurrentValue.ToString());
                sb.AppendLine();
                sb.AppendFormat("Database value : {0}", col.DatabaseValue.ToString());
                sb.AppendLine();
                sb.AppendLine();
            }
        }

        return sb.ToString();
    }
}

créer l'aide pour emballer vos sumbitChanges:

public static class DataContextExtensions
{
    public static void SubmitChangesWithDetailException(this DataContext dataContext)
    {   
        try
        {         
            dataContext.SubmitChanges();
        }
        catch (ChangeConflictException ex)
        {
            throw new ChangeConflictExceptionWithDetails(ex, dataContext);
        }           
    }
}

et ensuite appeler soumettre des changements de code:

Datamodel.SubmitChangesWithDetailException();

Enfin, enregistrez l'exception dans votre exception globale handler:

protected void Application_Error(object sender, EventArgs e)
{         
    Exception ex = Server.GetLastError();
    //TODO
}
12
répondu qub1n 2017-11-03 16:02:26

j'ai résolu cette erreur en redessinant une table de l'Explorateur de serveur au designer et en la reconstruisant.

10
répondu 2009-02-04 19:47:01

Cela peut aussi être causé par l'utilisation de plus d'un DbContext.

ainsi par exemple:

protected async Task loginUser(string username)
{
    using(var db = new Db())
    {
        var user = await db.Users
            .SingleAsync(u => u.Username == username);
        user.LastLogin = DateTime.UtcNow;
        await db.SaveChangesAsync();
    }
}

protected async Task doSomething(object obj)
{
    string username = "joe";
    using(var db = new Db())
    {
        var user = await db.Users
            .SingleAsync(u => u.Username == username);

        if (DateTime.UtcNow - user.LastLogin >
            new TimeSpan(0, 30, 0)
        )
            loginUser(username);

        user.Something = obj;
        await db.SaveChangesAsync();
    }
}

ce code échouera de temps en temps, d'une manière qui semble imprévisible, parce que l'utilisateur est utilisé dans les deux contextes, changé et sauvé dans l'un, puis sauvé dans l'autre. La représentation en mémoire de l'utilisateur qui possède "quelque chose" ne correspond pas à ce qu'il y a dans la base de données, et donc vous obtenez ce bug caché.

Une façon d' prévenir c'est d'écrire du code qui pourrait jamais être appelé comme une méthode de bibliothèque de telle manière qu'il prend une option DbContext:

protected async Task loginUser(string username, Db _db = null)
{
    await EFHelper.Using(_db, async db =>
    {
        var user = await db.Users...
        ... // Rest of loginUser code goes here
    });
}

public class EFHelper
{
    public static async Task Using<T>(T db, Func<T, Task> action)
        where T : DbContext, new()
    {
        if (db == null)
        {
            using (db = new T())
            {
                await action(db);
            }
        }
        else
        {
            await action(db);
        }
    }
}

donc maintenant votre méthode prend une base de données optionnelle, et s'il n'y en a pas une, Va et en fait une elle-même. Si c'est juste de l'réutilise ce qui a été transmis. La méthode d'assistance facilite la réutilisation de ce modèle à travers votre application.

10
répondu Chris Moschini 2018-08-21 14:17:39

Je ne sais pas si vous avez trouvé des réponses satisfaisantes à votre question, mais j'ai posté une question similaire et j'y ai finalement répondu moi-même. Il s'est avéré que L'option de connexion par défaut de NOCOUNT était activée pour la base de données, ce qui provoquait une exception de ChangeConflictException pour chaque mise à jour effectuée avec Linq vers Sql. Vous pouvez vous référer à mon message à ici .

3
répondu Michael Nero 2017-05-23 11:54:53

j'ai corrigé cette erreur en ajoutant (UpdateCheck = UpdateCheck.Never) à toutes les définitions de [Column] .

ne semble pas être une solution appropriée. Dans mon cas, il semble être lié au fait que ce tableau est une association à une autre table, d'où une ligne est supprimée.

C'est sur Windows Phone 7.5.

3
répondu Johan Paul 2012-07-03 10:27:21

c'est ce dont vous avez besoin pour annuler cette erreur sur le code C:

            try
            {
                _db.SubmitChanges(ConflictMode.ContinueOnConflict);
            }
            catch (ChangeConflictException e)
            {
                foreach (ObjectChangeConflict occ in _db.ChangeConflicts)
                {
                    occ.Resolve(RefreshMode.KeepChanges);
                }
            }
1
répondu MarceloBarbosa 2016-08-18 15:17:57

je sais que cette question a depuis longtemps été répondu, mais ici, j'ai passé les dernières heures à frapper ma tête contre un mur et je voulais juste partager ma solution qui s'est avéré ne pas être liée à l'un des éléments dans ce fil:

"151920920 la mise en Cache"!

la partie select() de mon objet de données utilisait la mise en cache. Quand il est venu à la mise à jour de l'objet une rangée non trouvée ou l'erreur changée surgissait.

plusieurs des les réponses ont mentionné l'utilisation de différents DataContext et rétrospectivement c'est probablement ce qui s'est passé mais cela ne m'a pas instantanément amené à penser la mise en cache donc espérons que cela aidera quelqu'un!

0
répondu rtpHarry 2011-09-06 15:26:21

j'ai récemment rencontré cette erreur, et j'ai trouvé que le problème n'était pas avec le contexte de mes données, mais avec une déclaration de mise à jour déclenchée à l'intérieur d'un déclencheur après que Commit était appelé sur le contexte. Le déclencheur était d'essayer de mettre à jour un champ non-nullable avec une valeur null, et il causait le contexte d'erreur avec le message mentionné ci-dessus.

j'ajoute cette réponse Uniquement pour aider les autres à faire face à cette erreur et à ne pas trouver de résolution dans les réponses ci-dessus.

0
répondu jamisonLikeCode 2011-11-25 17:20:28

j'ai aussi eu cette erreur en raison de l'utilisation de deux contextes différents. J'ai résolu ce problème en utilisant le contexte de données uniques.

0
répondu srinivas vadlamudi 2015-06-25 12:40:09

dans mon cas, le problème était avec les options de l'utilisateur à l'échelle du serveur.

https://msdn.microsoft.com/en-us/library/ms190763.aspx

j'ai activé L'option NOCOUNT dans hope pour obtenir des avantages de performance:

EXEC sys.sp_configure 'user options', 512;
RECONFIGURE;

et il s'avère que cela brise les contrôles de Linq pour les lignes affectées (autant que je peux le comprendre à partir de .net sources), conduisant à ChangeConflictException

Réinitialiser les options pour exclure le 512 bit a résolu le problème.

0
répondu Wojtek 2015-10-26 11:46:18

après avoir utilisé la réponse de qub1n, j'ai trouvé que le problème pour moi était que j'avais par inadvertance déclaré une colonne de base de données pour être décimal(18,0). J'attribuais une valeur décimale, mais la base de données la changeait, enlevant la partie décimale. Cela a abouti à la ligne changé au problème.

ajoute juste ceci si quelqu'un d'autre rencontre un problème similaire.

0
répondu John Pasquet 2016-10-03 18:17:35