Что я могу сделать, чтобы устранить исключение «Строка не найдена или не изменена» в 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 и действительно обновляет мою базу данных 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

Я подтвердил, что правильные значения первичных полей были определены как в схеме базы данных, так и в DBML, который генерирует классы LINQ.

Думаю, это почти вопрос из двух частей:

  1. Почему выбрасывается исключение?
  2. После просмотра второго набора сгенерированных SQL кажется, что для обнаружения конфликтов было бы неплохо проверить все поля, но я полагаю, что это было бы довольно неэффективно. Это всегда так работает? Есть ли возможность просто проверить первичный ключ?

Я боролся с этим последние два часа, поэтому любая помощь будет оценена.

Кевин
источник
FWIW: я получал эту ошибку при двойном непреднамеренном вызове метода. Это произойдет при втором звонке.
Kris
Отличную справочную информацию можно найти на c-sharpcorner.com/article/…
CAK2,

Ответы:

189

Противно, но просто:

Убедитесь, что типы данных для всех полей в O / R-Designer соответствуют типам данных в вашей таблице SQL. Дважды проверьте наличие значения NULL! Столбец должен быть либо допускающим значение NULL в O / R-Designer и SQL, либо не допускать значения NULL в обоих.

Например, «заголовок» столбца NVARCHAR помечен в вашей базе данных как NULLable и содержит значение NULL. Несмотря на то, что столбец отмечен как NOT NULLable в вашем сопоставлении O / R, LINQ успешно загрузит его и установит для String-String значение null.

  • Теперь вы что-то меняете и вызываете SubmitChanges ().
  • LINQ сгенерирует SQL-запрос, содержащий «WHERE [title] IS NULL», чтобы убедиться, что заголовок не был изменен кем-то другим.
  • LINQ ищет свойства [заголовок] в сопоставлении.
  • LINQ найдет [заголовок] NOT NULLable.
  • Поскольку [title] НЕ допускает NULL, по логике он никогда не может быть NULL!
  • Итак, оптимизируя запрос, LINQ заменяет его на «where 0 = 1», SQL-эквивалент «never».

Тот же симптом появится, когда типы данных поля не соответствуют типу данных в SQL или если поля отсутствуют, поскольку LINQ не сможет гарантировать, что данные SQL не изменились с момента чтения данных.

Сэм
источник
4
У меня была похожая - хотя и немного другая - проблема, и ваш совет дважды проверить наличие обнуляемого значения спас мне день! Я уже был лысым, но эта проблема наверняка стоила бы мне еще одной шевелюры, если бы она у меня была ... спасибо!
Rune Jacobsen
7
Убедитесь, что вы установили для свойства «Nullable» в окне свойств значение True. Я редактировал свойство «Тип данных сервера», меняя его с VARCHAR(MAX) NOT NULLна VARCHAR(MAX) NULLи ожидая, что оно будет работать. Очень простая ошибка.
Пришлось проголосовать за это. Это сэкономило мне массу времени. Смотрел на свои уровни изоляции, потому что думал, что это проблема параллелизма
Адриан
3
У меня был NUMERIC(12,8)столбец, сопоставленный со Decimalсвойством. Мне пришлось уточнить DbType в атрибуте Column [Column(DbType="numeric(12,8)")] public decimal? MyProperty ...
Costo
3
Один из способов определения проблемных полей / столбцов - сохранить текущие классы сущностей Linq-to-SQL, расположенные в файле .dbml, в отдельном файле. Затем удалите текущую модель и повторно сгенерируйте ее из базы данных (с помощью VS), в результате чего будет создан новый файл .dbml. Затем просто запустите компаратор, такой как WinMerge или WinDiff, для двух файлов .dbml, чтобы найти различия в проблемах.
david.barkhuizen
24

Во-первых, полезно знать, что вызывает проблему. Решение с поиском в Google должно помочь, вы можете зарегистрировать детали (таблица, столбец, старое значение, новое значение) о конфликте, чтобы найти лучшее решение для разрешения конфликта позже:

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 полей, и в таблице есть множество триггеров, которые обновляют различные поля во время вставок и обновлений. Я получал эту ошибку при обновлении контекста данных с помощью L2S, но был почти уверен, что это было вызвано одним из триггеров, обновляющих поле, что привело к тому, что контекст данных отличался от данных в таблице. Ваш код помог мне увидеть, в каком именно поле происходит рассинхронизация контекста данных с таблицей. Благодаря тонну!!
Jagd
1
Это отличное решение для больших столов. Чтобы обрабатывать значения NULL, измените col.XValue.ToString () на col.XValue == null? "null": col.XValue.ToString () 'для каждого из трех полей значений.
humbads
То же касается защиты от нулевых ссылок при строковом преобразовании OriginalValue, CurrentValue и DatabaseValue.
Floyd Kosch
16

В DataContext есть метод Refresh, который может здесь помочь. Он позволяет перезагружать запись базы данных перед отправкой изменений и предлагает различные режимы для определения, какие значения следует сохранить. «KeepChanges» кажется мне самым умным для моих целей, он предназначен для объединения моих изменений с любыми неконфликтными изменениями, которые произошли в базе данных за это время.

Если я правильно понимаю. :)

Мэтт Шерман
источник
5
Этот ответ dc.Refresh(RefreshMode.KeepChanges,changedObject);устранил проблему в моем случае: до dc.SubmitChanges
HugoRune
У меня возникла эта проблема при применении атрибута ReadOnlyAttribute к свойствам на веб-сайте динамических данных. Обновления перестали работать, и я получал сообщение об ошибке «Строка не найдена или не изменена» (хотя вставки были в порядке). Вышеупомянутое исправление сэкономило массу усилий и времени!
Крис Кэннон
Не могли бы вы объяснить значения 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();
    }
}

Этот код будет время от времени давать сбой, что кажется непредсказуемым, поскольку пользователь используется в обоих контекстах, изменяется и сохраняется в одном, а затем сохраняется в другом. Представление в памяти пользователя, владеющего «Something», не соответствует тому, что находится в базе данных, и поэтому вы получаете эту скрытую ошибку.

Один из способов предотвратить это - написать любой код, который может когда-либо вызываться как библиотечный метод, таким образом, чтобы он принимал необязательный 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

Я решил эту ошибку, перетащив таблицу из проводника сервера в конструктор и перестроив ее.


источник
Перетаскивание ошибочной таблицы из Server Explorer в конструктор и восстановление исправили это и для меня.
rstackhouse
4

Вот что вам нужно, чтобы переопределить эту ошибку в коде C #:

            try
            {
                _db.SubmitChanges(ConflictMode.ContinueOnConflict);
            }
            catch (ChangeConflictException e)
            {
                foreach (ObjectChangeConflict occ in _db.ChangeConflicts)
                {
                    occ.Resolve(RefreshMode.KeepChanges);
                }
            }
Марсело Барбоса
источник
Я запланировал элементы, отправленные интерфейсом приложения в базу данных. Они запускают выполнение в службе, каждый в разных потоках. Пользователь может нажать кнопку «Отмена», которая изменяет статус всех невыполненных команд. Служба завершает каждое из них, но обнаруживает, что «Ожидание» было изменено на «Отменено», и не может изменить его на «Завершено». Это устранило проблему для меня.
pwrgreg007 02
2
Также проверьте другие перечисления RefreshMode, например KeepCurrentValues. Обратите внимание, что после использования этой логики вам придется снова вызвать SubmitChanges. См. Msdn.microsoft.com/en-us/library/… .
pwrgreg007 02
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 за вдохновение.

CAK2
источник
0

Я знаю, что на этот вопрос уже давно дан ответ, но здесь я провел последние несколько часов, биться головой о стену, и я просто хотел поделиться своим решением, которое, как оказалось, не связано ни с одним из пунктов в этой ветке:

Кеширование!

Часть select () моего объекта данных использовала кеширование. Когда дело дошло до обновления объекта, возникала ошибка «Строка не найдена или изменена».

В нескольких ответах упоминалось использование разных DataContext, и, оглядываясь назад, вероятно, это то, что происходило, но это не сразу заставило меня думать о кешировании, поэтому, надеюсь, это кому-то поможет!

rtpГарри
источник
0

Недавно я столкнулся с этой ошибкой и обнаружил, что проблема не в моем контексте данных, а в том, что оператор обновления срабатывает внутри триггера после того, как в контексте вызывается фиксация. Триггер пытался обновить поле, не допускающее значения NULL, с помощью нулевого значения, и это вызывало ошибку контекста с сообщением, упомянутым выше.

Я добавляю этот ответ исключительно для того, чтобы помочь другим справиться с этой ошибкой и не нашел решения в ответах выше.

jamisonLikeCode
источник
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). Я назначал десятичное значение, но база данных меняла его, удаляя десятичную часть. Это привело к проблеме с изменением строки.

Просто добавьте это, если кто-то еще столкнется с аналогичной проблемой.

Джон Паске
источник
0

просто используйте Linq2DB, намного лучше

нам во
источник