Co mogę zrobić, aby rozwiązać wyjątek „Nie znaleziono lub zmieniono wiersza” w LINQ to SQL w bazie danych SQL Server Compact Edition?

96

Podczas wykonywania funkcji SubmitChanges w DataContext po zaktualizowaniu kilku właściwości z połączeniem LINQ to SQL (w przypadku programu SQL Server Compact Edition) otrzymuję komunikat „Wiersz nie został znaleziony lub zmieniony”. 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();

Zapytanie generuje następujący kod SQL:

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

Oczywistym problemem jest gdzie 0 = 1. Po załadowaniu rekordu potwierdziłem, że wszystkie właściwości w „deviceSessionRecord” są poprawne i obejmują klucz podstawowy. Również podczas przechwytywania „ChangeConflictException” nie ma żadnych dodatkowych informacji o tym, dlaczego to się nie powiodło. Potwierdziłem również, że ten wyjątek jest generowany z dokładnie jednym rekordem w bazie danych (rekord, który próbuję zaktualizować)

Dziwne jest to, że mam bardzo podobną instrukcję aktualizacji w innej sekcji kodu i generuje ona następujący kod SQL i faktycznie aktualizuje moją bazę danych SQL Server Compact Edition.

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

Potwierdziłem, że odpowiednie wartości pól podstawowych zostały zidentyfikowane zarówno w schemacie bazy danych, jak iw DBML, który generuje klasy LINQ.

Myślę, że to prawie dwuczęściowe pytanie:

  1. Dlaczego wyjątek jest zgłaszany?
  2. Po przejrzeniu drugiego zestawu wygenerowanego kodu SQL wydaje się, że do wykrywania konfliktów dobrze byłoby sprawdzić wszystkie pola, ale wyobrażam sobie, że byłoby to dość nieefektywne. Czy tak to zawsze działa? Czy istnieje ustawienie, aby po prostu sprawdzić klucz podstawowy?

Walczyłem z tym przez ostatnie dwie godziny, więc każda pomoc byłaby mile widziana.

Kevin
źródło
FWIW: Otrzymałem ten błąd, gdy nieumyślnie dwukrotnie wywołałem metodę. Nastąpiłoby to podczas drugiego połączenia.
Kris
Doskonałe informacje ogólne
CAK2

Odpowiedzi:

191

To nieprzyjemne, ale proste:

Sprawdź, czy typy danych dla wszystkich pól w O / R-Designer są zgodne z typami danych w tabeli SQL. Podwójne sprawdzenie wartości null! Kolumna powinna mieć wartość null w obu O / R-Designer i SQL lub nie dopuszczać wartości null w obu.

Na przykład kolumna NVARCHAR „tytuł” ​​jest oznaczona w bazie danych jako NULLable i zawiera wartość NULL. Mimo że kolumna jest oznaczona jako NIE NULLable w mapowaniu O / R, LINQ załaduje ją pomyślnie i ustawi ciąg kolumny na null.

  • Teraz możesz coś zmienić i wywołać funkcję SubmitChanges ().
  • LINQ wygeneruje zapytanie SQL zawierające „WHERE [tytuł] IS NULL”, aby upewnić się, że tytuł nie został zmieniony przez kogoś innego.
  • LINQ wyszukuje właściwości [tytuł] w mapowaniu.
  • LINQ znajdzie [title] NOT NULLable.
  • Ponieważ [tytuł] NIE jest NULLable, logicznie nie może być NULL!
  • Tak więc, optymalizując zapytanie, LINQ zamienia je na „gdzie 0 = 1”, odpowiednik „nigdy” w języku SQL.

Ten sam symptom pojawi się, gdy typy danych pola nie są zgodne z typem danych w języku SQL lub jeśli brakuje pól, ponieważ LINQ nie będzie w stanie upewnić się, że dane SQL nie uległy zmianie od czasu odczytania danych.

Sam
źródło
4
Miałem podobny - choć nieco inny - problem, a Twoja rada, aby dwukrotnie sprawdzić wartość null, uratowała mi dzień! Byłem już łysy, ale ten problem z pewnością kosztowałby mnie kolejną fryzurę, gdybym miał jedną .. dzięki!
Rune Jacobsen
7
Upewnij się, że właściwość „Nullable” w oknie właściwości została ustawiona na True. Edytowałem właściwość „Typ danych serwera”, zmieniając ją z VARCHAR(MAX) NOT NULLna VARCHAR(MAX) NULLi oczekując, że zadziała. Bardzo prosty błąd.
Musiałem to zagłosować. Zaoszczędziło mi to mnóstwo czasu. Patrzyłem na moje poziomy izolacji, ponieważ myślałem, że to problem z współbieżnością
Adrian,
3
Miałem NUMERIC(12,8)zmapowaną kolumnę do Decimalwłaściwości. Musiałem sprecyzować DbType w atrybucie kolumny [Column(DbType="numeric(12,8)")] public decimal? MyProperty ...
Costo
3
Jednym ze sposobów zidentyfikowania problematycznych pól / kolumn jest zapisanie bieżących klas jednostek Linq-to-SQL, znajdujących się w pliku .dbml, w osobnym pliku. Następnie usuń bieżący model i wygeneruj go ponownie z bazy danych (przy użyciu programu VS), co spowoduje wygenerowanie nowego pliku .dbml. Następnie wystarczy uruchomić komparator, taki jak WinMerge lub WinDiff na dwóch plikach .dbml, aby zlokalizować różnice w problemach.
david.barkhuizen
26

Po pierwsze, warto wiedzieć, co jest przyczyną problemu. Rozwiązanie google powinno pomóc, możesz zarejestrować szczegóły (tabela, kolumna, stara wartość, nowa wartość) o konflikcie, aby znaleźć lepsze rozwiązanie później:

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();
    }
}

Utwórz pomocnika do zawijania swojego sumbitu

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

A następnie zadzwoń i prześlij kod zmian:

Datamodel.SubmitChangesWithDetailException();

Na koniec zarejestruj wyjątek w globalnej obsłudze wyjątków:

protected void Application_Error(object sender, EventArgs e)
{         
    Exception ex = Server.GetLastError();
    //TODO
}
Tomas Kubes
źródło
3
Rewelacyjne rozwiązanie! Mam tabelę, która ma około 80 pól i jest w niej wiele wyzwalaczy, które aktualizują różne pola podczas wstawiania i aktualizacji. Otrzymałem ten błąd podczas aktualizowania kontekstu danych za pomocą L2S, ale byłem prawie pewien, że jest to spowodowane przez jeden z wyzwalaczy aktualizujących pole, co powoduje, że kontekst danych różni się od danych w tabeli. Twój kod pomógł mi zobaczyć, które dokładnie pole powodowało brak synchronizacji kontekstu danych z tabelą. Wielkie dzięki !!
Jagd
1
To świetne rozwiązanie do dużych stołów. Aby obsłużyć wartości null, zmień „col.XValue.ToString ()” na „col.XValue == null? „null”: col.XValue.ToString () ”dla każdego z trzech pól wartości.
humbads
Podobnie jak w przypadku ochrony przed odwołaniami o wartości null podczas określania wartości OriginalValue, CurrentValue i DatabaseValue.
Floyd Kosch
16

W DataContext znajduje się metoda o nazwie Refresh, która może w tym pomóc. Umożliwia ponowne załadowanie rekordu bazy danych przed przesłaniem zmian i oferuje różne tryby określania, które wartości zachować. „KeepChanges” wydaje się najmądrzejszy dla moich celów, ma na celu połączenie moich zmian z każdą niekolidującą zmianą, która wydarzyła się w międzyczasie w bazie danych.

Jeśli dobrze to rozumiem. :)

Matt Sherman
źródło
6
Ta odpowiedź rozwiązała problem w moim przypadku: dc.Refresh(RefreshMode.KeepChanges,changedObject);przed dc.SubmitChanges
HugoRune
Miałem ten problem podczas stosowania ReadOnlyAttribute do właściwości w witrynie sieci Web danych dynamicznych. Aktualizacje przestały działać i otrzymywałem komunikat o błędzie „Nie znaleziono lub zmieniono wiersza” (wstawki były jednak w porządku). Powyższe rozwiązanie pozwoliło zaoszczędzić mnóstwo wysiłku i czasu!
Chris Cannon
Czy mógłbyś wyjaśnić wartości RefreshMode, np. Co oznacza KeepCurrentValues? co to robi? Wielkie dzięki. Mógłbym stworzyć pytanie ...
Chris Cannon
Miałem problemy z równoczesnymi transakcjami, które nie kończyły się na czas, aby inna transakcja zaczęła się w tych samych wierszach. KeepChanges pomogło mi tutaj, więc może po prostu przerywa bieżącą transakcję (zachowując zapisane wartości) i rozpoczyna nową (szczerze mówiąc nie mam pojęcia)
Erik Bergstedt
11

Może to być również spowodowane użyciem więcej niż jednego DbContext.

Na przykład:

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();
    }
}

Ten kod od czasu do czasu zawodzi w sposób, który wydaje się nieprzewidywalny, ponieważ użytkownik jest używany w obu kontekstach, zmieniany i zapisywany w jednym, a następnie zapisywany w drugim. Reprezentacja użytkownika, który jest właścicielem „czegoś” w pamięci, nie jest zgodna z tym, co jest w bazie danych, więc pojawia się ten czający błąd.

Jednym ze sposobów, aby temu zapobiec, jest napisanie dowolnego kodu, który mógłby kiedykolwiek zostać wywołany jako metoda biblioteczna w taki sposób, aby pobierał opcjonalny 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);
        }
    }
}

Więc teraz twoja metoda przyjmuje opcjonalną bazę danych, a jeśli jej nie ma, idzie i tworzy ją sama. Jeśli tak, po prostu ponownie wykorzystuje to, co zostało przekazane. Metoda pomocnika ułatwia ponowne użycie tego wzorca w całej aplikacji.

Chris Moschini
źródło
10

Rozwiązałem ten błąd, przeciągając ponownie tabelę z eksploratora serwera do projektanta i ponownie budując.


źródło
Ponowne przeniesienie nieprawidłowej tabeli z Eksploratora serwera do projektanta i przebudowanie rozwiązało to również dla mnie.
rstackhouse
4

Oto, czego potrzebujesz, aby zastąpić ten błąd w kodzie C #:

            try
            {
                _db.SubmitChanges(ConflictMode.ContinueOnConflict);
            }
            catch (ChangeConflictException e)
            {
                foreach (ObjectChangeConflict occ in _db.ChangeConflicts)
                {
                    occ.Resolve(RefreshMode.KeepChanges);
                }
            }
MarceloBarbosa
źródło
Zaplanowałem elementy przesłane przez front-end aplikacji do bazy danych. Te wyzwalają wykonanie w usłudze, każdy w innym wątku. Użytkownik może nacisnąć przycisk „Anuluj”, który zmienia stan wszystkich oczekujących poleceń. Usługa kończy każdą z nich, ale stwierdza, że ​​„Oczekujące” zostało zmienione na „Anulowane” i nie można go zmienić na „Ukończono”. To rozwiązało problem.
pwrgreg007
2
Sprawdź także inne wyliczenia RefreshMode, takie jak KeepCurrentValues. Zauważ, że po użyciu tej logiki musisz ponownie wywołać funkcję SubmitChanges. Zobacz msdn.microsoft.com/en-us/library/… .
pwrgreg007
3

Nie wiem, czy znalazłeś zadowalające odpowiedzi na swoje pytanie, ale opublikowałem podobne pytanie i ostatecznie sam na nie odpowiedziałem. Okazało się, że dla bazy danych włączono opcję domyślnego połączenia NOCOUNT, co powodowało wyjątek ChangeConflictException dla każdej aktualizacji dokonanej za pomocą Linq do Sql. Możesz zapoznać się z moim postem tutaj .

Michael Nero
źródło
3

Naprawiłem to, dodając (UpdateCheck = UpdateCheck.Never)do wszystkich [Column]definicji.

Nie wydaje się jednak odpowiednim rozwiązaniem. W moim przypadku wydaje się to być związane z faktem, że ta tabela ma powiązanie z inną tabelą, z której usuwany jest wiersz.

To jest w systemie Windows Phone 7.5.

Johan Paul
źródło
1

W moim przypadku błąd został zgłoszony, gdy dwóch użytkowników mających różne konteksty danych LINQ-to-SQL zaktualizowało tę samą jednostkę w ten sam sposób. Gdy drugi użytkownik próbował aktualizacji, kopia, którą miał w kontekście danych, była nieaktualna, mimo że została odczytana po zakończeniu pierwszej aktualizacji.

Odkryłem wyjaśnienie i rozwiązanie w tym artykule autorstwa Akshaya Phadke: https://www.c-sharpcorner.com/article/overview-of-concurrency-in-linq-to-sql/

Oto kod, który głównie podniosłem:

try
{
    this.DC.SubmitChanges();
}
catch (ChangeConflictException)
{
     this.DC.ChangeConflicts.ResolveAll(RefreshMode.OverwriteCurrentValues);

     foreach (ObjectChangeConflict objectChangeConflict in this.DC.ChangeConflicts)
     {
         foreach (MemberChangeConflict memberChangeConflict in objectChangeConflict.MemberConflicts)
         {
             Debug.WriteLine("Property Name = " + memberChangeConflict.Member.Name);
             Debug.WriteLine("Current Value = " + memberChangeConflict.CurrentValue.ToString());
             Debug.WriteLine("Original Value = " + memberChangeConflict.OriginalValue.ToString());
             Debug.WriteLine("Database Value = " + memberChangeConflict.DatabaseValue.ToString());
         }
     }
     this.DC.SubmitChanges();
     this.DC.Refresh(RefreshMode.OverwriteCurrentValues, att);
 }

Kiedy spojrzałem na moje okno wyjściowe podczas debugowania, zobaczyłem, że bieżąca wartość jest zgodna z wartością bazy danych. Winowajcą była zawsze „oryginalna wartość”. To była wartość odczytana przez kontekst danych przed zastosowaniem aktualizacji.

Podziękowania dla MarceloBarbosa za inspirację.

CAK2
źródło
0

Wiem, że na to pytanie już dawno udzielono odpowiedzi, ale tutaj spędziłem ostatnie kilka godzin waląc głową w ścianę i chciałem tylko podzielić się swoim rozwiązaniem, które okazało się nie być związane z żadnym z elementów tego wątku:

Buforowanie!

Część select () mojego obiektu danych korzystała z buforowania. Podczas aktualizacji obiektu pojawiał się błąd Nie znaleziono lub zmieniono wiersza.

Kilka odpowiedzi wspominało o używaniu różnych DataContext i z perspektywy czasu prawdopodobnie tak się dzieje, ale nie od razu pomyślałem, że buforowanie, więc mam nadzieję, że to komuś pomoże!

rtpHarry
źródło
0

Niedawno napotkałem ten błąd i stwierdziłem, że problem nie dotyczy mojego kontekstu danych, ale instrukcji aktualizacji uruchamianej wewnątrz wyzwalacza po wywołaniu polecenia Commit w kontekście. Wyzwalacz próbował zaktualizować pole niepodlegające wartości null wartością null i powodował błąd kontekstu z komunikatem wymienionym powyżej.

Dodaję tę odpowiedź wyłącznie po to, aby pomóc innym radzić sobie z tym błędem i nie znaleźć rozwiązania w odpowiedziach powyżej.

jamisonLikeCode
źródło
0

Mam również ten błąd, ponieważ używam dwóch różnych kontekstów. Rozwiązałem ten problem, używając pojedynczego kontekstu danych.

srinivas vadlamudi
źródło
0

W moim przypadku problem dotyczył opcji użytkownika na całym serwerze. Następujący:

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

Włączyłem opcję NOCOUNT w nadziei uzyskania pewnych korzyści związanych z wydajnością:

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

i okazuje się, że powoduje to zerwanie kontroli Linqa pod kątem wierszy, których dotyczy problem (na ile mogę to ustalić na podstawie źródeł .NET), co prowadzi do ChangeConflictException

Zresetowanie opcji wykluczających 512-bitowe rozwiązanie rozwiązało problem.

Wojtek
źródło
0

Po zastosowaniu odpowiedzi qub1n stwierdziłem, że problem polegał na tym, że nieumyślnie zadeklarowałem kolumnę bazy danych jako dziesiętną (18,0). Przypisywałem wartość dziesiętną, ale baza danych ją zmieniała, usuwając część dziesiętną. Spowodowało to problem zmiany wiersza.

Po prostu dodaj to, jeśli ktoś napotka podobny problem.

John Pasquet
źródło
0

po prostu idź z Linq2DB, znacznie lepiej

nam vo
źródło