Що я можу зробити, щоб вирішити виняток “Рядок не знайдено чи змінено” у LINQ to SQL у базі даних SQL Server Compact Edition?


96

При виконанні SubmitChanges до DataContext після оновлення кількох властивостей за допомогою підключення LINQ to SQL (проти SQL Server Compact Edition) я отримую "Рядок не знайдено або змінено". 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();

Запит генерує такий 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

Очевидною проблемою є WHERE 0 = 1. Після завантаження запису я підтвердив, що всі властивості в "deviceSessionRecord" правильні для включення первинного ключа. Також при лові "ChangeConflictException" немає додаткової інформації про те, чому це не вдалося. Я також підтвердив, що цей виняток отримує рівно один запис у базі даних (запис, який я намагаюся оновити)

Що дивно, що у мене є дуже схожа заява на оновлення в іншому розділі коду, і він генерує наступний SQL і дійсно оновлює мою базу даних Compact Edition SQL Server.

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

Я підтвердив, що належні значення первинних полів були визначені як у Схемі бази даних, так і в DBML, який генерує класи LINQ.

Я думаю, це питання майже з двох частин:

  1. Чому виняток кидається?
  2. Після перегляду другого набору згенерованого SQL, здається, для виявлення конфліктів було б непогано перевірити всі поля, але, я думаю, це було б досить неефективно. Це так завжди працює? Чи є параметр просто перевірити первинний ключ?

Я боровся з цим протягом останніх двох годин, тому будь-яка допомога буде вдячна.


FWIW: Я отримував цю помилку при ненавмисному виклику методу двічі. Це відбудеться під час другого дзвінка.
Кріс,

Чудову довідкову інформацію можна знайти на сайті c-sharpcorner.com/article/…
CAK2

Відповіді:


189

Це неприємно, але просто:

Перевірте, чи відповідають типи даних для всіх полів O / R-конструктора типам даних у вашій таблиці SQL. Подвійна перевірка на нульовий! Стовпець повинен бути або нульовим, і в O / R-конструкторі, і в SQL, або не бути нульовим в обох.

Наприклад, стовпець NVARCHAR "title" позначений як NULLable у вашій базі даних і містить значення NULL. Навіть незважаючи на те, що стовпець позначено як NOT NULLable у вашому O / R-Mapping, LINQ успішно завантажить його та встановить для рядка стовпець значення null.

  • Тепер ви щось змінюєте і викликаєте SubmitChanges ().
  • LINQ генерує SQL-запит, що містить "ДЕ [заголовок] НУЛЬКИЙ", щоб переконатися, що заголовок не змінено кимось іншим.
  • LINQ шукає властивості [title] у відображенні.
  • LINQ знайде [title] NOT NULLable.
  • Оскільки [title] НЕ NULLable, за логікою ніколи не може бути NULL!
  • Отже, оптимізуючи запит, LINQ замінює його на "де 0 = 1", еквівалент SQL "ніколи".

Той самий симптом з'явиться, коли типи даних поля не відповідають типу даних у SQL або якщо поля відсутні, оскільки LINQ не зможе переконатися, що дані SQL не змінилися з моменту зчитування даних.


4
У мене була подібна - хоч і трохи інша - проблема, і ваша порада подвійної перевірки на наявність недійсного врятувала мені день! Я вже був лисим, але це питання, безсумнівно, коштувало б мені ще однієї шевелюри, якби у мене була така .. дякую!
Rune Jacobsen

7
Обов’язково встановіть для властивості 'Nullable' у вікні властивостей значення True. Я редагував властивість "Тип даних сервера", змінював його з VARCHAR(MAX) NOT NULLна VARCHAR(MAX) NULLі очікував, що він запрацює. Дуже проста помилка.

Довелося підтримати це. Це заощадило мені купу часу. Дивився на рівень моєї ізоляції, тому що я думав, що це питання паралельності
Адріан,

3
У мене була NUMERIC(12,8)стовпець, відображений у Decimalвласність. Мені довелося уточнити атрибут DbType у стовпці [Column(DbType="numeric(12,8)")] public decimal? MyProperty ...
Costo

3
Одним із способів визначення проблемних полів / стовпців є збереження поточних класів сутності Linq-SQL, розташованих у файлі .dbml, в окремий файл. Потім видаліть поточну модель і відновіть її з бази даних (за допомогою VS), що створить новий .dbml файл. Потім просто запустіть компаратор типу WinMerge або WinDiff на двох .dbml файлах, щоб знайти проблемні відмінності.
david.barkhuizen

24

По-перше, корисно знати, що викликає проблему. Рішення Googling має допомогти, ви можете записати деталі (таблиця, стовпець, старе значення, нове значення) про конфлікт, щоб пізніше знайти краще рішення для вирішення конфлікту:

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

Створіть помічник для обгортання вашого sumbitChanges:

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

А потім зателефонуйте коду подання змін:

Datamodel.SubmitChangesWithDetailException();

Нарешті, введіть виняток у вашому обробнику глобальних винятків:

protected void Application_Error(object sender, EventArgs e)
{         
    Exception ex = Server.GetLastError();
    //TODO
}

3
Чудове рішення! У мене є таблиця, яка містить близько 80 полів, і в таблиці є численні тригери, які оновлюють різні поля під час вставки та оновлення. Я отримував цю помилку під час оновлення datacontext за допомогою L2S, але був майже впевнений, що це викликано одним із тригерів оновлення поля, тим самим спричиняючи, що контекст даних відрізняється від даних у таблиці. Ваш код допоміг мені зрозуміти, яке саме поле спричиняє синхронізацію контексту даних із таблицею. Дякую тонна !!
Ягд

1
Це відмінне рішення для великих столів. Щоб обробляти нулі, змініть 'col.XValue.ToString ()' на 'col.XValue == null? "null": col.XValue.ToString () 'для кожного з трьох полів значень.
humbads

Так само щодо захисту від нульових посилань при роздрібненні OriginalValue, CurrentValue та DatabaseValue.
Флойд Кош

16

Існує метод на DataContext під назвою Refresh, який може допомогти тут. Це дозволяє перезавантажити запис бази даних до подання змін, і пропонує різні режими, щоб визначити, які значення зберігати. "KeepChanges" видається найрозумнішим для моїх цілей, він призначений для об'єднання моїх змін із будь-якими безконфліктними змінами, що відбулися тим часом у базі даних.

Якщо я це правильно розумію. :)


5
Ця відповідь dc.Refresh(RefreshMode.KeepChanges,changedObject);вирішила проблему в моєму випадку: до dc.SubmitChanges
HugoRune

У мене виникла ця проблема при застосуванні ReadOnlyAttribute до властивостей веб-сайту Dynamic Data. Оновлення перестали працювати, і я отримав повідомлення про помилку "Рядок не знайдено чи змінено" (хоча вставки були нормальними). Вищезазначене дозволило заощадити сили та час!
Кріс Кеннон

Не могли б ви пояснити значення RefreshMode, наприклад, що означає KeepCurrentValues? що воно робить? Велике дякую. Я міг би створити запитання ...
Кріс Кеннон,

У мене були проблеми з одночасними транзакціями, які не завершились вчасно, щоб розпочати іншу транзакцію в тих самих рядках. KeepChanges допоміг мені тут, тому, можливо, він просто перериває поточну транзакцію (зберігаючи збережені значення) та запускаючи нову (чесно кажучи, я не маю уявлення)
Ерік Бергштедт

11

Це також може бути спричинено використанням більш ніж одного DbContext.

Так, наприклад:

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

Цей код час від часу вийде з ладу таким чином, що здається непередбачуваним, оскільки користувач використовується в обох контекстах, змінюється і зберігається в одному, потім зберігається в іншому. Представлення в пам'яті користувача, який є власником "Щось", не відповідає тому, що є в базі даних, і тому ви отримуєте цю хибну помилку.

Одним із способів запобігти цьому є написання будь-якого коду, який коли-небудь може бути викликаний як бібліотечний метод, таким чином, що він бере необов’язковий 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);
        }
    }
}

Отже, тепер ваш метод бере необов’язкову базу даних, а якщо її немає, переходить і створює її сам. Якщо є, ви просто повторно використовуєте те, що було передано. Допоміжний метод дозволяє легко повторно використовувати цей шаблон у вашому додатку.


10

Я вирішив цю помилку, перенаправивши таблицю від провідника сервера до конструктора та повторно побудувавши.


Перенаправлення таблиці-порушника з Провідника серверів на конструктор та відновлення це також виправило це для мене.
rstackhouse

4

Ось що вам потрібно, щоб замінити цю помилку на коді C #:

            try
            {
                _db.SubmitChanges(ConflictMode.ContinueOnConflict);
            }
            catch (ChangeConflictException e)
            {
                foreach (ObjectChangeConflict occ in _db.ChangeConflicts)
                {
                    occ.Resolve(RefreshMode.KeepChanges);
                }
            }

У мене є планові елементи, подані додатком на базі даних. Ці тригерні виконання в сервісі, кожен з різних потоків. Користувач може натиснути кнопку "Скасувати", яка змінює весь статус невиконаної команди. Послуга завершує кожну з них, але виявляє, що значення "Очікує на розгляд" було змінено на "Скасовано" і не може змінити його на "Завершено". Це вирішило проблему для мене.
pwrgreg007

2
Також перевірте інші перерахунки RefreshMode, як-от KeepCurrentValues. Зауважте, що вам потрібно знову зателефонувати SubmitChanges після використання цієї логіки. Див msdn.microsoft.com/en-us/library / ... .
pwrgreg007

3

Не знаю, чи знайшли ви якісь задовільні відповіді на своє запитання, але я опублікував подібне запитання і врешті-решт сам відповів на нього. Виявилося, що для бази даних увімкнено опцію підключення за замовчуванням NOCOUNT, що спричинило ChangeConflictException для кожного оновлення, зробленого Linq до Sql. Ви можете звернутися до мого повідомлення тут .


3

Я виправив це, додавши (UpdateCheck = UpdateCheck.Never)до всіх [Column]визначень.

Однак не схоже на відповідне рішення. У моєму випадку це, здається, пов'язано з тим, що ця таблиця має зв'язок з іншою таблицею, з якої рядок видаляється.

Це на Windows Phone 7.5.


1

У моєму випадку помилка виникла, коли двоє користувачів, що мають різні контексти даних LINQ-to-SQL, оновлювали один і той самий об’єкт однаково. Коли другий користувач спробував оновити, копія, яку вони мали у своєму контексті даних, застаріла, хоча вона була прочитана після завершення першого оновлення.

Я знайшов пояснення та рішення в цій статті від Акшая Фадке: https://www.c-sharpcorner.com/article/overview-of-concurrency-in-linq-to-sql/

Ось код, який я переважно піднімав:

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

Коли я дивився на своє вікно виводу під час налагодження, я міг побачити, що поточне значення відповідає значенню бази даних. "Вихідна цінність" завжди була винуватцем. Це було значення, прочитане контекстом даних перед застосуванням оновлення.

Дякую MarceloBarbosa за натхнення.


0

Я знаю, що на це питання вже давно відповіли, але тут я провів останні кілька годин ударом головою об стіну, і я просто хотів поділитися своїм рішенням, яке виявилося не пов’язаним ні з одним із пунктів цієї теми:

Кешування!

Частина select () мого об'єкта даних використовувала кешування. Коли справа доходила до оновлення об’єкта, з’являлася помилка «Рядок не знайдений або змінений».

У кількох відповідях згадувалось використання різних DataContext, і в ретроспективі це, мабуть, саме те, що відбувалося, але це не відразу призвело мене до думки, що кешування так сподіваюся, це допоможе комусь!


0

Нещодавно я зіткнувся з цією помилкою і виявив, що проблема не в моєму контексті даних, а в операторі оновлення, що спрацьовує всередині тригера після того, як у контексті було викликано коміт. Тригер намагався оновити ненульоване поле з нульовим значенням, і це спричинило помилку контексту із повідомленням, згаданим вище.

Я додаю цю відповідь виключно для того, щоб допомогти іншим, хто має справу з цією помилкою та не знайти рішення у відповідях вище.


0

Я також отримав цю помилку через використання двох різних контекстів. Я вирішив цю проблему, використовуючи єдиний контекст даних.


0

У моєму випадку проблема полягала в загальносерверних опціях користувача. Наступні:

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

Я ввімкнув опцію NOCOUNT, сподіваючись отримати деякі переваги щодо продуктивності:

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

і це виявляється, щоб зламати чеки Linq на постраждалі рядки (наскільки я можу це зрозуміти з джерел .NET), що призводить до ChangeConflictException

Скидання параметрів для виключення 512 біт вирішило проблему.


0

Використовуючи відповідь qub1n, я виявив, що для мене проблема полягала в тому, що я ненавмисно оголосив стовпчик бази даних десятковим (18,0). Я призначав десяткове значення, але база даних змінювала його, позбавляючи десяткової частини. Це призвело до зміни зміни в рядку.

Просто додайте це, якщо хтось інший зіткнеться з подібною проблемою.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.