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:
- Dlaczego wyjątek jest zgłaszany?
- 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.
źródło
Odpowiedzi:
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.
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.
źródło
VARCHAR(MAX) NOT NULL
naVARCHAR(MAX) NULL
i oczekując, że zadziała. Bardzo prosty błąd.NUMERIC(12,8)
zmapowaną kolumnę doDecimal
właściwości. Musiałem sprecyzować DbType w atrybucie kolumny[Column(DbType="numeric(12,8)")] public decimal? MyProperty ...
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 }
źródło
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. :)
źródło
dc.Refresh(RefreshMode.KeepChanges,changedObject);
przed dc.SubmitChangesMoż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.
źródło
Rozwiązałem ten błąd, przeciągając ponownie tabelę z eksploratora serwera do projektanta i ponownie budując.
źródło
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); } }
źródło
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 .
źródło
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.
źródło
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ę.
źródło
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!
źródło
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.
źródło
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.
źródło
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.
źródło
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.
źródło
po prostu idź z Linq2DB, znacznie lepiej
źródło